Browse Source

add 即时通信

18339543638 3 years ago
parent
commit
e002ac55db
39 changed files with 1074 additions and 50 deletions
  1. 1 1
      nb-admin/src/main/resources/application.yml
  2. 3 4
      nb-common/config-common/src/main/java/com/nb/common/config/utils/RedissonUtil.java
  3. 3 2
      nb-common/crud-common/src/main/java/com/nb/common/crud/controller/BaseCrudController.java
  4. 1 1
      nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultMessageListener.java
  5. 1 5
      nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultWebSocketMsgHandler.java
  6. 1 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/MessageResponse.java
  7. 13 6
      nb-common/ws-common/src/main/java/com/nb/common/websocket/MessagingRequest.java
  8. 30 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/PubResponse.java
  9. 12 1
      nb-common/ws-common/src/main/java/com/nb/common/websocket/TopicMessage.java
  10. 23 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/event/PubMsgEvent.java
  11. 32 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/filter/DefaultPubMsgFilter.java
  12. 24 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/filter/PubMsgFilter.java
  13. 54 12
      nb-common/ws-common/src/main/java/com/nb/common/websocket/handler/Subscribe.java
  14. 12 0
      nb-im/pom.xml
  15. 31 0
      nb-im/src/main/java/com/nb/im/controller/ChatRooMsgController.java
  16. 34 0
      nb-im/src/main/java/com/nb/im/controller/ChatRoomController.java
  17. 54 0
      nb-im/src/main/java/com/nb/im/entity/ChatRoomEntity.java
  18. 51 0
      nb-im/src/main/java/com/nb/im/entity/ChatRoomMsgEntity.java
  19. 54 0
      nb-im/src/main/java/com/nb/im/entity/ChatRoomUserEntity.java
  20. 30 0
      nb-im/src/main/java/com/nb/im/enums/ImMsgType.java
  21. 26 0
      nb-im/src/main/java/com/nb/im/enums/ImStatusEnum.java
  22. 24 0
      nb-im/src/main/java/com/nb/im/enums/SponsorEnum.java
  23. 118 0
      nb-im/src/main/java/com/nb/im/listener/ConsultMsgListener.java
  24. 67 0
      nb-im/src/main/java/com/nb/im/listener/PubMsgListener.java
  25. 16 0
      nb-im/src/main/java/com/nb/im/mapper/ChatRoomMapper.java
  26. 16 0
      nb-im/src/main/java/com/nb/im/mapper/ChatRoomMsgMapper.java
  27. 16 0
      nb-im/src/main/java/com/nb/im/mapper/ChatRoomUserMapper.java
  28. 31 0
      nb-im/src/main/java/com/nb/im/service/LocalChatRoomMsgService.java
  29. 40 0
      nb-im/src/main/java/com/nb/im/service/LocalChatRoomService.java
  30. 33 0
      nb-im/src/main/java/com/nb/im/service/LocalChatRoomUserService.java
  31. 30 0
      nb-im/src/main/java/com/nb/im/ws/ImSubscribe.java
  32. 107 0
      nb-im/src/main/java/com/nb/im/ws/PubMsgInfo.java
  33. 48 0
      nb-im/src/main/java/com/nb/im/ws/filter/MsgFormatFilter.java
  34. 22 3
      nb-service-api/app-msg-api/src/main/java/com/nb/app/msg/bean/MsgBean.java
  35. 9 9
      nb-service/app-assistant/src/main/java/com/nb/app/assistant/service/LocalAssistantUserBindService.java
  36. 1 1
      nb-service/app-assistant/src/main/java/com/nb/app/assistant/service/LocalAssistantUserService.java
  37. 1 1
      nb-service/iot-service/src/main/java/com/nb/aliyun/service/AliyunConsumerGroupService.java
  38. 3 3
      nb-service/web-service/src/main/java/com/nb/web/service/bus/websocket/DefaultHisMsgHandler.java
  39. 2 1
      nb-service/web-service/src/main/java/com/nb/web/service/log/controller/SysRunningLogController.java

+ 1 - 1
nb-admin/src/main/resources/application.yml

@@ -28,7 +28,7 @@ tio:
     server:
     server:
       port: 9000
       port: 9000
       #心跳检测在业务层面自定义进行 ms 毫秒级 按照最后一次接受数据包算 30s
       #心跳检测在业务层面自定义进行 ms 毫秒级 按照最后一次接受数据包算 30s
-      heartbeat-timeout: 30000
+      heartbeat-timeout: -1
       #是否支持集群,集群开启需要redis
       #是否支持集群,集群开启需要redis
     cluster:
     cluster:
       enabled: false
       enabled: false

+ 3 - 4
nb-common/config-common/src/main/java/com/nb/common/config/utils/RedissonUtil.java

@@ -38,12 +38,11 @@ public class RedissonUtil {
 
 
     private static Map<String,RLock> lockMap=new ConcurrentHashMap<>();
     private static Map<String,RLock> lockMap=new ConcurrentHashMap<>();
 
 
-    public RPatternTopic getPatternTopic(String pattern){
-        return redissonClient.getPatternTopic(pattern);
-    }
+    private static Map<String,RTopic> rTopicMap=new ConcurrentHashMap<>();
 
 
     public RTopic getTopic(String pattern){
     public RTopic getTopic(String pattern){
-        return redissonClient.getTopic(pattern);
+//        return redissonClient.getTopic(pattern);
+        return rTopicMap.computeIfAbsent(pattern,k-> redissonClient.getTopic(pattern));
     }
     }
 
 
     public RTopic getTopic(String pattern, Codec codec){
     public RTopic getTopic(String pattern, Codec codec){

+ 3 - 2
nb-common/crud-common/src/main/java/com/nb/common/crud/controller/BaseCrudController.java

@@ -1,7 +1,8 @@
 package com.nb.common.crud.controller;
 package com.nb.common.crud.controller;
 
 
-import cn.dev33.satoken.SaManager;
 import cn.dev33.satoken.stp.StpLogic;
 import cn.dev33.satoken.stp.StpLogic;
+import com.nb.auth.utils.SecurityUtil;
+
 import java.io.Serializable;
 import java.io.Serializable;
 
 
 /**
 /**
@@ -33,6 +34,6 @@ public abstract class BaseCrudController<E, K extends Serializable> implements
 
 
     @Override
     @Override
     public StpLogic getStpLogin() {
     public StpLogic getStpLogin() {
-        return SaManager.getStpLogic("");
+        return SecurityUtil.getStpLogic();
     }
     }
 }
 }

+ 1 - 1
nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultMessageListener.java

@@ -35,7 +35,7 @@ public class DefaultMessageListener implements PatternMessageListener<TopicMessa
             String json = null;
             String json = null;
             try {
             try {
                 json = objectMapper.writeValueAsString(MessageResponse.of(id, "result", msg.getParam(),
                 json = objectMapper.writeValueAsString(MessageResponse.of(id, "result", msg.getParam(),
-                        msg.getMessage()));
+                        msg.getMessage(),msg.getMsgId()));
                 Tio.send(channelContext, WsResponse.fromText(json, WsPacket.CHARSET_NAME));
                 Tio.send(channelContext, WsResponse.fromText(json, WsPacket.CHARSET_NAME));
                 if(log.isDebugEnabled()){
                 if(log.isDebugEnabled()){
                     log.debug("通道【{}】发送消息【{}】",channelContext.toString(),json);
                     log.debug("通道【{}】发送消息【{}】",channelContext.toString(),json);

+ 1 - 5
nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultWebSocketMsgHandler.java

@@ -38,7 +38,6 @@ public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
     private final List<WsHandler> messageHandlers;
     private final List<WsHandler> messageHandlers;
     private final List<HisMsgHandler> hisMsgHandlers;
     private final List<HisMsgHandler> hisMsgHandlers;
     private final List<IWebSocketAuthFilter> authFilters;
     private final List<IWebSocketAuthFilter> authFilters;
-
     @Override
     @Override
     public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) {
     public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) {
         return httpResponse;
         return httpResponse;
@@ -63,7 +62,7 @@ public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
             if(loginUser!=null&&loginUser instanceof LoginUser){
             if(loginUser!=null&&loginUser instanceof LoginUser){
                 SpringUtil.publishEvent(new ConnectedEvent(this, (LoginUser) loginUser,channelContext));
                 SpringUtil.publishEvent(new ConnectedEvent(this, (LoginUser) loginUser,channelContext));
             }
             }
-            Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(R.success("连接成功")),"utf-8"));
+            Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(R.success("连接成功")),WsPacket.CHARSET_NAME));
         }else {
         }else {
             Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(R.fail("授权失败")), WsPacket.CHARSET_NAME));
             Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(R.fail("授权失败")), WsPacket.CHARSET_NAME));
             Thread.sleep(20);
             Thread.sleep(20);
@@ -125,9 +124,6 @@ public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
      */
      */
     private void handleUserMessage( String message, ChannelContext channelContext){
     private void handleUserMessage( String message, ChannelContext channelContext){
         MessagingRequest messagingRequest = JSONUtil.toBean(message, MessagingRequest.class);
         MessagingRequest messagingRequest = JSONUtil.toBean(message, MessagingRequest.class);
-        if(messagingRequest.isFromHis()){
-            return;
-        }
         List<WsHandler> collect = messageHandlers
         List<WsHandler> collect = messageHandlers
                 .parallelStream()
                 .parallelStream()
                 .filter(handler -> messagingRequest.getId().equals(handler.getId()))
                 .filter(handler -> messagingRequest.getId().equals(handler.getId()))

+ 1 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/MessageResponse.java

@@ -19,4 +19,5 @@ public class MessageResponse implements Serializable {
     private String type;
     private String type;
     private String param;
     private String param;
     private Object payload;
     private Object payload;
+    private String msgId;
 }
 }

+ 13 - 6
nb-common/ws-common/src/main/java/com/nb/common/websocket/MessagingRequest.java

@@ -3,9 +3,15 @@ package com.nb.common.websocket;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
 import lombok.Data;
 import lombok.Data;
 
 
+import java.io.Serializable;
 import java.util.*;
 import java.util.*;
 @Data
 @Data
-public class MessagingRequest {
+public class MessagingRequest implements Serializable {
+
+    /**
+     * 消息id
+     */
+    private String msgId;
 
 
     /**
     /**
      * 心跳、设备或报警信息,例如ping、device,alarm,patient
      * 心跳、设备或报警信息,例如ping、device,alarm,patient
@@ -21,19 +27,20 @@ public class MessagingRequest {
      * 产品名称
      * 产品名称
      */
      */
     private String productName;
     private String productName;
+
     /**
     /**
      * 订阅id,如设备id、病人id、报警类型、设备状态类型
      * 订阅id,如设备id、病人id、报警类型、设备状态类型
      */
      */
     private List<String> params;
     private List<String> params;
 
 
+    /**
+     * 发布消息负载
+     */
+    private Object payload;
 
 
     private String tenantId;
     private String tenantId;
 
 
     public static enum Type{
     public static enum Type{
-        sub,unsub
-    }
-
-    public boolean isFromHis(){
-        return StrUtil.isNullOrUndefined(id)||StrUtil.isNullOrUndefined(tenantId);
+        sub,unsub,pub
     }
     }
 }
 }

+ 30 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/PubResponse.java

@@ -0,0 +1,30 @@
+package com.nb.common.websocket;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName PubResponse.java
+ * @Description TODO
+ * @createTime 2022年08月16日 14:33:00
+ */
+@Data
+@AllArgsConstructor(staticName = "of")
+public class PubResponse implements Serializable {
+    private String msgId;
+    private boolean success;
+    private String errorMsg;
+
+
+    public static PubResponse success(String msgId){
+        return PubResponse.of(msgId,true,"");
+    }
+
+    public static PubResponse fail(String msgId,String errorMsg){
+        return PubResponse.of(msgId,false,errorMsg);
+    }
+}

+ 12 - 1
nb-common/ws-common/src/main/java/com/nb/common/websocket/TopicMessage.java

@@ -7,10 +7,21 @@ import lombok.NoArgsConstructor;
 import java.io.Serializable;
 import java.io.Serializable;
 
 
 @Data
 @Data
-@AllArgsConstructor(staticName = "of")
+@AllArgsConstructor
 @NoArgsConstructor
 @NoArgsConstructor
 public class TopicMessage implements Serializable {
 public class TopicMessage implements Serializable {
     private Object message;
     private Object message;
     private String param;
     private String param;
+    private String msgId;
+
+
+    public static TopicMessage of(Object message,String param){
+        return of(message,param,null);
+    }
+
+
+    public static TopicMessage of(Object message,String param,String msgId){
+        return new TopicMessage(message,param,msgId);
+    }
 
 
 }
 }

+ 23 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/event/PubMsgEvent.java

@@ -0,0 +1,23 @@
+package com.nb.common.websocket.event;
+
+import com.nb.common.websocket.MessagingRequest;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName PubMsgEvent.java
+ * @Description TODO
+ * @createTime 2022年08月16日 14:31:00
+ */
+@Getter
+public class PubMsgEvent  extends ApplicationEvent {
+
+    private MessagingRequest msg;
+
+    public PubMsgEvent(Object source,MessagingRequest msg) {
+        super(source);
+        this.msg=msg;
+    }
+}

+ 32 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/filter/DefaultPubMsgFilter.java

@@ -0,0 +1,32 @@
+package com.nb.common.websocket.filter;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.nb.common.websocket.MessagingRequest;
+import com.nb.common.websocket.PubResponse;
+import com.nb.core.result.R;
+import org.springframework.stereotype.Component;
+import org.tio.core.ChannelContext;
+import org.tio.core.Tio;
+import org.tio.websocket.common.WsPacket;
+import org.tio.websocket.common.WsResponse;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName DefaultPubMsgFilter.java
+ * @Description 通用过滤器
+ * @createTime 2022年08月16日 14:30:00
+ */
+@Component
+public class DefaultPubMsgFilter implements PubMsgFilter {
+    @Override
+    public boolean doFilter(ChannelContext channelContext, MessagingRequest source) {
+        if(StrUtil.isEmpty(source.getMsgId())){
+            Tio.send(channelContext, WsResponse.fromText(JSONUtil.toJsonStr(PubResponse.fail("","发布消息时msgId不能为空")), WsPacket.CHARSET_NAME));
+            return false;
+        }
+        return ObjectUtil.isNotNull(source.getPayload());
+    }
+}

+ 24 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/filter/PubMsgFilter.java

@@ -0,0 +1,24 @@
+package com.nb.common.websocket.filter;
+
+import com.nb.common.websocket.MessagingRequest;
+import org.tio.core.ChannelContext;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName PubMsgHandler.java
+ * @Description 发布消息过滤器
+ * @createTime 2022年08月16日 14:05:00
+ */
+public interface PubMsgFilter {
+
+    /**
+     * 描述: 判断消息是否能够发布
+     * @author lifang
+     * @date 2022/8/16 14:07
+     * @param channelContext
+     * @param source
+     * @return boolean true 可以发布 ;false 不可以发布
+     */
+    boolean doFilter(ChannelContext channelContext, MessagingRequest source);
+}

+ 54 - 12
nb-common/ws-common/src/main/java/com/nb/common/websocket/handler/Subscribe.java

@@ -2,21 +2,28 @@ package com.nb.common.websocket.handler;
 
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.json.JSONUtil;
 import com.nb.auth.bean.LoginUser;
 import com.nb.auth.bean.LoginUser;
+import com.nb.common.websocket.*;
+import com.nb.common.websocket.event.PubMsgEvent;
+import com.nb.common.websocket.filter.PubMsgFilter;
 import com.nb.core.Constants;
 import com.nb.core.Constants;
-import com.nb.common.websocket.DefaultMessageListener;
-import com.nb.common.websocket.MessagingRequest;
-import com.nb.common.websocket.TopicMessage;
-import com.nb.common.websocket.WebSocketConstant;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nb.core.result.R;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.tomcat.util.descriptor.web.WebXml;
 import org.redisson.api.RPatternTopic;
 import org.redisson.api.RPatternTopic;
+import org.redisson.api.RTopic;
 import org.redisson.api.RedissonClient;
 import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.util.ConcurrentReferenceHashMap;
 import org.tio.core.ChannelContext;
 import org.tio.core.ChannelContext;
+import org.tio.core.Tio;
+import org.tio.websocket.common.WsPacket;
+import org.tio.websocket.common.WsResponse;
 
 
 import java.util.*;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 /**
 /**
@@ -33,11 +40,18 @@ public abstract class Subscribe implements WsHandler {
 
 
     @Autowired
     @Autowired
     private RedissonClient redissonClient;
     private RedissonClient redissonClient;
+
+    @Autowired(required = false)
+    private List<PubMsgFilter> msgFilters;
     /**
     /**
-     * 存储主题与redis通道关联
+     * 订阅主题缓存
      */
      */
-    private static Map<String, RPatternTopic> topicMap=new ConcurrentReferenceHashMap<>();
+    private static Map<String, RPatternTopic> subTopicMap =new ConcurrentHashMap<>();
 
 
+    /**
+     * 发布主题缓存
+     */
+    private static Map<String, RTopic> pubTopicMap =new ConcurrentHashMap<>();
 
 
     public TopicWrapper getTopic(String productName,String param,String tenantId){
     public TopicWrapper getTopic(String productName,String param,String tenantId){
         return  WebSocketConstant.getTopic(this.getId(),productName, param, tenantId);
         return  WebSocketConstant.getTopic(this.getId(),productName, param, tenantId);
@@ -69,15 +83,43 @@ public abstract class Subscribe implements WsHandler {
         }
         }
 
 
         MessagingRequest.Type type = message.getType();
         MessagingRequest.Type type = message.getType();
-        if(MessagingRequest.Type.sub==type){
+        if(MessagingRequest.Type.sub.equals(type)){
             //订阅主题
             //订阅主题
             subScribeTopic.forEach(topicWrapper->this.subscribe(channelContext,topicWrapper));
             subScribeTopic.forEach(topicWrapper->this.subscribe(channelContext,topicWrapper));
-        }else {
+        }else if(MessagingRequest.Type.unsub.equals(type)){
             //取消订阅主题
             //取消订阅主题
             subScribeTopic.forEach(topicWrapper->this.unsubscribe(channelContext,topicWrapper.getTopic()));
             subScribeTopic.forEach(topicWrapper->this.unsubscribe(channelContext,topicWrapper.getTopic()));
+        }else {
+            subScribeTopic.forEach(topicWrapper->this.publish(channelContext,message,topicWrapper.getTopic(),topicWrapper.getParam()));
         }
         }
         if(log.isDebugEnabled()){
         if(log.isDebugEnabled()){
-            log.debug("订阅成功{}",subScribeTopic.stream().map(TopicWrapper::getTopic).collect(Collectors.toList()));
+            log.debug("ws数据处理成功{}", JSONUtil.toJsonStr(message));
+        }
+    }
+
+    /**
+     * 描述: 推送主题
+     * @author lifang
+     * @date 2022/8/16 14:04
+     * @param channelContext 发布推送消息的通道
+     * @param message
+     * @param topic
+     * @param param
+     * @return void
+     */
+    private void publish(ChannelContext channelContext, MessagingRequest message, String topic, String param) {
+        if(CollectionUtil.isNotEmpty(msgFilters)){
+            boolean result = msgFilters.stream()
+                    .allMatch(filter -> filter.doFilter(channelContext, message));
+            if(result){
+                SpringUtil.publishEvent(new PubMsgEvent(this,message));
+                pubTopicMap.computeIfAbsent(topic, k->redissonClient.getTopic(k))
+                        .publishAsync( TopicMessage.of(message.getPayload(),param,message.getMsgId()))
+                        .whenComplete((r,e)->{
+                            Tio.send(channelContext, WsResponse.fromText(JSONUtil.toJsonStr(PubResponse.success(message.getMsgId())), WsPacket.CHARSET_NAME));
+                        });
+
+            }
         }
         }
     }
     }
 
 
@@ -89,7 +131,7 @@ public abstract class Subscribe implements WsHandler {
     public void subscribe(ChannelContext channelContext, TopicWrapper topicWrapper){
     public void subscribe(ChannelContext channelContext, TopicWrapper topicWrapper){
         getChannelTopic(channelContext).add(topicWrapper.getTopic());
         getChannelTopic(channelContext).add(topicWrapper.getTopic());
         //同一主题只订阅一次
         //同一主题只订阅一次
-        RPatternTopic rTopic = topicMap.computeIfAbsent(topicWrapper.getTopic(),topic->redissonClient.getPatternTopic(topicWrapper.getTopic()));
+        RPatternTopic rTopic = subTopicMap.computeIfAbsent(topicWrapper.getTopic(), topic->redissonClient.getPatternTopic(topicWrapper.getTopic()));
         addTopicListener(rTopic,channelContext, topicWrapper.getTopic());
         addTopicListener(rTopic,channelContext, topicWrapper.getTopic());
     };
     };
 
 
@@ -110,7 +152,7 @@ public abstract class Subscribe implements WsHandler {
 
 
         //取消订阅
         //取消订阅
         for (String subTopic : allTopics) {
         for (String subTopic : allTopics) {
-            topicMap.computeIfPresent(subTopic,(k,rTopic)->{
+            subTopicMap.computeIfPresent(subTopic,(k, rTopic)->{
                 rTopic.removeListener( getTopicListener(channelContext,k));
                 rTopic.removeListener( getTopicListener(channelContext,k));
                 return rTopic;
                 return rTopic;
             });
             });

+ 12 - 0
nb-im/pom.xml

@@ -16,6 +16,18 @@
             <groupId>com.tuoren</groupId>
             <groupId>com.tuoren</groupId>
             <artifactId>ws-common</artifactId>
             <artifactId>ws-common</artifactId>
         </dependency>
         </dependency>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>crud-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>app-msg-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
     </dependencies>
     </dependencies>
 
 
 </project>
 </project>

+ 31 - 0
nb-im/src/main/java/com/nb/im/controller/ChatRooMsgController.java

@@ -0,0 +1,31 @@
+package com.nb.im.controller;
+
+import com.baomidou.mybatisplus.core.mapper.Mapper;
+import com.nb.common.crud.BaseService;
+import com.nb.common.crud.controller.BaseCrudController;
+import com.nb.im.entity.ChatRoomMsgEntity;
+import com.nb.im.service.LocalChatRoomMsgService;
+import io.swagger.annotations.Api;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ChatRoomController.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:42:00
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/im/msg")
+@Api(tags = "聊天室消息")
+public class ChatRooMsgController extends BaseCrudController<ChatRoomMsgEntity, String> {
+    private LocalChatRoomMsgService chatRoomMsgService;
+
+    @Override
+    public BaseService<? extends Mapper<ChatRoomMsgEntity>, ChatRoomMsgEntity, String> getService() {
+        return chatRoomMsgService;
+    }
+}

+ 34 - 0
nb-im/src/main/java/com/nb/im/controller/ChatRoomController.java

@@ -0,0 +1,34 @@
+package com.nb.im.controller;
+
+import com.baomidou.mybatisplus.core.mapper.Mapper;
+import com.nb.common.crud.BaseService;
+import com.nb.common.crud.controller.BaseCrudController;
+import com.nb.core.result.R;
+import com.nb.im.entity.ChatRoomEntity;
+import com.nb.im.service.LocalChatRoomService;
+import io.swagger.annotations.Api;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ChatRoomController.java
+ * @Description
+ * @createTime 2022年08月16日 09:42:00
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/im/room")
+@Api(tags = "聊天室")
+public class ChatRoomController extends BaseCrudController<ChatRoomEntity, String> {
+    private LocalChatRoomService chatRoomService;
+
+    @Override
+    public BaseService<? extends Mapper<ChatRoomEntity>, ChatRoomEntity, String> getService() {
+        return chatRoomService;
+    }
+
+
+}

+ 54 - 0
nb-im/src/main/java/com/nb/im/entity/ChatRoomEntity.java

@@ -0,0 +1,54 @@
+package com.nb.im.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.nb.core.entity.GenericEntity;
+import com.nb.im.enums.ImStatusEnum;
+import com.nb.im.enums.SponsorEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ChatRoomEntity.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:22:00
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName(value = "chat_room",autoResultMap = true)
+@ApiModel(value="聊天室")
+@ToString
+@NoArgsConstructor
+public class ChatRoomEntity extends GenericEntity<String> {
+    @ApiModelProperty(value = "病人id",required = true)
+    @NotNull(message = "病人id不能为空",groups = Insert.class)
+    private String patientId;
+
+    @ApiModelProperty(value = "看护人id",required = true)
+    @NotNull(message = "看护人id不能为空",groups = Insert.class)
+    private String assistId;
+
+    @ApiModelProperty(value = "医生id",required = true)
+    @NotNull(message = "医生id不能为空",groups = Insert.class)
+    private String doctorId;
+
+    @ApiModelProperty(value = "发起人类型",required = true,allowableValues = "0(看护人发起) 1(医生发起)")
+    @NotNull(message = "发起类型不能为空",groups = Insert.class)
+    private SponsorEnum sponsorType;
+
+    @ApiModelProperty(value = "聊天室总消息数",readOnly = true)
+    private Integer totalCount;
+
+    @ApiModelProperty("聊天室状态")
+    private ImStatusEnum status;
+
+    @ApiModelProperty("聊天室最后一条消息id")
+    private String lastMsgId;
+}

+ 51 - 0
nb-im/src/main/java/com/nb/im/entity/ChatRoomMsgEntity.java

@@ -0,0 +1,51 @@
+package com.nb.im.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.nb.core.entity.GenericEntity;
+import com.nb.im.enums.ImMsgType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ChatRoomEntity.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:02:00
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName(value = "chat_room_msg",autoResultMap = true)
+@ApiModel(value="聊天室消息")
+@ToString
+@NoArgsConstructor
+public class ChatRoomMsgEntity extends GenericEntity<String> {
+
+    @ApiModelProperty(value = "聊天室id",required = true)
+    private String chatRoomId;
+
+    @ApiModelProperty(value = "消息内容",required = true)
+    private String payload;
+
+    @ApiModelProperty(value = "由客户端生成的消息id,在同一聊天室中消息id唯一",required = true)
+    private String msgId;
+
+    @ApiModelProperty(value = "消息类型",required = true)
+    private ImMsgType msgType;
+
+    @ApiModelProperty(value = "发送人id",readOnly = true)
+    private String senderId;
+
+    @ApiModelProperty(value = "发送人头像",readOnly = true)
+    private String senderAvatar;
+
+    @ApiModelProperty("发送人昵称")
+    private String senderNickname;
+
+    @ApiModelProperty("扩展字段,例:当消息类型为一键呼叫时,存放 boolean类型标识是否已处理;当消息类型为问题咨询详情时,存放 咨询问题id")
+    private Object extend;
+}

+ 54 - 0
nb-im/src/main/java/com/nb/im/entity/ChatRoomUserEntity.java

@@ -0,0 +1,54 @@
+package com.nb.im.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.nb.core.entity.GenericEntity;
+import com.nb.im.enums.SponsorEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+import java.util.Date;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ChatRoomEntity.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:02:00
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@TableName(value = "chat_room_user",autoResultMap = true)
+@ApiModel(value="聊天室参与人")
+@ToString
+@NoArgsConstructor
+public class ChatRoomUserEntity extends GenericEntity<String> {
+
+    @ApiModelProperty(value = "聊天室id",required = true)
+    private String chatRoomId;
+
+    @ApiModelProperty(value = "是否置顶,默认 否")
+    private Boolean top;
+
+    @ApiModelProperty(value = "参与人id",required = true)
+    private String participantsId;
+
+    @ApiModelProperty(value = "参与人类型",required = true,allowableValues = "0(看护人发起) 1(医生发起)")
+    private SponsorEnum participantsType;
+
+    @ApiModelProperty(value = "该参与人在此聊天室中的发送消息个数",readOnly = true)
+    private Integer sendCount;
+
+    @ApiModelProperty(value = "该参与人在此聊天室中的已读消息个数",readOnly = true)
+    private Integer readCount;
+
+    @ApiModelProperty("最后一次发送消息时间")
+    private Date lastSendTime;
+
+    @ApiModelProperty("最后一次读取消息时间")
+    private Date lastReadTime;
+
+}

+ 30 - 0
nb-im/src/main/java/com/nb/im/enums/ImMsgType.java

@@ -0,0 +1,30 @@
+package com.nb.im.enums;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ImMsgType.java
+ * @Description TODO
+ * @createTime 2022年08月16日 10:47:00
+ */
+@Getter
+@AllArgsConstructor
+@JsonFormat(shape = JsonFormat.Shape.OBJECT)
+public enum ImMsgType implements IEnum<Integer> {
+
+    txt(0,"文本"),
+    image(1,"图片"),
+    voice(2,"声音"),
+    vedio(3,"视频"),
+    hyperlinks(4,"超链接")
+    ;
+
+    private Integer value;
+    private String text;
+
+}

+ 26 - 0
nb-im/src/main/java/com/nb/im/enums/ImStatusEnum.java

@@ -0,0 +1,26 @@
+package com.nb.im.enums;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ImStatusEnum.java
+ * @Description 即时通信聊天室状态
+ * @createTime 2022年08月16日 09:49:00
+ */
+@Getter
+@AllArgsConstructor
+@JsonFormat(shape = JsonFormat.Shape.OBJECT)
+public enum  ImStatusEnum  implements IEnum<Integer> {
+    WAITING(0,"等待医生确认"),
+    SUCCESS(1,"成功建立聊天室"),
+    DOCTOR_LIFT(2,"医生关闭聊天室");
+
+    private Integer value;
+    private String text;
+
+}

+ 24 - 0
nb-im/src/main/java/com/nb/im/enums/SponsorEnum.java

@@ -0,0 +1,24 @@
+package com.nb.im.enums;
+
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName SponsorEnum.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:47:00
+ */
+@Getter
+@AllArgsConstructor
+@JsonFormat(shape = JsonFormat.Shape.OBJECT)
+public enum  SponsorEnum implements IEnum<Integer> {
+    assist(0,"看护人发起聊天"),
+    doctor(1,"医生发起聊天");
+
+    private Integer value;
+    private String text;
+}

+ 118 - 0
nb-im/src/main/java/com/nb/im/listener/ConsultMsgListener.java

@@ -0,0 +1,118 @@
+package com.nb.im.listener;
+
+import com.nb.app.msg.bean.MsgBean;
+import com.nb.app.msg.enums.MsgEnum;
+import com.nb.app.msg.event.SaveMsgEvent;
+import com.nb.im.entity.ChatRoomEntity;
+import com.nb.im.entity.ChatRoomMsgEntity;
+import com.nb.im.entity.ChatRoomUserEntity;
+import com.nb.im.enums.ImStatusEnum;
+import com.nb.im.enums.ImMsgType;
+import com.nb.im.enums.SponsorEnum;
+import com.nb.im.service.LocalChatRoomMsgService;
+import com.nb.im.service.LocalChatRoomService;
+import com.nb.im.service.LocalChatRoomUserService;
+import lombok.AllArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ConsultMsgListener.java
+ * @Description TODO
+ * @createTime 2022年08月16日 10:29:00
+ */
+@Configuration
+@AllArgsConstructor
+public class ConsultMsgListener {
+
+    private final LocalChatRoomMsgService chatRoomMsgService;
+    private final LocalChatRoomUserService roomUserService;
+    private final LocalChatRoomService chatRoomService;
+    @EventListener
+    @Async
+    @Transactional(rollbackFor = Exception.class)
+    public void consultMsg(SaveMsgEvent source){
+        MsgBean payload = source.getPayload();
+        if(payload==null){
+            return;
+        }
+        MsgEnum msgType = payload.getMsgType();
+        if (!MsgEnum.CONSUL.equals(msgType)&&!MsgEnum.PAIN_CALL.equals(msgType)) {
+            return;
+        }
+        ChatRoomEntity room = createRoom(payload);
+        createRoomUser(payload, room);
+        createRoomMsg(payload, room);
+    }
+
+    /**
+     * 描述: 由看护人发起的咨询或一件疼痛呼叫
+     * @author lifang
+     * @date 2022/8/16 10:38
+     * @param source
+     * @return ChatRoomEntity
+     */
+    private ChatRoomEntity createRoom(MsgBean source){
+        ChatRoomEntity result = new ChatRoomEntity();
+        result.setAssistId(source.getSenderId());
+        result.setPatientId(source.getPatientId());
+        result.setDoctorId(source.getReceiverId());
+        result.setSponsorType(SponsorEnum.assist);
+        result.setTotalCount(1);
+        //等待医生确认
+        result.setStatus(ImStatusEnum.WAITING);
+        chatRoomService.save(result);
+        return result;
+    }
+
+
+    private ChatRoomMsgEntity createRoomMsg(MsgBean msg,ChatRoomEntity source){
+        ChatRoomMsgEntity result = new ChatRoomMsgEntity();
+        result.setChatRoomId(source.getId());
+        result.setPayload(msg.getPayload());
+        result.setMsgType(ImMsgType.hyperlinks);
+        result.setSenderId(msg.getSenderId());
+        result.setSenderAvatar(msg.getSenderAvatar());
+        result.setSenderNickname(msg.getSenderNickname());
+        if (MsgEnum.CONSUL.equals(msg.getMsgType())) {
+            result.setExtend(msg.getExtend());
+        }else if(MsgEnum.PAIN_CALL.equals(msg.getMsgType())){
+            result.setExtend(false);
+        }
+        chatRoomMsgService.save(result);
+        return result;
+    }
+
+
+
+
+    /**
+     * 描述: 创建聊天室人员关联信息
+     * @author lifang
+     * @date 2022/8/16 10:40
+     * @param source 聊天室
+     * @param  msg 消息
+     * @return ChatRoomUserEntity
+     */
+    private ChatRoomUserEntity createRoomUser(MsgBean msg,ChatRoomEntity source){
+        ChatRoomUserEntity result = new ChatRoomUserEntity();
+        result.setChatRoomId(source.getId());
+        result.setTop(false);
+        result.setParticipantsId(msg.getReceiverId());
+        result.setParticipantsType(SponsorEnum.assist);
+        result.setSendCount(0);
+        result.setReadCount(0);
+        result.setLastSendTime(new Date());
+        roomUserService.save(result);
+        return result;
+    }
+
+
+
+}

+ 67 - 0
nb-im/src/main/java/com/nb/im/listener/PubMsgListener.java

@@ -0,0 +1,67 @@
+package com.nb.im.listener;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.nb.common.websocket.MessagingRequest;
+import com.nb.common.websocket.event.PubMsgEvent;
+import com.nb.im.entity.ChatRoomEntity;
+import com.nb.im.entity.ChatRoomMsgEntity;
+import com.nb.im.entity.ChatRoomUserEntity;
+import com.nb.im.service.LocalChatRoomMsgService;
+import com.nb.im.service.LocalChatRoomService;
+import com.nb.im.service.LocalChatRoomUserService;
+import com.nb.im.ws.PubMsgInfo;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.Optional;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName PubMsgListener.java
+ * @Description TODO
+ * @createTime 2022年08月16日 14:49:00
+ */
+@Configuration
+@AllArgsConstructor
+@Slf4j
+public class PubMsgListener {
+
+    private final LocalChatRoomMsgService chatRoomMsgService;
+    private final LocalChatRoomUserService roomUserService;
+    private final LocalChatRoomService roomService;
+
+    @EventListener
+    @Async
+    @Transactional(rollbackFor = Exception.class)
+    public void pubMsg(PubMsgEvent source){
+        MessagingRequest msg = source.getMsg();
+        Object payload = msg.getPayload();
+        PubMsgInfo pubMsgInfo =null;
+
+        pubMsgInfo = JSONUtil.toBean(JSONUtil.toJsonStr(payload), PubMsgInfo.class);
+        ChatRoomMsgEntity roomMsg = BeanUtil.toBean(pubMsgInfo, ChatRoomMsgEntity.class);
+        chatRoomMsgService.save(roomMsg);
+        //更新最新发送消息记录
+        roomUserService.update(new UpdateWrapper<ChatRoomUserEntity>()
+                .lambda()
+                .eq(ChatRoomUserEntity::getChatRoomId,pubMsgInfo.getChatRoomId())
+                .eq(ChatRoomUserEntity::getParticipantsId,pubMsgInfo.getSenderId())
+                .eq(ChatRoomUserEntity::getParticipantsType,pubMsgInfo.getSenderType())
+                .set(ChatRoomUserEntity::getLastSendTime, Optional.ofNullable(roomMsg.getCreateTime()).orElse(new Date()))
+        );
+        //更新聊天室信息
+        roomService.update(new UpdateWrapper<ChatRoomEntity>()
+                .lambda()
+                .eq(ChatRoomEntity::getId,pubMsgInfo.getChatRoomId())
+                .set(ChatRoomEntity::getLastMsgId,roomMsg.getId())
+                .setSql("total_count=total_count+1"));
+    }
+}

+ 16 - 0
nb-im/src/main/java/com/nb/im/mapper/ChatRoomMapper.java

@@ -0,0 +1,16 @@
+package com.nb.im.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.nb.im.entity.ChatRoomEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ChatRoomMapper.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:27:00
+ */
+@Mapper
+public interface ChatRoomMapper extends BaseMapper<ChatRoomEntity> {
+}

+ 16 - 0
nb-im/src/main/java/com/nb/im/mapper/ChatRoomMsgMapper.java

@@ -0,0 +1,16 @@
+package com.nb.im.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.nb.im.entity.ChatRoomMsgEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ChatRoomMapper.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:27:00
+ */
+@Mapper
+public interface ChatRoomMsgMapper extends BaseMapper<ChatRoomMsgEntity> {
+}

+ 16 - 0
nb-im/src/main/java/com/nb/im/mapper/ChatRoomUserMapper.java

@@ -0,0 +1,16 @@
+package com.nb.im.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.nb.im.entity.ChatRoomUserEntity;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ChatRoomMapper.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:27:00
+ */
+@Mapper
+public interface ChatRoomUserMapper extends BaseMapper<ChatRoomUserEntity> {
+}

+ 31 - 0
nb-im/src/main/java/com/nb/im/service/LocalChatRoomMsgService.java

@@ -0,0 +1,31 @@
+package com.nb.im.service;
+
+import com.nb.common.crud.BaseService;
+import com.nb.im.entity.ChatRoomMsgEntity;
+import com.nb.im.mapper.ChatRoomMsgMapper;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName LocalChatRoomMsgService.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:31:00
+ */
+@Service
+public class LocalChatRoomMsgService extends BaseService<ChatRoomMsgMapper, ChatRoomMsgEntity,String> {
+    @Override
+    public void validateBeforeSave(ChatRoomMsgEntity entity) {
+
+    }
+
+    @Override
+    public void validateBeforeUpdate(ChatRoomMsgEntity entity) {
+
+    }
+
+    @Override
+    public void validateBeforeDelete(String id) {
+
+    }
+}

+ 40 - 0
nb-im/src/main/java/com/nb/im/service/LocalChatRoomService.java

@@ -0,0 +1,40 @@
+package com.nb.im.service;
+
+import com.nb.common.crud.BaseService;
+import com.nb.im.entity.ChatRoomEntity;
+import com.nb.im.mapper.ChatRoomMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName LocalChatRoomSerice.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:30:00
+ */
+@Service
+public class LocalChatRoomService extends BaseService<ChatRoomMapper, ChatRoomEntity,String> {
+    @Autowired
+    @Lazy
+    private LocalChatRoomUserService chatRoomUserService;
+
+    @Override
+    public void validateBeforeSave(ChatRoomEntity entity) {
+        entity.setTotalCount(0);
+    }
+
+    @Override
+    public void validateBeforeUpdate(ChatRoomEntity entity) {
+
+    }
+
+    @Override
+    public void validateBeforeDelete(String id) {
+
+    }
+
+
+
+}

+ 33 - 0
nb-im/src/main/java/com/nb/im/service/LocalChatRoomUserService.java

@@ -0,0 +1,33 @@
+package com.nb.im.service;
+
+import com.nb.common.crud.BaseService;
+import com.nb.im.entity.ChatRoomUserEntity;
+import com.nb.im.mapper.ChatRoomUserMapper;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName LocalChatRoomUserService.java
+ * @Description TODO
+ * @createTime 2022年08月16日 09:32:00
+ */
+@Service
+public class LocalChatRoomUserService extends BaseService<ChatRoomUserMapper, ChatRoomUserEntity,String> {
+    @Override
+    public void validateBeforeSave(ChatRoomUserEntity entity) {
+        entity.setTop(false);
+        entity.setSendCount(0);
+        entity.setReadCount(0);
+    }
+
+    @Override
+    public void validateBeforeUpdate(ChatRoomUserEntity entity) {
+
+    }
+
+    @Override
+    public void validateBeforeDelete(String id) {
+
+    }
+}

+ 30 - 0
nb-im/src/main/java/com/nb/im/ws/ImSubscribe.java

@@ -0,0 +1,30 @@
+package com.nb.im.ws;
+
+import com.nb.common.websocket.handler.Subscribe;
+import org.springframework.stereotype.Component;
+import org.tio.core.ChannelContext;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ImSubscribe.java
+ * @Description 即时通信主题
+ * @createTime 2022年08月16日 14:01:00
+ */
+@Component
+public class ImSubscribe extends Subscribe {
+    @Override
+    public String getId() {
+        return "im";
+    }
+
+    @Override
+    public void close(ChannelContext channelContext) {
+
+    }
+
+    @Override
+    public boolean needParam() {
+        return true;
+    }
+}

+ 107 - 0
nb-im/src/main/java/com/nb/im/ws/PubMsgInfo.java

@@ -0,0 +1,107 @@
+package com.nb.im.ws;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.nb.common.websocket.MessagingRequest;
+import com.nb.im.enums.ImMsgType;
+import com.nb.im.enums.SponsorEnum;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName PubMsgInfo.java
+ * @Description 即时通信消息
+ * @createTime 2022年08月16日 14:37:00
+ */
+@Data
+@Validated
+public class PubMsgInfo implements Serializable {
+    @ApiModelProperty("客户端生产的唯一消息id")
+    private String msgId;
+
+    @ApiModelProperty("聊天室id")
+    private String chatRoomId;
+
+    @ApiModelProperty("消息载荷")
+    private String payload;
+
+    @ApiModelProperty("发送人类型")
+    private SponsorEnum senderType;
+
+    @ApiModelProperty("发送人id")
+    private String senderId;
+
+    @ApiModelProperty("发送人昵称")
+    private String senderNickname;
+
+    @ApiModelProperty("发送人头像")
+    private String senderAvatar;
+
+    @ApiModelProperty("消息类型")
+    private ImMsgType msgType;
+
+    @ApiModelProperty("扩展字段")
+    private String extend;
+
+
+
+    public void validate(){
+        if(StrUtil.isEmpty(msgId)){
+            throw new RuntimeException("消息id不能为空");
+        }
+        if(StrUtil.isEmpty(payload)){
+            throw new RuntimeException("消息载荷不能为空");
+        }
+        if(StrUtil.isEmpty(senderId)){
+            throw new RuntimeException("发送人id不能为空");
+        }
+        if(StrUtil.isEmpty(senderNickname)){
+            throw new RuntimeException("发送人昵称不能为空");
+        }
+        if(ObjectUtil.isNull(msgType)){
+            throw new RuntimeException("信息类型不能为空");
+        }
+        if(StrUtil.isEmpty(chatRoomId)){
+            throw new RuntimeException("聊天室id不能为空");
+        }
+        if(ObjectUtil.isNull(senderType)){
+            throw new RuntimeException("发送人类型不能为空");
+        }
+    }
+
+
+    public static void main(String[] args) {
+        PubMsgInfo payload = new PubMsgInfo();
+        payload.setMsgId(UUID.randomUUID().toString());
+        payload.setChatRoomId("123");
+        payload.setPayload("你好,医生");
+        payload.setSenderType(SponsorEnum.assist);
+        payload.setSenderId("1");
+        payload.setSenderNickname("我叫白小飞");
+        payload.setMsgType(ImMsgType.txt);
+
+        MessagingRequest pub = new MessagingRequest();
+        pub.setId("im");
+        pub.setMsgId(UUID.randomUUID().toString());
+        pub.setType(MessagingRequest.Type.pub);
+        pub.setParams(Arrays.asList("123"));
+        pub.setPayload(payload);
+
+        System.out.println(JSONUtil.toJsonStr(pub));
+
+
+        MessagingRequest sub = new MessagingRequest();
+        sub.setId("im");
+        sub.setType(MessagingRequest.Type.sub);
+        sub.setParams(Arrays.asList("123"));
+        System.out.println(JSONUtil.toJsonStr(sub));
+    }
+}

+ 48 - 0
nb-im/src/main/java/com/nb/im/ws/filter/MsgFormatFilter.java

@@ -0,0 +1,48 @@
+package com.nb.im.ws.filter;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.nb.common.websocket.MessagingRequest;
+import com.nb.common.websocket.PubResponse;
+import com.nb.common.websocket.filter.PubMsgFilter;
+import com.nb.core.result.R;
+import com.nb.im.ws.PubMsgInfo;
+import org.springframework.stereotype.Component;
+import org.tio.core.ChannelContext;
+import org.tio.core.Tio;
+import org.tio.websocket.common.WsPacket;
+import org.tio.websocket.common.WsResponse;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName MsgFormatFilter.java
+ * @Description TODO
+ * @createTime 2022年08月16日 14:57:00
+ */
+@Component
+public class MsgFormatFilter implements PubMsgFilter {
+    @Override
+    public boolean doFilter(ChannelContext channelContext, MessagingRequest source) {
+        String id = source.getId();
+        if("im".equalsIgnoreCase(id)){
+            if(ObjectUtil.isEmpty(source.getPayload())){
+                Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(PubResponse.fail("","发布消息不可为空")),WsPacket.CHARSET_NAME));
+                return false;
+            }
+            PubMsgInfo pubMsgInfo = JSONUtil.toBean(JSONUtil.toJsonStr(source.getPayload()), PubMsgInfo.class);
+            try {
+                pubMsgInfo.validate();
+            }catch (RuntimeException e){
+                Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(PubResponse.fail(pubMsgInfo.getMsgId(),e.getMessage())),WsPacket.CHARSET_NAME));
+                return false;
+            }
+            //todo
+        }
+        return true;
+    }
+
+
+    //敏感词过滤
+}

+ 22 - 3
nb-service-api/app-msg-api/src/main/java/com/nb/app/msg/bean/MsgBean.java

@@ -3,6 +3,7 @@ package com.nb.app.msg.bean;
 import com.nb.app.msg.enums.MsgEnum;
 import com.nb.app.msg.enums.MsgEnum;
 import io.swagger.annotations.ApiModelProperty;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Builder;
 import lombok.Builder;
+import lombok.Getter;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 
 
@@ -14,24 +15,42 @@ import java.io.Serializable;
  * @createTime 2022年08月12日 10:06:00
  * @createTime 2022年08月12日 10:06:00
  */
  */
 @Builder
 @Builder
+@Getter
 public class MsgBean implements Serializable {
 public class MsgBean implements Serializable {
+    @ApiModelProperty("消息类型")
     private MsgEnum msgType;
     private MsgEnum msgType;
 
 
-    private String sender;
+    @ApiModelProperty("发送人id")
+    private String senderId;
 
 
-    private String senderName;
+    @ApiModelProperty("发送人昵称")
+    private String senderNickname;
 
 
+    @ApiModelProperty("发送人头像")
+    private String senderAvatar;
+
+    @ApiModelProperty("病人id")
     private String patientId;
     private String patientId;
 
 
+    @ApiModelProperty("住院号")
     private String patientCode;
     private String patientCode;
 
 
+    @ApiModelProperty("病人名称")
     private String patientName;
     private String patientName;
 
 
+    @ApiModelProperty("消息内容")
     private String payload;
     private String payload;
 
 
-    private String receiver;
+    @ApiModelProperty("接收人id")
+    private String receiverId;
 
 
+    @ApiModelProperty("接收人名称")
     private String receiverName;
     private String receiverName;
 
 
+    @ApiModelProperty("是否为系统消息")
     private boolean sys;
     private boolean sys;
+
+    @ApiModelProperty("扩展字段,例:存放咨询问题id")
+    private String extend;
+
 }
 }

+ 9 - 9
nb-service/app-assistant/src/main/java/com/nb/app/assistant/service/LocalAssistantUserBindService.java

@@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.nb.app.assistant.api.entity.AssistantUserBindEntity;
 import com.nb.app.assistant.api.entity.AssistantUserBindEntity;
 import com.nb.app.assistant.api.feign.IAssistantUserBindClient;
 import com.nb.app.assistant.api.feign.IAssistantUserBindClient;
 import com.nb.app.assistant.api.feign.result.AssistantUserResult;
 import com.nb.app.assistant.api.feign.result.AssistantUserResult;
-import com.nb.app.assistant.entity.AssistantUserEntity;
 import com.nb.app.assistant.api.enums.ApplyEnum;
 import com.nb.app.assistant.api.enums.ApplyEnum;
 import com.nb.app.assistant.api.enums.BindEnum;
 import com.nb.app.assistant.api.enums.BindEnum;
 import com.nb.app.assistant.mapper.AssistantUserBindMapper;
 import com.nb.app.assistant.mapper.AssistantUserBindMapper;
@@ -65,12 +64,13 @@ public class LocalAssistantUserBindService extends BaseService<AssistantUserBind
                             .msgType(ApplyEnum.INVITE_CODE.equals(entity.getApplyType())?
                             .msgType(ApplyEnum.INVITE_CODE.equals(entity.getApplyType())?
                                     MsgEnum.CODE_BIND_NOTIFY:MsgEnum.MANUAL_BIND_NOTIFY)
                                     MsgEnum.CODE_BIND_NOTIFY:MsgEnum.MANUAL_BIND_NOTIFY)
                             .payload(entity.getId())
                             .payload(entity.getId())
-                            .sender(entity.getAssistId())
-                            .senderName(currentUser.getNickname())
+                            .senderId(entity.getAssistId())
+                            .senderNickname(currentUser.getNickname())
+                            .senderAvatar(currentUser.getAvatar())
                             .patientId(entity.getPatientId())
                             .patientId(entity.getPatientId())
                             .patientCode(entity.getPatientCode())
                             .patientCode(entity.getPatientCode())
                             .patientName(entity.getPatientName())
                             .patientName(entity.getPatientName())
-                            .receiver(entity.getDoctorId())
+                            .receiverId(entity.getDoctorId())
                             .receiverName(entity.getDoctorName())
                             .receiverName(entity.getDoctorName())
                             .sys(false)
                             .sys(false)
                             .build()
                             .build()
@@ -94,15 +94,15 @@ public class LocalAssistantUserBindService extends BaseService<AssistantUserBind
         SpringUtil.publishEvent(new SaveMsgEvent(
         SpringUtil.publishEvent(new SaveMsgEvent(
                 this,
                 this,
                 MsgBean.builder()
                 MsgBean.builder()
-                        .msgType(ApplyEnum.INVITE_CODE.equals(userBind.getApplyType())?
-                                MsgEnum.CODE_BIND_NOTIFY:MsgEnum.MANUAL_BIND_NOTIFY)
+                        .msgType(MsgEnum.PAIN_CALL)
                         .payload(userBind.getPatientId())
                         .payload(userBind.getPatientId())
-                        .sender(userBind.getAssistId())
-                        .senderName(user.getNickname())
+                        .senderId(userBind.getAssistId())
+                        .senderNickname(user.getNickname())
+                        .senderAvatar(user.getAvatar())
                         .patientId(userBind.getPatientId())
                         .patientId(userBind.getPatientId())
                         .patientCode(userBind.getPatientCode())
                         .patientCode(userBind.getPatientCode())
                         .patientName(userBind.getPatientName())
                         .patientName(userBind.getPatientName())
-                        .receiver(userBind.getDoctorId())
+                        .receiverId(userBind.getDoctorId())
                         .receiverName(userBind.getDoctorName())
                         .receiverName(userBind.getDoctorName())
                         .sys(false)
                         .sys(false)
                         .build()
                         .build()

+ 1 - 1
nb-service/app-assistant/src/main/java/com/nb/app/assistant/service/LocalAssistantUserService.java

@@ -28,7 +28,7 @@ public class LocalAssistantUserService extends BaseService<AssistantUserMapper,
                 MsgBean.builder()
                 MsgBean.builder()
                         .msgType(MsgEnum.REGISTER)
                         .msgType(MsgEnum.REGISTER)
                         .sys(true)
                         .sys(true)
-                        .receiver(entity.getId()).build()
+                        .receiverId(entity.getId()).build()
         ));
         ));
 
 
     }
     }

+ 1 - 1
nb-service/iot-service/src/main/java/com/nb/aliyun/service/AliyunConsumerGroupService.java

@@ -47,7 +47,7 @@ public class AliyunConsumerGroupService {
     private static final String ITEMS = "items";
     private static final String ITEMS = "items";
     private static final String VALUE = "value";
     private static final String VALUE = "value";
     private static final String STATUS = "status";
     private static final String STATUS = "status";
-    private static final String CONTENT = "content";
+    private static final String CONTENT = "payload";
 
 
     @Autowired
     @Autowired
     @Lazy
     @Lazy

+ 3 - 3
nb-service/web-service/src/main/java/com/nb/web/service/bus/websocket/DefaultHisMsgHandler.java

@@ -25,8 +25,8 @@ public class DefaultHisMsgHandler implements HisMsgHandler {
     private final HospitalManagerRegister hospitalManagerRegister;
     private final HospitalManagerRegister hospitalManagerRegister;
     @Override
     @Override
     public void handle(String message, String hospitalId) {
     public void handle(String message, String hospitalId) {
-        MessagingRequest messagingRequest = JSONUtil.toBean(message, MessagingRequest.class);
-        if(messagingRequest.isFromHis()){
+//        MessagingRequest messagingRequest = JSONUtil.toBean(message, MessagingRequest.class);
+//        if(messagingRequest.isFromHis()){
             //医院响应
             //医院响应
             HisResponse hisResponse = JSONUtil.toBean(message, HisResponse.class);
             HisResponse hisResponse = JSONUtil.toBean(message, HisResponse.class);
             CompletableFuture
             CompletableFuture
@@ -35,6 +35,6 @@ public class DefaultHisMsgHandler implements HisMsgHandler {
                         log.error("医院【{}】响应处理失败,响应消息【{}】,",hospitalId,message,t);
                         log.error("医院【{}】响应处理失败,响应消息【{}】,",hospitalId,message,t);
                         return null;
                         return null;
                     });
                     });
-        }
+//        }
     }
     }
 }
 }

+ 2 - 1
nb-service/web-service/src/main/java/com/nb/web/service/log/controller/SysRunningLogController.java

@@ -3,6 +3,7 @@ package com.nb.web.service.log.controller;
 import cn.dev33.satoken.SaManager;
 import cn.dev33.satoken.SaManager;
 import cn.dev33.satoken.stp.StpLogic;
 import cn.dev33.satoken.stp.StpLogic;
 import com.baomidou.mybatisplus.core.mapper.Mapper;
 import com.baomidou.mybatisplus.core.mapper.Mapper;
+import com.nb.auth.utils.SecurityUtil;
 import com.nb.common.crud.BaseService;
 import com.nb.common.crud.BaseService;
 import com.nb.common.crud.controller.BaseQueryController;
 import com.nb.common.crud.controller.BaseQueryController;
 import com.nb.web.service.log.entity.SysRunningLog;
 import com.nb.web.service.log.entity.SysRunningLog;
@@ -39,7 +40,7 @@ public class SysRunningLogController implements BaseQueryController<SysRunningLo
 
 
     @Override
     @Override
     public StpLogic getStpLogin() {
     public StpLogic getStpLogin() {
-        return SaManager.getStpLogic("");
+        return SecurityUtil.getStpLogic();
     }
     }
 
 
 }
 }