18 changed files with 477 additions and 10 deletions
@ -0,0 +1,44 @@ |
|||||
|
package com.bx.implatform.config; |
||||
|
|
||||
|
import com.getui.push.v2.sdk.ApiHelper; |
||||
|
import com.getui.push.v2.sdk.GtApiConfiguration; |
||||
|
import com.getui.push.v2.sdk.api.PushApi; |
||||
|
import lombok.Data; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* @author: 谢绍许 |
||||
|
* @date: 2024-07-06 |
||||
|
* @version: 1.0 |
||||
|
*/ |
||||
|
@Data |
||||
|
@Component |
||||
|
public class UniPushConfig { |
||||
|
|
||||
|
@Value("${notify.uniPush.appId}") |
||||
|
private String appId; |
||||
|
@Value("${notify.uniPush.appKey}") |
||||
|
private String appKey; |
||||
|
@Value("${notify.uniPush.masterSecret}") |
||||
|
private String masterSecret; |
||||
|
|
||||
|
@Bean |
||||
|
public GtApiConfiguration uniPushConfiguration(){ |
||||
|
GtApiConfiguration apiConfiguration = new GtApiConfiguration(); |
||||
|
apiConfiguration.setAppId(appId); |
||||
|
apiConfiguration.setAppKey(appKey); |
||||
|
apiConfiguration.setMasterSecret(masterSecret); |
||||
|
return apiConfiguration; |
||||
|
} |
||||
|
|
||||
|
@Bean |
||||
|
public PushApi uniPushApi(GtApiConfiguration configuration){ |
||||
|
// 实例化ApiHelper对象,用于创建接口对象
|
||||
|
ApiHelper apiHelper = ApiHelper.build(configuration); |
||||
|
// 创建对象,建议复用。目前有PushApi、StatisticApi、UserApi
|
||||
|
PushApi pushApi = apiHelper.creatApi(PushApi.class); |
||||
|
return pushApi; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package com.bx.implatform.dto; |
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import javax.validation.constraints.Max; |
||||
|
import javax.validation.constraints.Min; |
||||
|
import javax.validation.constraints.NotEmpty; |
||||
|
import javax.validation.constraints.NotNull; |
||||
|
|
||||
|
@Data |
||||
|
@ApiModel("用户登录DTO") |
||||
|
public class LogoutDTO { |
||||
|
|
||||
|
@ApiModelProperty(value = "用户客户端id") |
||||
|
private String cid; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
package com.bx.implatform.service; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.bx.implatform.contant.RedisKey; |
||||
|
import com.bx.implatform.entity.User; |
||||
|
import com.bx.implatform.session.NotifySession; |
||||
|
import com.bx.implatform.service.thirdparty.UniPushService; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.context.annotation.Lazy; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Objects; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* 私聊离线通知服务 |
||||
|
* |
||||
|
* @author: blue |
||||
|
* @date: 2024-07-06 |
||||
|
* @version: 1.0 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
public class INotifyPrivateService { |
||||
|
@Lazy |
||||
|
@Autowired |
||||
|
private IUserService userService; |
||||
|
private final UniPushService uniPushService; |
||||
|
private final RedisTemplate<String, Object> redisTemplate; |
||||
|
@Value("${notify.enable}") |
||||
|
private Boolean enable = false; |
||||
|
@Value("${notify.max.private}") |
||||
|
private Integer max = -1; |
||||
|
public void sendMessage(Long sendId, Long recvId, String content) { |
||||
|
if(!enable){ |
||||
|
return; |
||||
|
} |
||||
|
NotifySession session = findNotifySession(sendId, recvId); |
||||
|
if (Objects.isNull(session)) { |
||||
|
session = createNotifySession(sendId, recvId); |
||||
|
} |
||||
|
// 未上报cid,无法推送
|
||||
|
if (StrUtil.isEmpty(session.getCid())) { |
||||
|
log.info("用户'{}'未上报cid,无法推送离线通知", recvId); |
||||
|
return; |
||||
|
} |
||||
|
// 已达到最大数量
|
||||
|
if (max > 0 && session.getCount() >= max) { |
||||
|
log.info("用户'{}'已到达推送数量上线,不再推送离线通知", recvId); |
||||
|
return; |
||||
|
} |
||||
|
// 消息数量加1
|
||||
|
session.setCount(session.getCount()+1); |
||||
|
String body = String.format("%s:%s", session.getSendNickName(),content); |
||||
|
// 大于1条时需要展示数量
|
||||
|
if (session.getCount() > 1) { |
||||
|
body = String.format("[%d条] ", session.getCount()) + body; |
||||
|
} |
||||
|
uniPushService.pushByCid(session,body); |
||||
|
// 保存会话
|
||||
|
saveNotifySession(session,sendId,recvId); |
||||
|
} |
||||
|
|
||||
|
public void removeNotifySession( Long recvId){ |
||||
|
String key = StrUtil.join(":", RedisKey.IM_OFFLINE_NOTIFY_PRIVATE, "*", recvId); |
||||
|
Set<String> keys = redisTemplate.keys(key); |
||||
|
redisTemplate.delete(keys); |
||||
|
} |
||||
|
|
||||
|
public void removeNotifySession(Long sendId, Long recvId){ |
||||
|
String key = StrUtil.join(":", RedisKey.IM_OFFLINE_NOTIFY_PRIVATE, sendId, recvId); |
||||
|
redisTemplate.delete(key); |
||||
|
} |
||||
|
|
||||
|
private NotifySession createNotifySession(Long sendId, Long recvId) { |
||||
|
String key = StrUtil.join(":", RedisKey.IM_OFFLINE_NOTIFY_PRIVATE, sendId, recvId); |
||||
|
User sendUser = userService.getById(sendId); |
||||
|
User recvUser = userService.getById(recvId); |
||||
|
NotifySession session = new NotifySession(); |
||||
|
session.setCount(0); |
||||
|
session.setCid(recvUser.getCid()); |
||||
|
session.setTitle(sendUser.getNickName()); |
||||
|
session.setSendNickName(sendUser.getNickName()); |
||||
|
session.setNotifyId(Math.abs(key.hashCode())); |
||||
|
session.setLogo(sendUser.getHeadImageThumb()); |
||||
|
redisTemplate.opsForValue().set(key, session, 30, TimeUnit.DAYS); |
||||
|
return session; |
||||
|
} |
||||
|
|
||||
|
private NotifySession findNotifySession(Long sendId, Long recvId) { |
||||
|
String key = StrUtil.join(":", RedisKey.IM_OFFLINE_NOTIFY_PRIVATE, sendId, recvId); |
||||
|
return (NotifySession)redisTemplate.opsForValue().get(key); |
||||
|
} |
||||
|
|
||||
|
private void saveNotifySession(NotifySession session, Long sendId, Long recvId) { |
||||
|
String key = StrUtil.join(":", RedisKey.IM_OFFLINE_NOTIFY_PRIVATE, sendId, recvId); |
||||
|
redisTemplate.opsForValue().set(key, session); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,122 @@ |
|||||
|
package com.bx.implatform.service.thirdparty; |
||||
|
|
||||
|
import com.bx.implatform.session.NotifySession; |
||||
|
import com.getui.push.v2.sdk.api.PushApi; |
||||
|
import com.getui.push.v2.sdk.common.ApiResult; |
||||
|
import com.getui.push.v2.sdk.dto.req.Audience; |
||||
|
import com.getui.push.v2.sdk.dto.req.Settings; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.PushChannel; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.PushDTO; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.PushMessage; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.android.AndroidDTO; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.android.GTNotification; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.android.ThirdNotification; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.android.Ups; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.ios.Alert; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.ios.Aps; |
||||
|
import com.getui.push.v2.sdk.dto.req.message.ios.IosDTO; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* @author: 谢绍许 |
||||
|
* @date: 2024-07-06 |
||||
|
* @version: 1.0 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@RequiredArgsConstructor |
||||
|
public class UniPushService { |
||||
|
|
||||
|
private final PushApi pushApi; |
||||
|
|
||||
|
public void pushByCid(NotifySession session, String body){ |
||||
|
//根据cid进行单推
|
||||
|
PushDTO<Audience> pushDTO = new PushDTO<Audience>(); |
||||
|
// 设置推送参数,requestid需要每次变化唯一
|
||||
|
pushDTO.setRequestId(System.currentTimeMillis()+""); |
||||
|
Settings settings = new Settings(); |
||||
|
pushDTO.setSettings(settings); |
||||
|
//消息有效期,走厂商消息必须设置该值
|
||||
|
settings.setTtl(3600000); |
||||
|
//在线走个推通道时推送的消息体
|
||||
|
PushMessage pushMessage = new PushMessage(); |
||||
|
pushDTO.setPushMessage(pushMessage); |
||||
|
//此格式的透传消息由 unipush 做了特殊处理,会自动展示通知栏。开发者也可自定义其它格式,在客户端自己处理。
|
||||
|
GTNotification gtNotification = new GTNotification(); |
||||
|
gtNotification.setTitle(session.getTitle()); |
||||
|
gtNotification.setBody(body); |
||||
|
gtNotification.setClickType("startapp"); |
||||
|
gtNotification.setNotifyId(session.getNotifyId().toString()); |
||||
|
gtNotification.setLogoUrl(session.getLogo()); |
||||
|
gtNotification.setBadgeAddNum("1"); |
||||
|
pushMessage.setNotification(gtNotification); |
||||
|
// 设置接收人信息
|
||||
|
Audience audience = new Audience(); |
||||
|
pushDTO.setAudience(audience); |
||||
|
audience.addCid(session.getCid()); |
||||
|
//设置离线推送时的消息体
|
||||
|
PushChannel pushChannel = new PushChannel(); |
||||
|
//安卓离线厂商通道推送的消息体
|
||||
|
AndroidDTO androidDTO = new AndroidDTO(); |
||||
|
Ups ups = new Ups(); |
||||
|
ThirdNotification thirdNotification = new ThirdNotification(); |
||||
|
ups.setNotification(thirdNotification); |
||||
|
ups.setOptions(buildOptions(session.getLogo())); |
||||
|
thirdNotification.setTitle(session.getTitle()); |
||||
|
thirdNotification.setBody(body); |
||||
|
thirdNotification.setNotifyId(session.getNotifyId().toString()); |
||||
|
// 打开首页
|
||||
|
thirdNotification.setClickType("startapp"); |
||||
|
androidDTO.setUps(ups); |
||||
|
pushChannel.setAndroid(androidDTO); |
||||
|
// ios离线apn通道推送的消息体
|
||||
|
Alert alert = new Alert(); |
||||
|
alert.setTitle(session.getTitle()); |
||||
|
alert.setBody(body); |
||||
|
Aps aps = new Aps(); |
||||
|
// 0:普通通知消息 1:静默推送(无通知栏消息),静默推送时不需要填写其他参数。苹果建议1小时最多推送3条静默消息
|
||||
|
aps.setContentAvailable(0); |
||||
|
// default: 系统铃声 不填:无声
|
||||
|
aps.setSound("default"); |
||||
|
aps.setAlert(alert); |
||||
|
|
||||
|
IosDTO iosDTO = new IosDTO(); |
||||
|
iosDTO.setAps(aps); |
||||
|
iosDTO.setType("notify"); |
||||
|
pushChannel.setIos(iosDTO); |
||||
|
pushDTO.setPushChannel(pushChannel); |
||||
|
// 进行cid单推
|
||||
|
ApiResult<Map<String, Map<String, String>>> apiResult = pushApi.pushToSingleByCid(pushDTO); |
||||
|
if (apiResult.isSuccess()) { |
||||
|
log.info("推送成功,{}",apiResult.getData()); |
||||
|
} else { |
||||
|
log.info("推送失败,code:{},msg:{}",apiResult.getCode(),apiResult.getMsg()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private Map<String, Map<String, Object>> buildOptions(String logo){ |
||||
|
Map<String, Map<String, Object>> options = new HashMap<>(); |
||||
|
// 小米
|
||||
|
Map<String,Object> xm = new HashMap<>(); |
||||
|
xm.put("/extra.notification_style_type",1); |
||||
|
xm.put("/extra.notification_large_icon_uri",logo); |
||||
|
options.put("XM",xm); |
||||
|
// 华为
|
||||
|
Map<String,Object> hw = new HashMap<>(); |
||||
|
hw.put("/message/android/notification/image",logo); |
||||
|
hw.put("/message/android/notification/style",1); |
||||
|
options.put("HW",hw); |
||||
|
// 荣耀
|
||||
|
Map<String,Object> ho = new HashMap<>(); |
||||
|
hw.put("/android/notification/badge/addNum",1); |
||||
|
hw.put("/android/notification/icon",logo); |
||||
|
options.put("HW",hw); |
||||
|
return options; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
package com.bx.implatform.session; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* 离线通知session |
||||
|
* @author: blue |
||||
|
* @date: 2024-07-06 |
||||
|
* @version: 1.0 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class NotifySession { |
||||
|
/** |
||||
|
* 接收方客户端id |
||||
|
*/ |
||||
|
private String cid; |
||||
|
|
||||
|
/** |
||||
|
* 通知id |
||||
|
*/ |
||||
|
private Integer notifyId; |
||||
|
|
||||
|
/** |
||||
|
* 发送方用户名 |
||||
|
*/ |
||||
|
private String sendNickName; |
||||
|
|
||||
|
/** |
||||
|
* 标题 |
||||
|
*/ |
||||
|
private String title; |
||||
|
|
||||
|
/** |
||||
|
* 显示图标 |
||||
|
*/ |
||||
|
private String logo; |
||||
|
|
||||
|
/** |
||||
|
* 消息数量 |
||||
|
*/ |
||||
|
private Integer count; |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue