Kaynağa Gözat

feat: 接入unipush

xsx 1 yıl önce
ebeveyn
işleme
7901224f01

+ 5 - 0
im-platform/pom.xml

@@ -115,6 +115,11 @@
             <artifactId>redisson</artifactId>
             <version>3.17.3</version>
         </dependency>
+        <dependency>
+            <groupId>com.getui.push</groupId>
+            <artifactId>restful-sdk</artifactId>
+            <version>1.0.3.0</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 44 - 0
im-platform/src/main/java/com/bx/implatform/config/UniPushConfig.java

@@ -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;
+    }
+}

+ 6 - 0
im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java

@@ -10,6 +10,11 @@ public final class RedisKey {
      * 已读群聊消息位置(已读最大id)
      */
     public static final String IM_GROUP_READED_POSITION = "im:readed:group:position";
+
+    /**
+     * 私聊离线通知
+     */
+    public static final String IM_OFFLINE_NOTIFY_PRIVATE = "im:notify:private";
     /**
      * webrtc 单人通话
      */
@@ -45,4 +50,5 @@ public final class RedisKey {
      */
     public static final String IM_LOCK_RTC_GROUP = IM_LOCK + "rtc:group";
 
+
 }

+ 11 - 2
im-platform/src/main/java/com/bx/implatform/controller/LoginController.java

@@ -1,6 +1,7 @@
 package com.bx.implatform.controller;
 
 import com.bx.implatform.dto.LoginDTO;
+import com.bx.implatform.dto.LogoutDTO;
 import com.bx.implatform.dto.ModifyPwdDTO;
 import com.bx.implatform.dto.RegisterDTO;
 import com.bx.implatform.result.Result;
@@ -21,13 +22,21 @@ public class LoginController {
 
     private final IUserService userService;
 
+
     @PostMapping("/login")
-    @ApiOperation(value = "用户注册", notes = "用户注册")
-    public Result register(@Valid @RequestBody LoginDTO dto) {
+    @ApiOperation(value = "用户登陆", notes = "用户注册")
+    public Result login(@Valid @RequestBody LoginDTO dto) {
         LoginVO vo = userService.login(dto);
         return ResultUtils.success(vo);
     }
 
+    @PostMapping("/logout")
+    @ApiOperation(value = "用户退出登陆", notes = "用户退出登陆")
+    public Result logout(@Valid @RequestBody LogoutDTO dto) {
+        userService.logout(dto);
+        return ResultUtils.success();
+    }
+
 
     @PutMapping("/refreshToken")
     @ApiOperation(value = "刷新token", notes = "用refreshtoken换取新的token")

+ 3 - 0
im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java

@@ -27,4 +27,7 @@ public class LoginDTO {
     @ApiModelProperty(value = "用户密码")
     private String password;
 
+    @ApiModelProperty(value = "用户客户端id")
+    private String cid;
+
 }

+ 19 - 0
im-platform/src/main/java/com/bx/implatform/dto/LogoutDTO.java

@@ -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;
+
+}

+ 5 - 0
im-platform/src/main/java/com/bx/implatform/entity/User.java

@@ -79,6 +79,11 @@ public class User extends Model<User> {
     @TableField("password")
     private String password;
 
+    /**
+     * 客户端id,用于uni-push推送
+     */
+    @TableField("cid")
+    private String cid;
     /**
      * 最后登录时间
      */

+ 33 - 6
im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java

@@ -6,10 +6,12 @@ import com.bx.imclient.annotation.IMListener;
 import com.bx.imclient.listener.MessageListener;
 import com.bx.imcommon.enums.IMListenerType;
 import com.bx.imcommon.enums.IMSendCode;
+import com.bx.imcommon.enums.IMTerminalType;
 import com.bx.imcommon.model.IMSendResult;
 import com.bx.implatform.entity.PrivateMessage;
 import com.bx.implatform.enums.MessageStatus;
 import com.bx.implatform.service.IPrivateMessageService;
+import com.bx.implatform.service.INotifyPrivateService;
 import com.bx.implatform.vo.PrivateMessageVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -25,24 +27,49 @@ public class PrivateMessageListener implements MessageListener<PrivateMessageVO>
     @Lazy
     @Autowired
     private IPrivateMessageService privateMessageService;
+
+    @Lazy
+    @Autowired
+    private INotifyPrivateService uniPushService;
+
     @Override
     public void process(List<IMSendResult<PrivateMessageVO>> results) {
+        // 更新消息状态
+        updateMessageStatus(results);
+        // 推送离线通知
+        sendOfflineNotify(results);
+    }
+
+    private void updateMessageStatus(List<IMSendResult<PrivateMessageVO>> results) {
         Set<Long> messageIds = new HashSet<>();
-        for(IMSendResult<PrivateMessageVO> result : results){
+        for (IMSendResult<PrivateMessageVO> result : results) {
             PrivateMessageVO messageInfo = result.getData();
             // 更新消息状态,这里只处理成功消息,失败的消息继续保持未读状态
             if (result.getCode().equals(IMSendCode.SUCCESS.code())) {
                 messageIds.add(messageInfo.getId());
-                log.info("消息送达,消息id:{},发送者:{},接收者:{},终端:{}", messageInfo.getId(), result.getSender().getId(), result.getReceiver().getId(), result.getReceiver().getTerminal());
+                log.info("消息送达,消息id:{},发送者:{},接收者:{},终端:{}", messageInfo.getId(),
+                    result.getSender().getId(), result.getReceiver().getId(), result.getReceiver().getTerminal());
             }
         }
-        // 批量修改状态
-        if(CollUtil.isNotEmpty(messageIds)){
+        // 对发送成功的消息修改状态
+        if (CollUtil.isNotEmpty(messageIds)) {
             UpdateWrapper<PrivateMessage> updateWrapper = new UpdateWrapper<>();
             updateWrapper.lambda().in(PrivateMessage::getId, messageIds)
-                    .eq(PrivateMessage::getStatus, MessageStatus.UNSEND.code())
-                    .set(PrivateMessage::getStatus, MessageStatus.SENDED.code());
+                .eq(PrivateMessage::getStatus, MessageStatus.UNSEND.code())
+                .set(PrivateMessage::getStatus, MessageStatus.SENDED.code());
             privateMessageService.update(updateWrapper);
         }
     }
+
+    private void sendOfflineNotify(List<IMSendResult<PrivateMessageVO>> results) {
+        for (IMSendResult<PrivateMessageVO> result : results) {
+            PrivateMessageVO messageInfo = result.getData();
+            if (result.getCode().equals(IMSendCode.SUCCESS.code()) && result.getReceiver().getTerminal()
+                .equals(IMTerminalType.APP.code())) {
+                uniPushService.sendMessage(messageInfo.getSendId(), messageInfo.getRecvId(), messageInfo.getContent());
+                log.info("推送离线通知,消息id:{},发送者:{},接收者:{}", messageInfo.getId(), result.getSender().getId(),
+                    result.getReceiver().getId());
+            }
+        }
+    }
 }

+ 98 - 0
im-platform/src/main/java/com/bx/implatform/service/INotifyPrivateService.java

@@ -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);
+    }
+
+}

+ 9 - 0
im-platform/src/main/java/com/bx/implatform/service/IUserService.java

@@ -2,6 +2,7 @@ package com.bx.implatform.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.bx.implatform.dto.LoginDTO;
+import com.bx.implatform.dto.LogoutDTO;
 import com.bx.implatform.dto.ModifyPwdDTO;
 import com.bx.implatform.dto.RegisterDTO;
 import com.bx.implatform.entity.User;
@@ -21,6 +22,14 @@ public interface IUserService extends IService<User> {
      */
     LoginVO login(LoginDTO dto);
 
+    /**
+     * 用户退出登陆
+     *
+     * @param dto 退出登陆dto
+     * @return
+     */
+    void logout(LogoutDTO dto);
+
     /**
      * 修改用户密码
      *

+ 3 - 3
im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java

@@ -75,7 +75,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
         return vo;
     }
 
-    @CacheEvict(value = "#vo.getId()")
+    @CacheEvict(key = "#vo.getId()")
     @Transactional(rollbackFor = Exception.class)
     @Override
     public GroupVO modifyGroup(GroupVO vo) {
@@ -100,7 +100,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
     }
 
     @Transactional(rollbackFor = Exception.class)
-    @CacheEvict(value = "#groupId")
+    @CacheEvict(key = "#groupId")
     @Override
     public void deleteGroup(Long groupId) {
         UserSession session = SessionContext.getSession();
@@ -178,7 +178,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
         return vo;
     }
 
-    @Cacheable(value = "#groupId")
+    @Cacheable(key = "#groupId")
     @Override
     public Group getById(Long groupId) {
         Group group = super.getById(groupId);

+ 5 - 1
im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

@@ -21,6 +21,7 @@ import com.bx.implatform.exception.GlobalException;
 import com.bx.implatform.mapper.PrivateMessageMapper;
 import com.bx.implatform.service.IFriendService;
 import com.bx.implatform.service.IPrivateMessageService;
+import com.bx.implatform.service.INotifyPrivateService;
 import com.bx.implatform.session.SessionContext;
 import com.bx.implatform.session.UserSession;
 import com.bx.implatform.util.BeanUtils;
@@ -43,7 +44,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
     private final IFriendService friendService;
     private final IMClient imClient;
     private final SensitiveFilterUtil sensitiveFilterUtil;
-
+    private final INotifyPrivateService notifyPrivateService;
     @Override
     public Long sendMessage(PrivateMessageDTO dto) {
         UserSession session = SessionContext.getSession();
@@ -218,6 +219,9 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
                 .eq(PrivateMessage::getStatus, MessageStatus.SENDED.code())
                 .set(PrivateMessage::getStatus, MessageStatus.READED.code());
         this.update(updateWrapper);
+        // 清除通知会话信息
+        notifyPrivateService.removeNotifySession(msgInfo.getSendId(),msgInfo.getRecvId());
+
         log.info("消息已读,接收方id:{},发送方id:{}", session.getUserId(), friendId);
     }
 

+ 24 - 0
im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java

@@ -1,8 +1,10 @@
 package com.bx.implatform.service.impl;
 
+import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.bx.imclient.IMClient;
@@ -10,6 +12,7 @@ import com.bx.imcommon.enums.IMTerminalType;
 import com.bx.imcommon.util.JwtUtil;
 import com.bx.implatform.config.JwtProperties;
 import com.bx.implatform.dto.LoginDTO;
+import com.bx.implatform.dto.LogoutDTO;
 import com.bx.implatform.dto.ModifyPwdDTO;
 import com.bx.implatform.dto.RegisterDTO;
 import com.bx.implatform.entity.Friend;
@@ -29,6 +32,7 @@ import com.bx.implatform.vo.OnlineTerminalVO;
 import com.bx.implatform.vo.UserVO;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.util.Strings;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -47,6 +51,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
     private final JwtProperties jwtProperties;
     private final IMClient imClient;
 
+
     @Override
     public LoginVO login(LoginDTO dto) {
         User user = this.findUserByUserName(dto.getUserName());
@@ -56,6 +61,12 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
         if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
             throw new GlobalException(ResultCode.PASSWOR_ERROR);
         }
+        // 更新用户登陆时间和cid
+        user.setLastLoginTime(new Date());
+        if(StrUtil.isNotEmpty(dto.getCid())){
+            user.setCid(dto.getCid());
+        }
+        this.updateById(user);
         // 生成token
         UserSession session = BeanUtils.copyProperties(user, UserSession.class);
         session.setUserId(user.getId());
@@ -71,6 +82,19 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
         return vo;
     }
 
+    @Override
+    public void logout(LogoutDTO dto) {
+        UserSession session = SessionContext.getSession();
+        if(StrUtil.isNotEmpty(dto.getCid())){
+            // 清除cid,不再推送离线通知
+            LambdaUpdateWrapper<User> wrapper =  Wrappers.lambdaUpdate();
+            wrapper.eq(User::getId,session.getUserId());
+            wrapper.eq(User::getCid,dto.getCid());
+            wrapper.set(User::getCid, Strings.EMPTY);
+            this.update(wrapper);
+        }
+    }
+
     @Override
     public LoginVO refreshToken(String refreshToken) {
         //验证 token

+ 122 - 0
im-platform/src/main/java/com/bx/implatform/service/thirdparty/UniPushService.java

@@ -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;
+    }
+}

+ 43 - 0
im-platform/src/main/java/com/bx/implatform/session/NotifySession.java

@@ -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;
+
+}

+ 10 - 0
im-platform/src/main/resources/application.yml

@@ -43,6 +43,16 @@ webrtc:
   iceServers:
     - urls: stun:stun.l.google.com:19302
 
+notify:
+  enable: true
+  max:
+    private: -1 # 私聊单个会话最大通知数量,-1表示不显示
+    group: 10  # 群聊单个会话最大通知数量,-1表示不显示
+  uniPush:
+    appId: nyK71XQYUy9s7Vlzoutlp1
+    appKey: XtG6NkUSL98evtpLSE0jYA
+    masterSecret: MxApXxsx057jcPCeC0cXk6
+
 jwt:
   accessToken:
     expireIn: 1800 #半个小时

+ 1 - 0
im-platform/src/main/resources/db/db.sql

@@ -9,6 +9,7 @@ create table `im_user`(
     `sex`  tinyint(1) default 0 comment '性别 0:男 1:女',
     `type`  smallint default 1 comment '用户类型 1:普通用户 2:审核账户',
     `signature` varchar(1024) default '' comment '个性签名',
+    `cid` varchar(255) default '' comment '客户端id,用于uni-push推送',
     `last_login_time`  datetime DEFAULT null comment '最后登录时间',
     `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
     unique key `idx_user_name`(user_name),

+ 17 - 0
im-uniapp/App.vue

@@ -19,6 +19,7 @@
 				store.dispatch("load").then(() => {
 					// 初始化websocket
 					this.initWebSocket();
+					this.initUniPush();
 				}).catch((e) => {
 					console.log(e);
 					this.exit();
@@ -312,6 +313,21 @@
 					url: '/user/self',
 					method: 'GET'
 				})
+			},
+			initUniPush(){
+				// #ifdef APP-PLUS  
+					plus.push.setAutoNotification(true);
+					const clientInfo = plus.push.getClientInfo();
+					console.log("clientInfo",clientInfo);
+					plus.push.addEventListener('click', (message)=>{
+						const messages = plus.push.getAllMessage();
+						console.log("messages",messages)
+						console.log("click",message)
+					});  
+					plus.push.addEventListener('receive', (message)=>{
+						console.log("receive",message)
+					});  
+				// #endif  
 			}
 		},
 		onLaunch() {
@@ -333,6 +349,7 @@
 				})
 				// #endif
 			}
+			
 		}
 	}
 </script>

+ 12 - 2
im-uniapp/manifest.json

@@ -20,7 +20,8 @@
         "modules" : {
             "Camera" : {},
             "Record" : {},
-            "Bluetooth" : {}
+            "Bluetooth" : {},
+            "Push" : {}
         },
         /* 应用发布信息 */
         "distribute" : {
@@ -59,7 +60,16 @@
             /* SDK配置 */
             "sdkConfigs" : {
                 "ad" : {},
-                "speech" : {}
+                "speech" : {},
+                "push" : {
+                    "unipush" : {
+                        "icons" : {
+                            "small" : {
+                                "ldpi" : "C:/Users/82565/Desktop/盒子/logo.png"
+                            }
+                        }
+                    }
+                }
             },
             "icons" : {
                 "android" : {

+ 7 - 1
im-uniapp/pages/login/login.vue

@@ -21,7 +21,8 @@
 				loginForm: {
 					terminal: 1, // APP终端
 					userName: '',
-					password: ''
+					password: '',
+					cid: ''
 				},
 				rules: {
 					userName: {
@@ -41,6 +42,11 @@
 		},
 		methods: {
 			submit() {
+				// APP需要上报cid,用于离线推送
+				// #ifdef APP-PLUS
+					const clientInfo = plus.push.getClientInfo();
+					this.loginForm.cid = clientInfo.clientid;
+				// #endif  
 				this.$http({
 					url: '/login',
 					data: this.loginForm,