20 changed files with 477 additions and 15 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,98 @@ |
|||
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.Value; |
|||
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 { |
|||
private final UniPushService uniPushService; |
|||
private final IUserService userService; |
|||
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 sendId, Long recvId){ |
|||
String key = StrUtil.join(":", RedisKey.IM_OFFLINE_NOTIFY_PRIVATE, "*", recvId); |
|||
Set<String> keys = redisTemplate.keys(key); |
|||
redisTemplate.delete(keys); |
|||
} |
|||
|
|||
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