Kaynağa Gözat

Merge branch 'dev' of lifang/NB_NetPump into master

lifang 3 yıl önce
ebeveyn
işleme
84e71245fc
100 değiştirilmiş dosya ile 2418 ekleme ve 489 silme
  1. 18 0
      nb-admin/pom.xml
  2. 3 0
      nb-admin/src/main/java/com/nb/admin/AdminApplication.java
  3. 2 2
      nb-admin/src/main/java/com/nb/admin/controller/monitor/OnlineUserController.java
  4. 19 6
      nb-admin/src/main/resources/application-dev.yml
  5. 5 4
      nb-admin/src/main/resources/application.yml
  6. 2 2
      nb-admin/src/main/resources/python/xmltodict.py
  7. 20 1
      nb-admin/src/test/java/com/nb/admin/AliyunTest.java
  8. 2 2
      nb-admin/src/test/java/com/nb/admin/BusClinicTest.java
  9. 29 2
      nb-admin/src/test/java/com/nb/admin/BusDeviceAlarmTest.java
  10. 1 1
      nb-admin/src/test/java/com/nb/admin/BusDeviceTest.java
  11. 25 6
      nb-admin/src/test/java/com/nb/admin/BusHospitalLogTest.java
  12. 3 3
      nb-admin/src/test/java/com/nb/admin/BusNetpumpTest.java
  13. 4 4
      nb-admin/src/test/java/com/nb/admin/BusPatientTest.java
  14. 23 1
      nb-admin/src/test/java/com/nb/admin/FileUploadTest.java
  15. 1 1
      nb-admin/src/test/java/com/nb/admin/HisStrategyTest.java
  16. 1 1
      nb-admin/src/test/java/com/nb/admin/SpringBootApplicationTests.java
  17. 9 0
      nb-auth/pom.xml
  18. 2 0
      nb-auth/src/main/java/com/nb/auth/bean/AuthInfo.java
  19. 3 0
      nb-auth/src/main/java/com/nb/auth/bean/LoginUser.java
  20. 2 1
      nb-auth/src/main/java/com/nb/auth/config/CustomStpInterface.java
  21. 20 9
      nb-auth/src/main/java/com/nb/auth/controller/AccountController.java
  22. 6 4
      nb-auth/src/main/java/com/nb/auth/controller/AuthController.java
  23. 12 7
      nb-auth/src/main/java/com/nb/auth/controller/vo/AccountInfoVO.java
  24. 2 0
      nb-auth/src/main/java/com/nb/auth/controller/vo/UserInfoVO.java
  25. 12 0
      nb-auth/src/main/java/com/nb/auth/delay/AuthDelayConstant.java
  26. 23 0
      nb-auth/src/main/java/com/nb/auth/delay/AuthDelayMessage.java
  27. 74 0
      nb-auth/src/main/java/com/nb/auth/delay/AuthDelayMessageHandler.java
  28. 10 1
      nb-auth/src/main/java/com/nb/auth/enums/GrantTypeEnum.java
  29. 28 0
      nb-auth/src/main/java/com/nb/auth/enums/StpTypeEnum.java
  30. 14 3
      nb-auth/src/main/java/com/nb/auth/granter/IAccountOperator.java
  31. 8 5
      nb-auth/src/main/java/com/nb/auth/granter/TokenParameter.java
  32. 77 0
      nb-auth/src/main/java/com/nb/auth/sa/DefaultSaTokenListener.java
  33. 88 1
      nb-auth/src/main/java/com/nb/auth/sa/SaConfig.java
  34. 178 169
      nb-auth/src/main/java/com/nb/auth/sa/SaTokenActionDefaultImpl.java
  35. 13 2
      nb-auth/src/main/java/com/nb/auth/sa/SaTokenConfig.java
  36. 24 7
      nb-auth/src/main/java/com/nb/auth/utils/SecurityUtil.java
  37. 7 2
      nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/CreateAndUpdateMetaObjectHandler.java
  38. 15 0
      nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/IntegerListTypeHandler.java
  39. 49 0
      nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/ListTypeHandler.java
  40. 15 0
      nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/StringListTypeHandler.java
  41. 2 1
      nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/TenantNameHandler.java
  42. 4 0
      nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/interceptor/DefaultTenantLineInnerInterceptor.java
  43. 2 2
      nb-common/config-common/src/main/java/com/nb/common/config/notice/msg/ErrorMsg.java
  44. 5 0
      nb-common/config-common/src/main/java/com/nb/common/config/utils/DictUtil.java
  45. 3 4
      nb-common/config-common/src/main/java/com/nb/common/config/utils/RedissonUtil.java
  46. 0 1
      nb-common/crud-common/src/main/java/com/nb/common/crud/BaseService.java
  47. 2 6
      nb-common/crud-common/src/main/java/com/nb/common/crud/controller/BaseCrudController.java
  48. 5 1
      nb-common/crud-common/src/main/java/com/nb/common/crud/controller/BaseCurdAuthController.java
  49. 32 26
      nb-common/delay-queue-common/src/main/java/com/nb/common/queue/delay/RedissonDelayMessageManager.java
  50. 6 0
      nb-common/delay-queue-common/src/main/java/com/nb/common/queue/delay/message/DelayMessage.java
  51. 23 7
      nb-common/log-common/src/main/java/com/nb/common/log/aop/LogAspect.java
  52. 4 2
      nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultMessageListener.java
  53. 0 20
      nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultRedisCallBack.java
  54. 13 7
      nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultWebSocketMsgHandler.java
  55. 30 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/PubResponse.java
  56. 12 1
      nb-common/ws-common/src/main/java/com/nb/common/websocket/TopicMessage.java
  57. 2 1
      nb-common/ws-common/src/main/java/com/nb/common/websocket/WebSocketConstant.java
  58. 68 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/WebSocketSessionLifeCycleManage.java
  59. 24 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/event/ConnectedEvent.java
  60. 24 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/event/DisConnectedEvent.java
  61. 23 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/event/PubMsgEvent.java
  62. 41 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/filter/DefaultPubMsgFilter.java
  63. 25 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/filter/PubMsgFilter.java
  64. 43 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/handler/DefaultCloseHandler.java
  65. 10 99
      nb-common/ws-common/src/main/java/com/nb/common/websocket/handler/Subscribe.java
  66. 1 1
      nb-common/ws-common/src/main/java/com/nb/common/websocket/handler/WsHandler.java
  67. 2 1
      nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/MessageResponse.java
  68. 14 8
      nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/MessagingRequest.java
  69. 88 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/IMsgRequestHandler.java
  70. 24 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/ISubSuccessProcessor.java
  71. 25 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/IUnSubSuccessProcessor.java
  72. 22 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/MsgRequestHandlerManager.java
  73. 43 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/SubMsgRequestHandler.java
  74. 60 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/UnSubMsgRequestHandler.java
  75. 342 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/ws/CloseRunnable.java
  76. 2 0
      nb-common/ws-common/src/main/java/com/nb/common/websocket/ws/HeartBeatConfig.java
  77. 7 3
      nb-core/pom.xml
  78. 1 1
      nb-core/src/main/java/com/nb/core/Constants.java
  79. 1 0
      nb-core/src/main/java/com/nb/core/GlobalExceptionHandler.java
  80. 44 0
      nb-core/src/main/java/com/nb/core/annotation/Phone.java
  81. 9 4
      nb-core/src/main/java/com/nb/core/cache/manager/ClusterStorageManager.java
  82. 1 0
      nb-core/src/main/java/com/nb/core/cache/manager/ConfigStorageManager.java
  83. 2 1
      nb-core/src/main/java/com/nb/core/doc/SwaggerConfig.java
  84. 4 0
      nb-core/src/main/java/com/nb/core/entity/GenericEntity.java
  85. 2 0
      nb-core/src/main/java/com/nb/core/entity/QueryParamEntity.java
  86. 2 0
      nb-core/src/main/java/com/nb/core/entity/param/Term.java
  87. 0 38
      nb-core/src/main/java/com/nb/core/enums/GrantTypeEnum.java
  88. 0 1
      nb-core/src/main/java/com/nb/core/enums/SexEnum.java
  89. 8 5
      nb-core/src/main/java/com/nb/core/enums/StatusEnum.java
  90. 5 1
      nb-core/src/main/java/com/nb/core/enums/UserPlatformEnum.java
  91. 3 0
      nb-core/src/main/java/com/nb/core/result/R.java
  92. 41 0
      nb-im/pom.xml
  93. 16 0
      nb-im/src/main/java/com/nb/im/ImAutoConfiguration.java
  94. 15 0
      nb-im/src/main/java/com/nb/im/constant/ImRoomConstant.java
  95. 182 0
      nb-im/src/main/java/com/nb/im/controller/ImRoomController.java
  96. 50 0
      nb-im/src/main/java/com/nb/im/controller/ImRoomMsgController.java
  97. 16 0
      nb-im/src/main/java/com/nb/im/controller/vo/MsgExtendVo.java
  98. 31 0
      nb-im/src/main/java/com/nb/im/controller/vo/RemarkReadVo.java
  99. 24 0
      nb-im/src/main/java/com/nb/im/controller/vo/UnreadVo.java
  100. 79 0
      nb-im/src/main/java/com/nb/im/delay/ImRoomAutoFinishDelayHandler.java

+ 18 - 0
nb-admin/pom.xml

@@ -12,6 +12,24 @@
     <artifactId>nb-admin</artifactId>
 
     <dependencies>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>nb-im</artifactId>
+            <version>${nb.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>app-msg</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>app-assistant</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>app-doctor</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>com.tuoren</groupId>

+ 3 - 0
nb-admin/src/main/java/com/nb/admin/AdminApplication.java

@@ -25,6 +25,9 @@ import org.tio.websocket.starter.EnableTioWebSocketServer;
         "com.nb.web",
         "com.nb.auth",
         "com.nb.admin",
+        "com.nb.app.doctor",
+        "com.nb.app.assistant",
+        "com.nb.app.msg",
         "springfox.documentation.schema"},
 exclude = {RedisAutoConfiguration.class,RedissonAutoConfiguration.class})
 @Import(cn.hutool.extra.spring.SpringUtil.class)

+ 2 - 2
nb-admin/src/main/java/com/nb/admin/controller/monitor/OnlineUserController.java

@@ -40,11 +40,11 @@ public class OnlineUserController {
     @SaCheckPermission("monitor:online:page")
     @Log(title = "在线用户分页查询")
     public R page(Page reqPage, String username, String userPlatform) {
-        List<String> keys = StpUtil.searchTokenValue("", -1, 0);
+        List<String> keys = SecurityUtil.getStpLogic().searchTokenValue("", -1, 0,false);
         List<LoginUser> loginUserList = Lists.newArrayList();
         for (String key : keys) {
             // token sample, Authorization:login:token:8734da0866f4440daeea3d836d5ecf8c
-            String tokenValue = CharSequenceUtil.subAfter(key, StpUtil.getTokenName() + ":" + StpUtil.TYPE + ":token:", true);
+            String tokenValue = CharSequenceUtil.subAfter(key, SecurityUtil.getStpLogic().getTokenName() + ":" + StpUtil.TYPE + ":token:", true);
             LoginUser item =   SecurityUtil.getLoginUser(tokenValue);
             if (CharSequenceUtil.isNotBlank(username) && CharSequenceUtil.isNotBlank(userPlatform)) {
                 if (CharSequenceUtil.contains(item.getUsername(), username) && item.getUserPlatform().equals(userPlatform)) {

+ 19 - 6
nb-admin/src/main/resources/application-dev.yml

@@ -107,19 +107,32 @@ request:
     repeat-interval: 600
 
 
+# 阿里云对接配置
+#aliyun:
+#  accessKey: "LTAI4FhB19MgQuviGxwA3aod"
+#  accessSecret: "cQQVkATR0yv2G9CEtfjAhEGBepPDRs"
+#  consumerGroupId: "nalavzBm4RuVJc0BUij7000100"
+#  aliyunUid: "1177450762772738"
+#  regionId: "cn-shanghai"
+#  # iotInstanceId:企业版实例请填写实例ID,公共实例请填空字符串""。
+#  iotInstanceId: ""
+#  server-subscription:
+#    enable: true  # 是否开启阿里云物联网服务端订阅
+#  product:
+#    productKey: a1ALlsBa2ZK
 # 阿里云对接配置
 aliyun:
-  accessKey: "LTAI4FhB19MgQuviGxwA3aod"
-  accessSecret: "cQQVkATR0yv2G9CEtfjAhEGBepPDRs"
-  consumerGroupId: "nalavzBm4RuVJc0BUij7000100"
-  aliyunUid: "1177450762772738"
+  accessKey: "LTAI4G7FA9ytMc76oNkJ45YJ"
+  accessSecret: "R7hOvMfiHb0PYroDqUDXAYgB9htQss"
+  consumerGroupId: "NZKDBbvhxUqtcF5VqDb2000100"
+  aliyunUid: "1238892013759131"
   regionId: "cn-shanghai"
   # iotInstanceId:企业版实例请填写实例ID,公共实例请填空字符串""。
-  iotInstanceId: ""
+  iotInstanceId: "iot-060a0bgd"
   server-subscription:
     enable: true  # 是否开启阿里云物联网服务端订阅
   product:
-    productKey: a1ALlsBa2ZK
+    productKey: he1fACg7ySx
 
 notify:
   wechat:

+ 5 - 4
nb-admin/src/main/resources/application.yml

@@ -6,7 +6,7 @@ spring:
   application:
     name: nb
   profiles:
-    active: @profiles.active@
+    active: dev
   jackson:
     time-zone: GMT+8
 
@@ -28,7 +28,7 @@ tio:
     server:
       port: 9000
       #心跳检测在业务层面自定义进行 ms 毫秒级 按照最后一次接受数据包算 30s
-      heartbeat-timeout: 30000
+      heartbeat-timeout: -1
       #是否支持集群,集群开启需要redis
     cluster:
       enabled: false
@@ -55,7 +55,8 @@ sa-token:
 
 logging:
   level:
-    com.nb: @logging.level@
+    springfox: error
+    com.nb: info
   config: classpath:logback-spring.xml
   file:
     path: ./logs
@@ -77,7 +78,7 @@ mybatis-plus:
       logic-not-delete-value: 0
       logic-delete-value: 1
   configuration:
-    log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
     default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
   type-aliases-package: com.nb.bus.entity
 

+ 2 - 2
nb-admin/src/main/resources/python/xmltodict.py

@@ -256,7 +256,7 @@ def parse(xml_input, encoding=None, expat=expat, process_namespaces=False,
     when there is only a single child of a given level of hierarchy. The
     force_list argument is a tuple of keys. If the key for a given level
     of hierarchy is in the force_list argument, that level of hierarchy
-    will have a list as a child (even if there is only one sub-element).
+    will have a contactList as a child (even if there is only one sub-element).
     The index_keys operation takes precendence over this. This is applied
     after any user-supplied postprocessor has already run.
 
@@ -286,7 +286,7 @@ def parse(xml_input, encoding=None, expat=expat, process_namespaces=False,
 
         `force_list` can also be a callable that receives `path`, `key` and
         `value`. This is helpful in cases where the logic that decides whether
-        a list should be forced is more complex.
+        a contactList should be forced is more complex.
     """
     handler = _DictSAXHandler(namespace_separator=namespace_separator,
                               **kwargs)

+ 20 - 1
nb-admin/src/test/java/com/nb/admin/AliyunTest.java

@@ -1,9 +1,13 @@
 package com.nb.admin;
 
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nb.aliyun.api.service.AliyunIotSdk;
+import com.nb.im.enums.ImMsgType;
+import com.nb.im.ws.PubMsgInfo;
 import com.nb.web.api.entity.BusDeviceEntity;
-import com.nb.web.service.bus.entity.BusInfusionHistoryEntity;
+import com.nb.web.api.entity.BusInfusionHistoryEntity;
 import com.nb.web.service.bus.listener.event.bean.DeviceInfoEvent;
 import com.nb.web.service.bus.service.LocalBusDeviceService;
 import com.nb.web.service.bus.service.LocalBusInfusionHistoryService;
@@ -15,6 +19,7 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.ApplicationContext;
 import org.springframework.test.context.junit4.SpringRunner;
 
+import java.io.IOException;
 import java.util.Date;
 import java.util.List;
 
@@ -89,4 +94,18 @@ public class AliyunTest {
         Date date = new Date(1658227636571L);
         System.out.println(infusionHistory.getLastUploadTime().equals(date));
     }
+
+    @Autowired
+    ObjectMapper objectMapper;
+    @Test
+    public void test006() throws IOException {
+        PubMsgInfo pubMsgInfo = PubMsgInfo.builder()
+                .key("123")
+                .msgType(ImMsgType.txt)
+                .build();
+        System.out.println(JSONUtil.toJsonStr(pubMsgInfo));
+        String str="{\"msgType\":\"0\",\"key\":\"123\"}";
+        PubMsgInfo pubMsgInfo1 = objectMapper.readerFor(PubMsgInfo.class).readValue(str);
+        System.out.println(pubMsgInfo1);
+    }
 }

+ 2 - 2
nb-admin/src/test/java/com/nb/admin/BusClinicTest.java

@@ -6,9 +6,9 @@ import cn.hutool.json.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.nb.web.service.bus.controller.vo.ClinicStatsVo;
-import com.nb.web.service.bus.entity.BusClinicEntity;
+import com.nb.web.api.entity.BusClinicEntity;
 import com.nb.web.service.bus.entity.BusDocEntity;
-import com.nb.web.service.bus.entity.BusInfusionHistoryEntity;
+import com.nb.web.api.entity.BusInfusionHistoryEntity;
 import com.nb.web.service.bus.service.LocalBusClinicService;
 import com.nb.web.service.bus.service.LocalBusDocService;
 import com.nb.web.service.bus.service.LocalBusInfusionHistoryService;

+ 29 - 2
nb-admin/src/test/java/com/nb/admin/BusDeviceAlarmTest.java

@@ -1,8 +1,10 @@
 package com.nb.admin;
 
+import cn.hutool.bloomfilter.bitMap.BitMap;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.nb.web.service.bus.controller.BusDeviceHistoryController;
-import com.nb.web.service.bus.entity.BusDeviceAlarmEntity;
-import com.nb.web.service.bus.entity.BusInfusionHistoryEntity;
+import com.nb.web.api.entity.BusDeviceAlarmEntity;
+import com.nb.web.api.entity.BusInfusionHistoryEntity;
 import com.nb.web.api.enums.DeviceStatusEnum;
 import com.nb.web.service.bus.mapper.BusDeviceAlarmMapper;
 import com.nb.web.service.bus.service.LocalBusDeviceAlarmService;
@@ -11,14 +13,25 @@ import com.nb.web.service.bus.service.dto.DeviceAlarmQuery;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.redisson.api.RBitSet;
+import org.redisson.api.RBloomFilter;
+import org.redisson.api.RSet;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
 import org.springframework.test.context.junit4.SpringRunner;
 
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @author zsl
@@ -41,6 +54,8 @@ public class BusDeviceAlarmTest {
     BusDeviceHistoryController controller;
     @Autowired
     LocalBusInfusionHistoryService infusionHistoryService;
+    @Autowired
+    RedissonClient redissonClient;
     @Test
     public void test001(){
         DeviceAlarmQuery query = new DeviceAlarmQuery();
@@ -98,4 +113,16 @@ public class BusDeviceAlarmTest {
         }
         service.updateBatchById(objects);
     }
+
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+    @Test
+    public void redissonClientTest(){
+        RBitSet bitSet = redissonClient.getBitSet("123");
+        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("123");
+
+    }
+
+
 }

+ 1 - 1
nb-admin/src/test/java/com/nb/admin/BusDeviceTest.java

@@ -4,7 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.date.DateUtil;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.nb.web.api.entity.BusDeviceEntity;
-import com.nb.web.service.bus.entity.BusDeviceHistoryEntity;
+import com.nb.web.api.entity.BusDeviceHistoryEntity;
 import com.nb.web.service.bus.service.LocalBusDeviceHistoryService;
 import com.nb.web.service.bus.service.LocalBusDeviceService;
 import com.nb.web.service.bus.service.dto.ClinicAnalInfusionRecord;

+ 25 - 6
nb-admin/src/test/java/com/nb/admin/BusHospitalLogTest.java

@@ -1,15 +1,23 @@
 package com.nb.admin;
 
+import cn.hutool.json.JSONUtil;
+import com.nb.common.queue.delay.message.DelayMessage;
+import com.nb.core.Value;
 import com.nb.web.service.bus.controller.BusHospitalLogController;
 import com.nb.web.api.entity.BusHospitalLogEntity;
 import com.nb.web.service.bus.service.LocalBusHospitalLogService;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.redisson.api.RBlockingQueue;
+import org.redisson.api.RDelayedQueue;
+import org.redisson.api.RedissonClient;
+import org.redisson.client.RedisClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author lifang
@@ -26,14 +34,25 @@ public class BusHospitalLogTest {
 
     @Autowired
     private BusHospitalLogController logController;
+
+    @Autowired
+    RedissonClient redissonClient;
     @Test
     public void save(){
-//        BusHospitalLogEntity logEntity = new BusHospitalLogEntity();
-//        logEntity.setIp("192.168.100.32");
-//        logEntity.setReceiveTime(new Date());
-//        logEntity.setResult("success");
-//        logEntity.setType(HospitalLogEnum.HEART);
-//        logService.save(logEntity);
+        int count=0;
+        RBlockingQueue<Object> test = redissonClient.getBlockingQueue("test");
+        RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(test);
+        test.subscribeOnElements(i->{
+//            test.poll();
+//            delayedQueue.remove(i);
+            System.out.println("第"+count+"次接收到消息:" + JSONUtil.toJsonStr(i));
+        });
+        for (int i = 0; i < 100; i++) {
+            delayedQueue.offer(new DelayMessage(Value.simple(i),"test",null),10, TimeUnit.SECONDS);
+        }
+        while (true){
+
+        }
     }
 
     @Test

+ 3 - 3
nb-admin/src/test/java/com/nb/admin/BusNetpumpTest.java

@@ -60,7 +60,7 @@ public class BusNetpumpTest {
 
 //        BusDeviceRunningEntity runningEntity = deviceRunningService.getOne(new QueryWrapper<BusDeviceRunningEntity>().last("limit 1"));
 //        for (int i = 0; i < 2000; i++) {
-//            for (BusDeviceEntity deviceEntity : list) {
+//            for (BusDeviceEntity deviceEntity : contactList) {
 //                deviceEntity.setDeviceId(RandomUtil.randomString(5));
 //                deviceEntity.setId(null);
 //                deviceEntity.setMqttConnInfo("1");
@@ -97,8 +97,8 @@ public class BusNetpumpTest {
 //        deviceEntity1.setEnable(true);
 //        deviceService.save(deviceEntity);
 //        deviceService.save(deviceEntity1);
-//        List<BusDeviceRunningEntity> list = netPumpService.list();
-//        System.out.println(list);
+//        List<BusDeviceRunningEntity> contactList = netPumpService.contactList();
+//        System.out.println(contactList);
 
     }
 

+ 4 - 4
nb-admin/src/test/java/com/nb/admin/BusPatientTest.java

@@ -7,11 +7,11 @@ import cn.hutool.core.util.RandomUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.nb.web.service.bus.controller.BusDeviceManualController;
 import com.nb.web.service.bus.controller.vo.ClinicEditVo;
-import com.nb.web.service.bus.entity.BusClinicEntity;
-import com.nb.web.service.bus.entity.BusDeviceManualEntity;
-import com.nb.web.service.bus.entity.BusInfusionHistoryEntity;
+import com.nb.web.api.entity.BusClinicEntity;
+import com.nb.web.api.entity.BusDeviceManualEntity;
+import com.nb.web.api.entity.BusInfusionHistoryEntity;
 import com.nb.web.service.bus.entity.BusPatientEntity;
-import com.nb.web.service.bus.enums.DeviceManualEnum;
+import com.nb.web.api.enums.DeviceManualEnum;
 import com.nb.web.service.bus.hospital.his.strategy.all.EqualsStrategyHandler;
 import com.nb.web.service.bus.service.LocalBusClinicService;
 import com.nb.web.service.bus.service.LocalBusInfusionHistoryService;

+ 23 - 1
nb-admin/src/test/java/com/nb/admin/FileUploadTest.java

@@ -4,10 +4,18 @@ import cn.hutool.core.io.FileUtil;
 import com.nb.oss.strategy.MinioUtil;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.redisson.api.RMap;
+import org.redisson.api.RMapCache;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+import java.util.concurrent.TimeUnit;
+
 /**
  * @Author longsanlang
  * @Date 2022-04-06 17:55:44
@@ -17,12 +25,26 @@ import org.springframework.test.context.junit4.SpringRunner;
 @RunWith(SpringRunner.class)
 @SpringBootTest(classes = AdminApplication.class)
 public class FileUploadTest {
-    @Autowired
+    @Autowired(required = false)
     MinioUtil minioUtil;
 
+    @Autowired
+    RedissonClient redissonClient;
+
     @Test
     public void upload(){
         String s = minioUtil.uploadObject(FileUtil.getInputStream(FileUtil.file("C:\\Users\\JR\\Desktop\\新疆患者数据.xls")), "/docker-compose.yml");
         System.out.println(s);
     }
+
+    @Test
+    public void test() throws InterruptedException {
+        RMapCache<String, String> mapCache = redissonClient.getMapCache("localhost:123");
+        mapCache.put("1","1",3,TimeUnit.SECONDS);
+        System.out.println(mapCache.get("1"));
+        System.out.println(mapCache.getWithTTLOnly("1"));
+        Thread.sleep(4000);
+        System.out.println(mapCache.get("1"));
+        System.out.println(mapCache.getWithTTLOnly("1"));
+    }
 }

+ 1 - 1
nb-admin/src/test/java/com/nb/admin/HisStrategyTest.java

@@ -5,7 +5,7 @@ import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.EnumUtil;
 import cn.hutool.core.util.RandomUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.nb.web.service.bus.entity.BusClinicEntity;
+import com.nb.web.api.entity.BusClinicEntity;
 import com.nb.web.service.bus.hospital.his.strategy.HisStrategyEnum;
 import com.nb.web.service.bus.hospital.his.strategy.HisStrategyHandler;
 import com.nb.web.service.bus.hospital.his.strategy.HisStrategyManager;

+ 1 - 1
nb-admin/src/test/java/com/nb/admin/SpringBootApplicationTests.java

@@ -32,7 +32,7 @@ public class SpringBootApplicationTests {
     HospitalManagerRegister hospitalManagerRegister;
     @Test
     public void autoUndo(){
-        String json="{\"msgId\":1551842476831064066,\"body\":{\"nativeValue\":{\"uploadTime\":1658823052919,\"deviceId\":\"43165411383902B1\",\"infusionId\":\"1551842476755566594\",\"tenantId\":\"1544525896866643970\",\"patientCode\":\"1000000000000\",\"timestamp\":1658823052972}},\"handlerId\":\"no_signal\",\"properties\":{\"expire\":30,\"timeUnit\":\"MINUTES\"}}";
+        String json="{\"key\":1551842476831064066,\"body\":{\"nativeValue\":{\"uploadTime\":1658823052919,\"deviceId\":\"43165411383902B1\",\"infusionId\":\"1551842476755566594\",\"tenantId\":\"1544525896866643970\",\"patientCode\":\"1000000000000\",\"timestamp\":1658823052972}},\"handlerId\":\"no_signal\",\"properties\":{\"expire\":30,\"timeUnit\":\"MINUTES\"}}";
         DelayMessage delayMessage = JSONUtil.parseObj(json).toBean(DelayMessage.class);
         String body="{\"uploadTime\":1658823052919,\"deviceId\":\"43165411383902B1\",\"infusionId\":\"1551842476755566594\",\"tenantId\":\"1544525896866643970\",\"patientCode\":\"1000000000000\",\"timestamp\":1658823052972}";
         HospitalDeviceAutoUndoConfigHandler.UndoEntity undoEntity = JSONUtil.parseObj(body).toBean(HospitalDeviceAutoUndoConfigHandler.UndoEntity.class);

+ 9 - 0
nb-auth/pom.xml

@@ -13,6 +13,10 @@
 
 
     <dependencies>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>delay-queue-common</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.tuoren</groupId>
             <artifactId>nb-core</artifactId>
@@ -37,6 +41,11 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>com.github.xiaoymin</groupId>
             <artifactId>knife4j-spring-boot-starter</artifactId>

+ 2 - 0
nb-auth/src/main/java/com/nb/auth/bean/AuthInfo.java

@@ -26,4 +26,6 @@ public class AuthInfo {
     private GrantTypeEnum grantType;
     @ApiModelProperty("令牌失效时间")
     private long activityTimeout;
+    @ApiModelProperty("权限体系名称")
+    private String loginType;
 }

+ 3 - 0
nb-auth/src/main/java/com/nb/auth/bean/LoginUser.java

@@ -2,6 +2,7 @@ package com.nb.auth.bean;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.nb.auth.enums.GrantTypeEnum;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -76,6 +77,8 @@ public class LoginUser<T> implements Serializable {
      */
     private List<SysRoleInfo> roles;
 
+    @ApiModelProperty("权限体系名称")
+    private String loginType;
 
     private T tenantId;
 

+ 2 - 1
nb-auth/src/main/java/com/nb/auth/config/CustomStpInterface.java

@@ -1,6 +1,7 @@
 package com.nb.auth.config;
 
 import cn.dev33.satoken.stp.StpInterface;
+import cn.hutool.core.collection.CollectionUtil;
 import com.nb.auth.bean.SysRoleInfo;
 import com.nb.auth.utils.SecurityUtil;
 import org.springframework.stereotype.Component;
@@ -19,7 +20,7 @@ import java.util.stream.Collectors;
 public class CustomStpInterface implements StpInterface {
     @Override
     public List<String> getPermissionList(Object loginId, String loginType) {
-        return new ArrayList<>(SecurityUtil.getLoginUser().getPermissions());
+        return    CollectionUtil.newArrayList(SecurityUtil.getLoginUser().getPermissions());
     }
 
     @Override

+ 20 - 9
nb-auth/src/main/java/com/nb/auth/controller/AccountController.java

@@ -1,5 +1,6 @@
 package com.nb.auth.controller;
 
+import com.nb.auth.bean.LoginUser;
 import com.nb.auth.bean.UserPassVo;
 import com.nb.auth.granter.IAccountOperator;
 import com.nb.auth.controller.vo.AccountInfoVO;
@@ -15,7 +16,7 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
-
+import java.util.*;
 /**
  * @author lifang
  * @version 1.0.0
@@ -27,12 +28,11 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 @Api(tags = "账号信息接口")
 public class AccountController {
-    private final IAccountOperator accountOperator;
-
+    private final List<IAccountOperator> accountOperatorList;
     @ApiOperation("修改密码")
     @PostMapping(value = "/updatePass")
     public R<Boolean> updatePass(@RequestBody @Validated UserPassVo passVo) {
-        if (accountOperator.updatePass(passVo.getOldPass(),passVo.getNewPass())) {
+        if (getAccountOperator().updatePass(passVo.getOldPass(),passVo.getNewPass())) {
             return R.success();
         }else {
             throw new CustomException("修改失败,请重试");
@@ -43,35 +43,46 @@ public class AccountController {
     @ApiOperation("获取当前用户信息")
     @GetMapping("/getUserInfo")
     public R getUserInfo() {
-        return R.success(accountOperator.getUserInfo());
+        return R.success(getAccountOperator().getUserInfo());
     }
 
     @Log(title = "获取权限集合")
     @ApiOperation("获取当前用户权限集合")
     @GetMapping("/getPermCode")
     public R getPermCode() {
-        return R.success(accountOperator.getPermCode());
+        return R.success(getAccountOperator().getPermCode());
     }
 
     @Log(title = "获取菜单集合")
     @ApiOperation("获取当前用户菜单集合")
     @GetMapping("/getMenuList")
     public R getMenuList() {
-        return R.success(accountOperator.getMenuList());
+        return R.success(getAccountOperator().getMenuList());
     }
 
     @Log(title = "获取账户信息")
     @ApiOperation("获取当前用户账户信息")
     @GetMapping("/getAccountInfo")
     public R getAccountInfo() {
-        return R.success(accountOperator.getAccountInfo());
+        return R.success(getAccountOperator().getAccountInfo());
     }
 
     @Log(title = "保存用户信息")
     @ApiOperation("保存用户信息")
     @PostMapping("/saveAccountInfo")
     public R saveAccountInfo(@Validated @RequestBody AccountInfoVO req) {
-        accountOperator.saveAccountInfo(req);
+        getAccountOperator().saveAccountInfo(req);
         return R.success();
     }
+
+    private IAccountOperator getAccountOperator(){
+        LoginUser loginUser = SecurityUtil.getLoginUser();
+        Optional<IAccountOperator> accountOperator = accountOperatorList.stream()
+                .filter(operator -> operator.matchGrantType(loginUser.getGrantType()))
+                .findFirst();
+        if(accountOperator.isPresent()){
+            return accountOperator.get();
+        }
+        throw new CustomException(String.format("登录方式【%s】不存在",loginUser.getGrantType().getDesc()));
+    }
 }

+ 6 - 4
nb-auth/src/main/java/com/nb/auth/controller/AuthController.java

@@ -4,6 +4,7 @@ import com.nb.auth.bean.AuthInfo;
 import com.nb.auth.bean.LoginUser;
 import com.nb.auth.granter.IAuthGranter;
 import com.nb.auth.granter.TokenParameter;
+import com.nb.auth.utils.SecurityUtil;
 import com.nb.core.exception.CustomException;
 import com.nb.core.result.R;
 import io.swagger.annotations.Api;
@@ -56,6 +57,7 @@ public class AuthController {
         return R.success(AuthInfo.builder()
                 .activityTimeout(activityTimeout)
                 .grantType(req.getGrantType())
+                .loginType(grant.getLoginType())
                 .token(grant.getToken())
                 .build());
     }
@@ -66,11 +68,11 @@ public class AuthController {
     @ApiOperation("刷新token时间")
     @GetMapping("/refresh")
     public R getTime() {
-        if (StpUtil.isLogin()) {
+        if (SecurityUtil.getStpLogic().isLogin()) {
             try {
-                StpUtil.checkActivityTimeout();
+                SecurityUtil.getStpLogic().checkActivityTimeout();
                 //手动续签
-                StpUtil.updateLastActivityToNow();
+                SecurityUtil.getStpLogic().updateLastActivityToNow();
             }catch (Exception e){
 
             }
@@ -82,7 +84,7 @@ public class AuthController {
     @ApiOperation("用户登出")
     @PostMapping("/logout")
     public R logout() {
-        StpUtil.logout();
+        SecurityUtil.getStpLogic().logout();
         return R.success();
     }
 }

+ 12 - 7
nb-auth/src/main/java/com/nb/auth/controller/vo/AccountInfoVO.java

@@ -1,10 +1,11 @@
 package com.nb.auth.controller.vo;
 
+import com.nb.core.enums.SexEnum;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.Size;
 import java.io.Serializable;
+import java.util.Map;
 
 /**
  * 账户信息VO
@@ -12,22 +13,23 @@ import java.io.Serializable;
  * @author Kevin
  */
 @Data
-public class AccountInfoVO implements Serializable {
+public class AccountInfoVO<T> implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
     /**
      * 昵称
      */
-    @NotBlank(message = "昵称不能为空")
+    @ApiModelProperty(value = "昵称",required = false)
     @Size(max = 100, message = "昵称长度不能超过100个字符")
     private String nickname;
 
     /**
      * 姓名
      */
+    @ApiModelProperty(value = "姓名",required = false)
     @Size(max = 100, message = "姓名长度不能超过100个字符")
-    private String realname;
+    private String realName;
 
     /**
      * 英文名
@@ -50,8 +52,8 @@ public class AccountInfoVO implements Serializable {
     /**
      * 性别 1男;2女;3未知
      */
-    @NotBlank(message = "性别不能为空")
-    private String sex;
+    @ApiModelProperty(value = "性别",required = false,allowableValues = "1(男)2(女)3(未知)")
+    private SexEnum sex;
 
     /**
      * 备注
@@ -59,4 +61,7 @@ public class AccountInfoVO implements Serializable {
     @Size(max = 500, message = "备注长度不能超过500个字符")
     private String remarks;
 
+
+    @ApiModelProperty("扩展字段")
+    private Map<String,Object> extendFields;
 }

+ 2 - 0
nb-auth/src/main/java/com/nb/auth/controller/vo/UserInfoVO.java

@@ -21,6 +21,8 @@ public class UserInfoVO implements Serializable {
 
     private String realName;
 
+    private String nickname;
+
     private String avatar;
 
     private String desc;

+ 12 - 0
nb-auth/src/main/java/com/nb/auth/delay/AuthDelayConstant.java

@@ -0,0 +1,12 @@
+package com.nb.auth.delay;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName AuthDelayConstant.java
+ * @Description TODO
+ * @createTime 2022年09月16日 13:46:00
+ */
+public class AuthDelayConstant {
+    public static final String AUTH_HANDLER="auth_login";
+}

+ 23 - 0
nb-auth/src/main/java/com/nb/auth/delay/AuthDelayMessage.java

@@ -0,0 +1,23 @@
+package com.nb.auth.delay;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName AuthDelayMessage.java
+ * @Description TODO
+ * @createTime 2022年09月16日 13:40:00
+ */
+@Data
+@AllArgsConstructor(staticName = "of")
+public class AuthDelayMessage implements Serializable {
+    private String loginType;
+    private String tokenValue;
+    private long activityTimeout;
+    private TimeUnit unit;
+}

+ 74 - 0
nb-auth/src/main/java/com/nb/auth/delay/AuthDelayMessageHandler.java

@@ -0,0 +1,74 @@
+package com.nb.auth.delay;
+
+import cn.dev33.satoken.dao.SaTokenDao;
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.SaTokenException;
+import cn.dev33.satoken.stp.StpLogic;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.nb.auth.utils.SecurityUtil;
+import com.nb.common.queue.delay.handler.DelayMessageHandler;
+import com.nb.common.queue.delay.manager.DelayMessageManager;
+import com.nb.common.queue.delay.message.DelayMessage;
+import com.nb.common.queue.delay.message.DelayMessageProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName AuthDelayHandler.java
+ * @Description TODO
+ * @createTime 2022年09月16日 13:46:00
+ */
+@Component
+@Slf4j
+public class AuthDelayMessageHandler implements DelayMessageHandler {
+    @Autowired
+    @Lazy
+    private DelayMessageManager delayMessageManager;
+    @Override
+    public String getId() {
+        return AuthDelayConstant.AUTH_HANDLER;
+    }
+
+    @Override
+    public String description() {
+        return "登录成功后处理,清除冗余token,减少冗余资源";
+    }
+
+    @Override
+    public void handle(DelayMessage message) {
+        AuthDelayMessage body = message.getBody().as(AuthDelayMessage.class);
+        if(body==null){
+            return;
+        }
+        if(StrUtil.isEmpty(body.getTokenValue())){
+            return;
+        }
+        String tokenValue = body.getTokenValue();
+        StpLogic stpLogic =null;
+        try {
+            stpLogic = SecurityUtil.getStpLogic(body.getLoginType());
+            long timeout = stpLogic.getTokenActivityTimeoutByToken(tokenValue);
+            if(timeout == SaTokenDao.NOT_VALUE_EXPIRE) {
+                throw NotLoginException.newInstance(body.getLoginType(), NotLoginException.TOKEN_TIMEOUT, tokenValue);
+            }
+            //继续判断
+            delayMessageManager.add(new DelayMessage(message, AuthDelayConstant.AUTH_HANDLER, DelayMessageProperties.of(TimeUnit.SECONDS,body.getActivityTimeout())));
+        }catch (NotLoginException e){
+            //已过期,清除
+            if(stpLogic!=null){
+                stpLogic.logoutByTokenValue(tokenValue);
+            }
+            log.info("清除冗余tokenValue:{},loginType:{}",tokenValue,body.getLoginType());
+        }catch (SaTokenException e){
+            log.error("清除冗余token数据发生错误,message:{}", JSONUtil.toJsonStr(body),e);
+        }
+
+    }
+}

+ 10 - 1
nb-auth/src/main/java/com/nb/auth/enums/GrantTypeEnum.java

@@ -27,7 +27,16 @@ public enum GrantTypeEnum{
     /**
      * appkey、appSecret模式
      */
-    APPKEY_APPSECRET("3", "第三方应用登录"),;
+    APPKEY_APPSECRET("3", "第三方应用登录"),
+
+    /**
+     * appkey、appSecret模式
+     */
+    APP_DOCTOR("4", "app医生登录"),
+
+    APP_ASSISTANT_PASSWORD("5","疼痛小管家账密登录"),
+
+    APP_ASSISTANT_ONCELICK("6","疼痛小管家一键登录");;
 
     private String code;
     private String desc;

+ 28 - 0
nb-auth/src/main/java/com/nb/auth/enums/StpTypeEnum.java

@@ -0,0 +1,28 @@
+package com.nb.auth.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 StpConstant.java
+ * @Description TODO
+ * @createTime 2022年08月09日 22:00:00
+ */
+@Getter
+@AllArgsConstructor
+@JsonFormat(shape = JsonFormat.Shape.OBJECT)
+public enum  StpTypeEnum implements IEnum<Integer> {
+    DEFAULT(1,"默认登录体系","login"),
+    APP_DOCTOR(2,"app医生端登录体系","app_doctor"),
+    ASSISTANT(3,"疼痛小助手登录体系","assistant")
+    ;
+
+    private Integer value;
+    private String desc;
+    private String text;
+}
+

+ 14 - 3
nb-auth/src/main/java/com/nb/auth/granter/IAccountOperator.java

@@ -3,6 +3,7 @@ package com.nb.auth.granter;
 import com.nb.auth.controller.vo.AccountInfoVO;
 import com.nb.auth.controller.vo.RouteItemVO;
 import com.nb.auth.controller.vo.UserInfoVO;
+import com.nb.auth.enums.GrantTypeEnum;
 
 import java.util.List;
 import java.util.Set;
@@ -14,7 +15,17 @@ import java.util.Set;
  * @Description 账户操作符
  * @createTime 2022年08月01日 14:45:00
  */
-public interface IAccountOperator {
+public interface IAccountOperator<T> {
+
+    /**
+     * 描述: 匹配登录方式
+     * @author lifang
+     * @date 2022/8/9 22:08
+     * @param type
+     * @return boolean
+     */
+    boolean matchGrantType(GrantTypeEnum type);
+
     /**
      * 描述: 获取当前用户信息
      * @author lifang
@@ -41,7 +52,7 @@ public interface IAccountOperator {
     List<RouteItemVO> getMenuList();
 
 
-    AccountInfoVO getAccountInfo();
+    AccountInfoVO<T> getAccountInfo();
 
     /**
      * 描述: 设置当前用户账户信息
@@ -50,7 +61,7 @@ public interface IAccountOperator {
      * @param req 账户信息
      * @return Set<String>
      */
-    void saveAccountInfo(AccountInfoVO req);
+    void saveAccountInfo(AccountInfoVO<T> req);
 
     boolean updatePass(String oldPass, String newPass);
 }

+ 8 - 5
nb-auth/src/main/java/com/nb/auth/granter/TokenParameter.java

@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
 
 /**
  * 登录实体类
@@ -12,22 +13,24 @@ import javax.validation.constraints.NotBlank;
  * @author Kevin
  */
 @Data
-public class TokenParameter {
+public class TokenParameter implements Serializable {
 
-    @ApiModelProperty("授权方式,1、web账密登录 2、手机短信登录 3、第三方应用登陆")
+    @ApiModelProperty(value = "授权方式 ",allowableValues = "1(web账密登录), 2 (手机短信登录),3(第三方应用登陆), 4(app医生登录), 5(疼痛小管家手机账密登录)6(疼痛小管家手机一键登录)")
     GrantTypeEnum grantType;
 
+    @ApiModelProperty("用户名 账密登录、app医生登录、疼痛小管家账密(此处用户名即为用户的手机号)登录时使用")
     String username;
 
+    @ApiModelProperty("密码  账密登录、app医生登录、疼痛小管家账密登录时使用")
     String password;
 
-    String mobile;
-
+    @ApiModelProperty("验证码,web账密登录、疼痛小管家手机验证码时使用")
     String code;
 
+    @ApiModelProperty("验证码key,web账密登录时使用")
     String codeKey;
 
-    @ApiModelProperty("appkey 第三方应用登录时使用,换取token")
+    @ApiModelProperty("第三方应用登陆时使用")
     String appKey;
 
     @ApiModelProperty("第三方应用登录时使用时的签名")

+ 77 - 0
nb-auth/src/main/java/com/nb/auth/sa/DefaultSaTokenListener.java

@@ -0,0 +1,77 @@
+package com.nb.auth.sa;
+
+import cn.dev33.satoken.config.SaTokenConfig;
+import cn.dev33.satoken.listener.SaTokenListener;
+import cn.dev33.satoken.stp.SaLoginModel;
+import com.nb.auth.delay.AuthDelayConstant;
+import com.nb.auth.delay.AuthDelayMessage;
+import com.nb.common.queue.delay.manager.DelayMessageManager;
+import com.nb.common.queue.delay.message.DelayMessage;
+import com.nb.common.queue.delay.message.DelayMessageProperties;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName DefaultSaTokenListener.java
+ * @Description TODO
+ * @createTime 2022年09月16日 13:38:00
+ */
+@Component
+@AllArgsConstructor
+@Slf4j
+public class DefaultSaTokenListener implements SaTokenListener {
+    private final DelayMessageManager delayMessageManager;
+    private final SaTokenConfig saTokenConfig;
+
+
+    @Override
+    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
+        AuthDelayMessage message = AuthDelayMessage.of(loginType, tokenValue, saTokenConfig.getActivityTimeout(), TimeUnit.SECONDS);
+        log.info("账号{}登录成功,tokenValue:{},loginType:{}",loginId,loginModel.getToken(),loginType);
+        delayMessageManager.add(new DelayMessage(message, AuthDelayConstant.AUTH_HANDLER, DelayMessageProperties.of(TimeUnit.SECONDS,saTokenConfig.getActivityTimeout())));
+    }
+
+    @Override
+    public void doLogout(String loginType, Object loginId, String tokenValue) {
+
+    }
+
+    @Override
+    public void doKickout(String loginType, Object loginId, String tokenValue) {
+
+    }
+
+    @Override
+    public void doReplaced(String loginType, Object loginId, String tokenValue) {
+
+    }
+
+    @Override
+    public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
+
+    }
+
+    @Override
+    public void doUntieDisable(String loginType, Object loginId, String service) {
+
+    }
+
+    @Override
+    public void doCreateSession(String id) {
+
+    }
+
+    @Override
+    public void doLogoutSession(String id) {
+
+    }
+
+    @Override
+    public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
+
+    }
+}

+ 88 - 1
nb-auth/src/main/java/com/nb/auth/sa/SaConfig.java

@@ -1,11 +1,20 @@
 package com.nb.auth.sa;
 
 import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.annotation.*;
+import cn.dev33.satoken.basic.SaBasicUtil;
 import cn.dev33.satoken.dao.SaTokenDaoRedisJackson;
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.dev33.satoken.stp.StpLogic;
+import cn.dev33.satoken.strategy.SaStrategy;
+import cn.hutool.core.util.StrUtil;
+import com.nb.auth.enums.StpTypeEnum;
+import com.nb.auth.utils.SecurityUtil;
 import lombok.AllArgsConstructor;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.PostConstruct;
+import java.util.regex.Pattern;
 
 /**
  * @author lifang
@@ -18,9 +27,87 @@ import javax.annotation.PostConstruct;
 @AllArgsConstructor
 public class SaConfig {
     private final SaTokenDaoRedisJackson saTokenDaoRedisJackson;
+    private final DefaultSaTokenListener defaultSaTokenListener;
     @PostConstruct
     public void init(){
-        SaManager.setSaTokenAction(new SaTokenActionDefaultImpl());
+        SaStrategy.me.hasElement=(list,element)->{
+            // 空集合直接返回false
+            if(list == null || list.size() == 0) {
+                return false;
+            }
+            // 先尝试一下简单匹配,如果可以匹配成功则无需继续模糊匹配
+            if (list.contains(element)) {
+                return true;
+            }
+            // 开始模糊匹配
+            for (String patt : list) {
+                if(vagueMatch(patt, element)) {
+                    return true;
+                }
+            }
+            // 走出for循环说明没有一个元素可以匹配成功
+            return false;
+        };
+        SaStrategy.me.checkElementAnnotation=target->{
+            // 校验 @SaCheckLogin 注解
+            SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
+            if(checkLogin != null) {
+                SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
+            }
+
+            // 校验 @SaCheckRole 注解
+            SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
+            if(checkRole != null) {
+                SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
+            }
+
+            // 校验 @SaCheckPermission 注解
+            SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
+            if(checkPermission != null) {
+                String type= StrUtil.isBlankIfStr(checkPermission.type())?"login":checkPermission.type();
+                StpLogic stpLogic = SecurityUtil.getStpLogic();
+                if (!type.equals(stpLogic.getLoginType())) {
+                    throw new NotPermissionException("无权限访问此体系接口");
+                }
+                SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
+            }
+
+            // 校验 @SaCheckSafe 注解
+            SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
+            if(checkSafe != null) {
+                SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
+            }
+
+            // 校验 @SaCheckBasic 注解
+            SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
+            if(checkBasic != null) {
+                SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
+            }
+        };
+//        SaManager.setSaTokenContext();
+//        SaManager.setSaTokenAction(new SaTokenActionDefaultImpl());
         SaManager.setSaTokenDao(saTokenDaoRedisJackson);
+//        SaManager.setSaTokenListener(defaultSaTokenListener);
+        StpTypeEnum[] values = StpTypeEnum.values();
+        for (StpTypeEnum value : values) {
+            SaManager.putStpLogic(new StpLogic(value.getText()));
+        }
+    }
+
+    /**
+     * 字符串模糊匹配
+     * <p>example:
+     * <p> user-add user*    --  true
+     * <p> art-add user*     --  false
+     * @param patt 表达式
+     * @param str 待匹配的字符串
+     * @return 是否可以匹配
+     */
+    public static boolean vagueMatch(String patt, String str) {
+        // 如果表达式不带有*号,则只需简单equals即可 (速度提升200倍)
+        if(!str.contains("*")&&!patt.contains("*")) {
+            return patt.equals(str);
+        }
+        return Pattern.matches(str.replaceAll("\\*", ".*"), patt)||Pattern.matches(patt.replaceAll("\\*", ".*"), str);
     }
 }

+ 178 - 169
nb-auth/src/main/java/com/nb/auth/sa/SaTokenActionDefaultImpl.java

@@ -1,169 +1,178 @@
-package com.nb.auth.sa;
-
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.UUID;
-import java.util.regex.Pattern;
-
-import cn.dev33.satoken.SaManager;
-import cn.dev33.satoken.action.SaTokenAction;
-import cn.dev33.satoken.annotation.SaCheckBasic;
-import cn.dev33.satoken.annotation.SaCheckLogin;
-import cn.dev33.satoken.annotation.SaCheckPermission;
-import cn.dev33.satoken.annotation.SaCheckRole;
-import cn.dev33.satoken.annotation.SaCheckSafe;
-import cn.dev33.satoken.basic.SaBasicUtil;
-import cn.dev33.satoken.session.SaSession;
-import cn.dev33.satoken.strategy.SaStrategy;
-import cn.dev33.satoken.util.SaFoxUtil;
-import cn.dev33.satoken.util.SaTokenConsts;
-
-/**
- * <h1> v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy </h1>
- * <p> Sa-Token 逻辑代理接口 [默认实现类]  </p> 
- * @author kong
- *
- */
-public class SaTokenActionDefaultImpl implements SaTokenAction {
-
-	/**
-	 * 创建一个Token 
-	 */
-	@Override
-	public String createToken(Object loginId, String loginType) {
-		// 根据配置的tokenStyle生成不同风格的token 
-		String tokenStyle = SaManager.getConfig().getTokenStyle();
-		// uuid 
-		if(SaTokenConsts.TOKEN_STYLE_UUID.equals(tokenStyle)) {
-			return UUID.randomUUID().toString();
-		}
-		// 简单uuid (不带下划线)
-		if(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID.equals(tokenStyle)) {
-			return UUID.randomUUID().toString().replaceAll("-", "");
-		}
-		// 32位随机字符串
-		if(SaTokenConsts.TOKEN_STYLE_RANDOM_32.equals(tokenStyle)) {
-			return SaFoxUtil.getRandomString(32);
-		}
-		// 64位随机字符串
-		if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) {
-			return SaFoxUtil.getRandomString(64);
-		}
-		// 128位随机字符串
-		if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) {
-			return SaFoxUtil.getRandomString(128);
-		}
-		// tik风格 (2_14_16)
-		if(SaTokenConsts.TOKEN_STYLE_TIK.equals(tokenStyle)) {
-			return SaFoxUtil.getRandomString(2) + "_" + SaFoxUtil.getRandomString(14) + "_" + SaFoxUtil.getRandomString(16) + "__";
-		}
-		// 默认,还是uuid 
-		return UUID.randomUUID().toString();
-	}
-
-	/**
-	 * 创建一个Session 
-	 */
-	@Override
-	public SaSession createSession(String sessionId) {
-		return new SaSession(sessionId);
-	}
-
-	/**
-	 * 判断:集合中是否包含指定元素(模糊匹配) 
-	 */
-	@Override
-	public boolean hasElement(List<String> list, String element) {
-		
-		// 空集合直接返回false
-		if(list == null || list.size() == 0) {
-			return false;
-		}
-
-		// 先尝试一下简单匹配,如果可以匹配成功则无需继续模糊匹配 
-		if (list.contains(element)) {
-			return true;
-		}
-		
-		// 开始模糊匹配 
-		for (String patt : list) {
-			if(vagueMatch(patt, element)) {
-				return true;
-			}
-		}
-		
-		// 走出for循环说明没有一个元素可以匹配成功 
-		return false;
-	}
-
-	/**
-	 * 对一个Method对象进行注解检查(注解鉴权内部实现) 
-	 */
-	@Override
-	public void checkMethodAnnotation(Method method) {
-
-		// 先校验 Method 所属 Class 上的注解 
-		validateAnnotation(method.getDeclaringClass());
-		
-		// 再校验 Method 上的注解  
-		validateAnnotation(method);
-	}
-
-	/**
-	 * 从指定元素校验注解 
-	 * @param target see note 
-	 */
-	@Override
-	public void validateAnnotation(AnnotatedElement target) {
-		
-		// 校验 @SaCheckLogin 注解 
-		SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
-		if(checkLogin != null) {
-			SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
-		}
-		
-		// 校验 @SaCheckRole 注解 
-		SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
-		if(checkRole != null) {
-			SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
-		}
-		
-		// 校验 @SaCheckPermission 注解
-		SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
-		if(checkPermission != null) {
-			SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
-		}
-
-		// 校验 @SaCheckSafe 注解
-		SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
-		if(checkSafe != null) {
-			SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
-		}
-		
-		// 校验 @SaCheckBasic 注解
-		SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
-		if(checkBasic != null) {
-			SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
-		}
-		
-	}
-
-
-	/**
-	 * 字符串模糊匹配
-	 * <p>example:
-	 * <p> user-add user*    --  true
-	 * <p> art-add user*     --  false
-	 * @param patt 表达式
-	 * @param str 待匹配的字符串
-	 * @return 是否可以匹配
-	 */
-	public static boolean vagueMatch(String patt, String str) {
-		// 如果表达式不带有*号,则只需简单equals即可 (速度提升200倍)
-		if(!str.contains("*")&&!patt.contains("*")) {
-			return patt.equals(str);
-		}
-		return Pattern.matches(str.replaceAll("\\*", ".*"), patt)||Pattern.matches(patt.replaceAll("\\*", ".*"), str);
-	}
-}
+//package com.nb.auth.sa;
+//
+//import java.lang.reflect.AnnotatedElement;
+//import java.lang.reflect.Method;
+//import java.util.List;
+//import java.util.UUID;
+//import java.util.regex.Pattern;
+//
+//import cn.dev33.satoken.SaManager;
+//import cn.dev33.satoken.action.SaTokenAction;
+//import cn.dev33.satoken.annotation.SaCheckBasic;
+//import cn.dev33.satoken.annotation.SaCheckLogin;
+//import cn.dev33.satoken.annotation.SaCheckPermission;
+//import cn.dev33.satoken.annotation.SaCheckRole;
+//import cn.dev33.satoken.annotation.SaCheckSafe;
+//import cn.dev33.satoken.basic.SaBasicUtil;
+//import cn.dev33.satoken.exception.NotPermissionException;
+//import cn.dev33.satoken.session.SaSession;
+//import cn.dev33.satoken.stp.StpLogic;
+//import cn.dev33.satoken.strategy.SaStrategy;
+//import cn.dev33.satoken.util.SaFoxUtil;
+//import cn.dev33.satoken.util.SaTokenConsts;
+//import cn.hutool.core.util.StrUtil;
+//import com.nb.auth.utils.SecurityUtil;
+//
+///**
+// * <h1> v1.27+ 此接口已废弃,目前版本暂时向下兼容,请及时更换为 SaStrategy </h1>
+// * <p> Sa-Token 逻辑代理接口 [默认实现类]  </p>
+// * @author kong
+// *
+// */
+//public class SaTokenActionDefaultImpl implements SaTokenAction {
+//
+//	/**
+//	 * 创建一个Token
+//	 */
+//	@Override
+//	public String createToken(Object loginId, String loginType) {
+//		// 根据配置的tokenStyle生成不同风格的token
+//		String tokenStyle = SaManager.getConfig().getTokenStyle();
+//		// uuid
+//		if(SaTokenConsts.TOKEN_STYLE_UUID.equals(tokenStyle)) {
+//			return UUID.randomUUID().toString();
+//		}
+//		// 简单uuid (不带下划线)
+//		if(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID.equals(tokenStyle)) {
+//			return UUID.randomUUID().toString().replaceAll("-", "");
+//		}
+//		// 32位随机字符串
+//		if(SaTokenConsts.TOKEN_STYLE_RANDOM_32.equals(tokenStyle)) {
+//			return SaFoxUtil.getRandomString(32);
+//		}
+//		// 64位随机字符串
+//		if(SaTokenConsts.TOKEN_STYLE_RANDOM_64.equals(tokenStyle)) {
+//			return SaFoxUtil.getRandomString(64);
+//		}
+//		// 128位随机字符串
+//		if(SaTokenConsts.TOKEN_STYLE_RANDOM_128.equals(tokenStyle)) {
+//			return SaFoxUtil.getRandomString(128);
+//		}
+//		// tik风格 (2_14_16)
+//		if(SaTokenConsts.TOKEN_STYLE_TIK.equals(tokenStyle)) {
+//			return SaFoxUtil.getRandomString(2) + "_" + SaFoxUtil.getRandomString(14) + "_" + SaFoxUtil.getRandomString(16) + "__";
+//		}
+//		// 默认,还是uuid
+//		return UUID.randomUUID().toString();
+//	}
+//
+//	/**
+//	 * 创建一个Session
+//	 */
+//	@Override
+//	public SaSession createSession(String sessionId) {
+//		return new SaSession(sessionId);
+//	}
+//
+//	/**
+//	 * 判断:集合中是否包含指定元素(模糊匹配)
+//	 */
+//	@Override
+//	public boolean hasElement(List<String> list, String element) {
+//
+//		// 空集合直接返回false
+//		if(list == null || list.size() == 0) {
+//			return false;
+//		}
+//
+//		// 先尝试一下简单匹配,如果可以匹配成功则无需继续模糊匹配
+//		if (list.contains(element)) {
+//			return true;
+//		}
+//
+//		// 开始模糊匹配
+//		for (String patt : list) {
+//			if(vagueMatch(patt, element)) {
+//				return true;
+//			}
+//		}
+//
+//		// 走出for循环说明没有一个元素可以匹配成功
+//		return false;
+//	}
+//
+//	/**
+//	 * 对一个Method对象进行注解检查(注解鉴权内部实现)
+//	 */
+//	@Override
+//	public void checkMethodAnnotation(Method method) {
+//
+//		// 先校验 Method 所属 Class 上的注解
+//		validateAnnotation(method.getDeclaringClass());
+//
+//		// 再校验 Method 上的注解
+//		validateAnnotation(method);
+//	}
+//
+//	/**
+//	 * 从指定元素校验注解
+//	 * @param target see note
+//	 */
+//	@Override
+//	public void validateAnnotation(AnnotatedElement target) {
+//
+//		// 校验 @SaCheckLogin 注解
+//		SaCheckLogin checkLogin = (SaCheckLogin) SaStrategy.me.getAnnotation.apply(target, SaCheckLogin.class);
+//		if(checkLogin != null) {
+//			SaManager.getStpLogic(checkLogin.type()).checkByAnnotation(checkLogin);
+//		}
+//
+//		// 校验 @SaCheckRole 注解
+//		SaCheckRole checkRole = (SaCheckRole) SaStrategy.me.getAnnotation.apply(target, SaCheckRole.class);
+//		if(checkRole != null) {
+//			SaManager.getStpLogic(checkRole.type()).checkByAnnotation(checkRole);
+//		}
+//
+//		// 校验 @SaCheckPermission 注解
+//		SaCheckPermission checkPermission = (SaCheckPermission) SaStrategy.me.getAnnotation.apply(target, SaCheckPermission.class);
+//		if(checkPermission != null) {
+//			String type= StrUtil.isBlankIfStr(checkPermission.type())?"login":checkPermission.type();
+//			StpLogic stpLogic = SecurityUtil.getStpLogic();
+//			if (!type.equals(stpLogic.getLoginType())) {
+//				throw new NotPermissionException("无权限访问此体系接口");
+//			}
+//			SaManager.getStpLogic(checkPermission.type()).checkByAnnotation(checkPermission);
+//		}
+//
+//		// 校验 @SaCheckSafe 注解
+//		SaCheckSafe checkSafe = (SaCheckSafe) SaStrategy.me.getAnnotation.apply(target, SaCheckSafe.class);
+//		if(checkSafe != null) {
+//			SaManager.getStpLogic(checkSafe.type()).checkByAnnotation(checkSafe);
+//		}
+//
+//		// 校验 @SaCheckBasic 注解
+//		SaCheckBasic checkBasic = (SaCheckBasic) SaStrategy.me.getAnnotation.apply(target, SaCheckBasic.class);
+//		if(checkBasic != null) {
+//			SaBasicUtil.check(checkBasic.realm(), checkBasic.account());
+//		}
+//
+//	}
+//
+//
+//	/**
+//	 * 字符串模糊匹配
+//	 * <p>example:
+//	 * <p> user-add user*    --  true
+//	 * <p> art-add user*     --  false
+//	 * @param patt 表达式
+//	 * @param str 待匹配的字符串
+//	 * @return 是否可以匹配
+//	 */
+//	public static boolean vagueMatch(String patt, String str) {
+//		// 如果表达式不带有*号,则只需简单equals即可 (速度提升200倍)
+//		if(!str.contains("*")&&!patt.contains("*")) {
+//			return patt.equals(str);
+//		}
+//		return Pattern.matches(str.replaceAll("\\*", ".*"), patt)||Pattern.matches(patt.replaceAll("\\*", ".*"), str);
+//	}
+//}

+ 13 - 2
nb-auth/src/main/java/com/nb/auth/sa/SaTokenConfig.java

@@ -1,13 +1,16 @@
 package com.nb.auth.sa;
 
+import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
 import cn.dev33.satoken.interceptor.SaRouteInterceptor;
 import cn.dev33.satoken.router.SaRouter;
 import cn.dev33.satoken.stp.StpUtil;
+import com.nb.auth.utils.SecurityUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.annotation.Order;
 import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -20,16 +23,22 @@ import java.util.List;
 @Slf4j
 @Configuration
 public class SaTokenConfig {
-
     @Bean
     @Order(1)
     public HandlerInterceptor handlerInterceptor(){
         return new SaRouteInterceptor((req, res, handler) -> {
             SaRouter.match("/**")
                     .notMatch(IGNORE_URL)
-                    .check(StpUtil::checkLogin);
+                    .check(__-> SecurityUtil.getStpLogic().checkLogin());
+//                    .check(StpUtil::checkLogin);
         });
     }
+
+    @Bean
+    @Order(1)
+    public HandlerInterceptor annotationInterceptor(){
+        return new SaAnnotationInterceptor();
+    }
     private static final List<String> IGNORE_URL = new ArrayList<>();
 
     static {
@@ -52,10 +61,12 @@ public class SaTokenConfig {
         IGNORE_URL.add("/v2/api-docs/*");
         IGNORE_URL.add("/v2/api-docs");
         IGNORE_URL.add("/v1/**");
+        IGNORE_URL.add("/assist/captcha/**");
         IGNORE_URL.add("/authority/captcha/**");
         IGNORE_URL.add("/system/curl/**");
         IGNORE_URL.add("/system/sysConfig/getTime");
         IGNORE_URL.add("/system/sysDept/**");
+        IGNORE_URL.add("/assist/phone/**");
     }
 
 }

+ 24 - 7
nb-auth/src/main/java/com/nb/auth/utils/SecurityUtil.java

@@ -1,12 +1,14 @@
 package com.nb.auth.utils;
 
+import cn.dev33.satoken.SaManager;
 import cn.dev33.satoken.secure.BCrypt;
 import cn.dev33.satoken.spring.SpringMVCUtil;
-import cn.dev33.satoken.stp.StpUtil;
-//import cn.hutool.crypto.digest.BCrypt;
+import cn.dev33.satoken.stp.StpLogic;
+import cn.hutool.core.util.StrUtil;
 import com.nb.auth.bean.LoginUser;
 import com.nb.auth.enums.GrantTypeEnum;
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.Objects;
 
 /**
@@ -15,7 +17,7 @@ import java.util.Objects;
  * @author Kevin
  */
 public class SecurityUtil {
-    private static final String LOGIN_USER_KEY="loginUser";
+    public static final String LOGIN_USER_KEY="loginUser";
     /**
      * 获取用户账户
      **/
@@ -29,7 +31,7 @@ public class SecurityUtil {
      **/
     public static LoginUser getLoginUser(String authorization) {
         try {
-            return (LoginUser) StpUtil.getTokenSessionByToken(authorization).get(LOGIN_USER_KEY);
+            return (LoginUser) getStpLogic().getTokenSessionByToken(authorization).get(LOGIN_USER_KEY);
         } catch (Exception ex) {
             return null;
         }
@@ -40,7 +42,8 @@ public class SecurityUtil {
      **/
     public static LoginUser getLoginUser() {
         try {
-            return (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY);
+
+            return (LoginUser) getStpLogic().getTokenSession().get(LOGIN_USER_KEY);
         } catch (Exception ex) {
             return null;
         }
@@ -50,7 +53,7 @@ public class SecurityUtil {
      * 获取用户
      **/
     public static void  setLogin(LoginUser loginUser) {
-        StpUtil.getTokenSession().set(LOGIN_USER_KEY,loginUser);
+        getStpLogic().getTokenSession().set(LOGIN_USER_KEY,loginUser);
     }
 
     public static boolean isSys(){
@@ -75,7 +78,7 @@ public class SecurityUtil {
         return getLoginUser().isSuperAdmin();
 //        LoginUser loginUser = getLoginUser();
 //        String grantType = getLoginUser().getGrantType();
-//        if (GrantTypeEnum.APPKEY_APPSECRET.getCode().equalsIgnoreCase(grantType)) {
+//        if (GrantTypeEnum.APPKEY_APPSECRET.getValue().equalsIgnoreCase(grantType)) {
 //            return false;
 //        }
 //        List<SysRoleBO> roles = getSysUser().getRoles();
@@ -119,4 +122,18 @@ public class SecurityUtil {
     public static Object getId() {
         return getLoginUser().getId();
     }
+
+
+    public static StpLogic getStpLogic(){
+        HttpServletRequest request = SpringMVCUtil.getRequest();
+        return getStpLogic(request.getHeader("Login-Type"));
+    }
+
+    public static StpLogic getStpLogic(String loginType){
+        String header ="";
+        if(!StrUtil.isNullOrUndefined(loginType)){
+            header=loginType;
+        };
+        return SaManager.getStpLogic(header);
+    }
 }

+ 7 - 2
nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/CreateAndUpdateMetaObjectHandler.java

@@ -36,6 +36,12 @@ public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
     @Override
     public void insertFill(MetaObject metaObject) {
         try {
+            LoginUser loginUser =null;
+            try {
+                loginUser= SecurityUtil.getLoginUser();
+            }catch (Exception __){
+
+            }
             if (metaObject.hasGetter(CREATE_TIME) && metaObject.getValue(CREATE_TIME) == null) {
                 this.strictInsertFill(metaObject, CREATE_TIME, Date.class, new Date());
             }
@@ -43,14 +49,13 @@ public class CreateAndUpdateMetaObjectHandler implements MetaObjectHandler {
                 this.strictInsertFill(metaObject, IS_DELETE, Integer.class,0);
             }
             if (metaObject.hasGetter(CREATE_BY) && metaObject.getValue(CREATE_BY) == null) {
-                LoginUser loginUser = SecurityUtil.getLoginUser();
+
                 this.strictInsertFill(metaObject, CREATE_BY, String.class, Objects.isNull(loginUser) ? "1" : loginUser.getId().toString());
             }
             if (metaObject.hasGetter(UPDATE_TIME) && metaObject.getValue(UPDATE_TIME) == null) {
                 this.strictUpdateFill(metaObject, UPDATE_TIME, Date.class, new Date());
             }
             if (metaObject.hasGetter(UPDATE_BY) && metaObject.getValue(UPDATE_BY) == null) {
-                LoginUser loginUser = SecurityUtil.getLoginUser();
                 this.strictUpdateFill(metaObject, UPDATE_BY, String.class, Objects.isNull(loginUser) ? "1" : loginUser.getId().toString());
             }
             if (metaObject.hasGetter(TENANT_ID) && metaObject.getValue(TENANT_ID) == null) {

+ 15 - 0
nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/IntegerListTypeHandler.java

@@ -0,0 +1,15 @@
+package com.nb.common.config.mybatisplus.handler;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName StringListTypeHandler.java
+ * @Description TODO
+ * @createTime 2022年08月20日 14:32:00
+ */
+public class IntegerListTypeHandler extends ListTypeHandler<Integer> {
+    @Override
+    protected Class specificType() {
+        return Integer.class;
+    }
+}

+ 49 - 0
nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/ListTypeHandler.java

@@ -0,0 +1,49 @@
+package com.nb.common.config.mybatisplus.handler;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import org.apache.ibatis.type.*;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+@MappedJdbcTypes(JdbcType.VARCHAR)
+@MappedTypes({List.class})
+public abstract class ListTypeHandler<T> extends BaseTypeHandler<List<T>> {
+ 
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
+        String content = CollUtil.isEmpty(parameter) ? null : JSONUtil.toJsonStr(parameter);
+        ps.setString(i, content);
+    }
+ 
+    @Override
+    public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        return this.getListByJsonArrayString(rs.getString(columnName));
+    }
+ 
+    @Override
+    public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        return this.getListByJsonArrayString(rs.getString(columnIndex));
+    }
+ 
+    @Override
+    public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        return this.getListByJsonArrayString(cs.getString(columnIndex));
+    } 
+ 
+    private List<T> getListByJsonArrayString(String content) {
+        return StrUtil.isBlank(content) ? new ArrayList<>() : JSONUtil.toList(content, this.specificType());
+    }
+ 
+    /**
+     * 具体类型,由子类提供
+     *
+     * @return 具体类型
+     */
+    protected abstract Class specificType();
+}

+ 15 - 0
nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/StringListTypeHandler.java

@@ -0,0 +1,15 @@
+package com.nb.common.config.mybatisplus.handler;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName StringListTypeHandler.java
+ * @Description TODO
+ * @createTime 2022年08月20日 14:32:00
+ */
+public class StringListTypeHandler extends ListTypeHandler<String> {
+    @Override
+    protected Class specificType() {
+        return String.class;
+    }
+}

+ 2 - 1
nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/TenantNameHandler.java → nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/handler/TenantNameHandler.java

@@ -1,7 +1,8 @@
-package com.nb.common.config.mybatisplus;
+package com.nb.common.config.mybatisplus.handler;
 
 import cn.hutool.core.text.CharSequenceUtil;
 import cn.hutool.extra.spring.SpringUtil;
+import com.nb.common.config.mybatisplus.GetNameInterface;
 import org.apache.ibatis.type.JdbcType;
 import org.apache.ibatis.type.MappedJdbcTypes;
 import org.apache.ibatis.type.MappedTypes;

+ 4 - 0
nb-common/config-common/src/main/java/com/nb/common/config/mybatisplus/interceptor/DefaultTenantLineInnerInterceptor.java

@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
 import com.nb.auth.bean.LoginUser;
+import com.nb.auth.enums.StpTypeEnum;
 import com.nb.auth.utils.SecurityUtil;
 import com.nb.core.entity.TenantGenericEntity;
 import net.sf.jsqlparser.expression.Expression;
@@ -86,6 +87,9 @@ public class DefaultTenantLineInnerInterceptor {
                 if(request==null||request.getRequest() instanceof MockHttpServletRequest){
                     return true;
                 }
+                if (StpTypeEnum.ASSISTANT.getText().equals(SecurityUtil.getStpLogic().loginType)) {
+                    return true;
+                }
                 StringBuffer url = request.getRequest().getRequestURL();
                 if(ignoreUrlTenantId.stream().anyMatch(ignoreUrl->url.toString().endsWith(ignoreUrl))){
                     return true;

+ 2 - 2
nb-common/config-common/src/main/java/com/nb/common/config/notice/msg/ErrorMsg.java

@@ -18,8 +18,8 @@ public class ErrorMsg extends AbstractMsg{
 
     @Override
     String getSendMarkDownMsgContent() {
-        if(CharSequenceUtil.length(content)>4000){
-            content=content.substring(0,4000);
+        if(CharSequenceUtil.length(content)>1000){
+            content=content.substring(0,1000);
         }
         return  getType().getMarkDownTitle()+"\n"+String.format("> ### **错误信息**: \n" +
                 "`%s`",content);

+ 5 - 0
nb-common/config-common/src/main/java/com/nb/common/config/utils/DictUtil.java

@@ -32,6 +32,11 @@ public class DictUtil {
         return Objects.nonNull(obj) ? (List<DictModel>) obj : new ArrayList<>();
     }
 
+
+    public static void clearDict(String dictCode) {
+         SpringUtil.getBean(RedisUtils.class).del(getCacheKey(dictCode));
+    }
+
     public static String getDictLabel(String dictCode, String dictValue) {
         if (StrUtil.isBlank(dictCode) || StrUtil.isBlank(dictValue)) {
             return "";

+ 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<>();
 
-    public RPatternTopic getPatternTopic(String pattern){
-        return redissonClient.getPatternTopic(pattern);
-    }
+    private static Map<String,RTopic> rTopicMap=new ConcurrentHashMap<>();
 
     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){

+ 0 - 1
nb-common/crud-common/src/main/java/com/nb/common/crud/BaseService.java

@@ -325,5 +325,4 @@ public abstract class BaseService<M extends BaseMapper<E>, E,PK extends Serializ
      **/
 
     public  abstract void validateBeforeDelete(PK id) ;
-
 }

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

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

+ 5 - 1
nb-common/crud-common/src/main/java/com/nb/common/crud/controller/BaseCurdAuthController.java

@@ -2,6 +2,8 @@ package com.nb.common.crud.controller;
 
 import cn.dev33.satoken.stp.StpLogic;
 import cn.hutool.core.text.CharSequenceUtil;
+import com.nb.auth.utils.SecurityUtil;
+
 import java.util.Arrays;
 import java.util.List;
 
@@ -22,7 +24,9 @@ public interface BaseCurdAuthController {
      **/
     String getPermissionPrefix();
 
-    StpLogic getStpLogin();
+    default StpLogic getStpLogin(){
+        return SecurityUtil.getStpLogic();
+    };
 
     default void saveAuth(){
         if(isAuth()){

+ 32 - 26
nb-common/delay-queue-common/src/main/java/com/nb/common/queue/delay/RedissonDelayMessageManager.java

@@ -36,38 +36,20 @@ public class RedissonDelayMessageManager implements DelayMessageManager {
 
     private final RDelayedQueue<DelayMessage> delayedQueue;
 
+    private final List<DelayMessageHandler> handlers;
+
     private static final String NAME="redisson-delay-message-queue";
     @Autowired
     public RedissonDelayMessageManager(RedissonClient redissonClient,
                                        List<DelayMessageHandler> handlers) {
         this.blockingQueue = redissonClient.getBlockingQueue(NAME);
         this.delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
+        this.handlers=handlers;
         if(CollectionUtil.isEmpty(handlers)){
             log.warn("the size of DelayMessageHandler is zero");
         }
         //将头结点取出后触发该方法
-        blockingQueue.subscribeOnElements(i->{
-            //开启新的线程消费,唯一线程消费,不可阻塞该异步线程
-            CompletableFuture.runAsync(()->{
-                log.info("redisson延迟队列开始消费数据,消息id【{}】,消息内容【{}】",i.getMsgId(),JSONUtil.toJsonStr(i));
-                Optional<DelayMessageHandler> delayMessageHandler = handlers.stream().filter(handler -> ObjectUtil.equals(i.getHandlerId(), handler.getId()))
-                        .findFirst()
-                        .map(handler -> {
-                            handler.handle(i);
-                            return handler;
-                        });
-                if(!delayMessageHandler.isPresent()){
-                    log.warn("延迟队列消息处理失败,消息【{}】无相应的处理器处理",JSONUtil.toJsonStr(i));
-                }
-            })
-                    .whenComplete((__,t)->{
-                        if(t==null){
-                            log.info("redisson延迟队列中,数据【{}】消费完成",i.getMsgId());
-                        }else {
-                            log.error("redisson延迟队列中,消费数据【{}】失败,",JSONUtil.toJsonStr(i), t);
-                        }
-                    });
-        });
+        blockingQueue.subscribeOnElements(this::handler);
     }
 
     @PostConstruct
@@ -78,11 +60,13 @@ public class RedissonDelayMessageManager implements DelayMessageManager {
 
     @Override
     public void add(DelayMessage message) {
-        if (delayedQueue.contains(message)) {
-            return;
-        }
         log.info("redisson-delay-queue ,add message = 【{}】",JSONUtil.toJsonStr(message));
-        delayedQueue.offerAsync(message, message.getProperties().getExpire(), message.getProperties().getTimeUnit());
+        long expire = message.getProperties().getExpire();
+        if(expire<=0){
+            handler(message);
+        }else {
+            delayedQueue.offerAsync(message, message.getProperties().getExpire(), message.getProperties().getTimeUnit());
+        }
     }
 
     @Override
@@ -94,4 +78,26 @@ public class RedissonDelayMessageManager implements DelayMessageManager {
     public void destroy() {
         delayedQueue.destroy();
     }
+
+    private void handler(DelayMessage message){
+        CompletableFuture.runAsync(()->{
+            log.info("redisson延迟队列开始消费数据,消息id【{}】,消息内容【{}】",message.getMsgId(),JSONUtil.toJsonStr(message));
+            Optional<DelayMessageHandler> delayMessageHandler = handlers.stream().filter(handler -> ObjectUtil.equals(message.getHandlerId(), handler.getId()))
+                    .findFirst()
+                    .map(handler -> {
+                        handler.handle(message);
+                        return handler;
+                    });
+            if(!delayMessageHandler.isPresent()){
+                log.warn("延迟队列消息处理失败,消息【{}】无相应的处理器处理",JSONUtil.toJsonStr(message));
+            }
+        })
+                .whenComplete((__,t)->{
+                    if(t==null){
+                        log.info("redisson延迟队列中,数据【{}】消费完成",message.getMsgId());
+                    }else {
+                        log.error("redisson延迟队列中,消费数据【{}】失败,",JSONUtil.toJsonStr(message), t);
+                    }
+                });
+    }
 }

+ 6 - 0
nb-common/delay-queue-common/src/main/java/com/nb/common/queue/delay/message/DelayMessage.java

@@ -53,6 +53,12 @@ public class DelayMessage implements Serializable {
         this.properties = properties;
     }
 
+    public DelayMessage(@NonNull Object body, @NonNull String handlerId, @NonNull DelayMessageProperties properties) {
+        this.msgId = UUID.randomUUID().toString();
+        this.body = Value.simple(body);
+        this.handlerId = handlerId;
+        this.properties = properties;
+    }
 
 
     public void check() {

+ 23 - 7
nb-common/log-common/src/main/java/com/nb/common/log/aop/LogAspect.java

@@ -1,6 +1,7 @@
 package com.nb.common.log.aop;
 
 import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import cn.hutool.http.useragent.UserAgent;
 import cn.hutool.http.useragent.UserAgentUtil;
@@ -23,6 +24,7 @@ import org.aspectj.lang.annotation.Pointcut;
 import org.aspectj.lang.reflect.MethodSignature;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.stereotype.Component;
+import org.springframework.util.SerializationUtils;
 import org.springframework.validation.BindingResult;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
@@ -53,19 +55,24 @@ public class LogAspect {
         Long startTime = System.currentTimeMillis();
         Object obj = null;
         Exception exception = null;
+        List<Object> param = new ArrayList<>();
         try {
+
+            param = Arrays.stream(point.getArgs()).filter(item -> Objects.nonNull(item) && !isFilterObject(item))
+                    .map(BeanUtil::beanToMap)
+                    .collect(Collectors.toList());
             obj = point.proceed();
         } catch (Exception e) {
             exception = e;
             throw e;
         } finally {
             Long endTime = System.currentTimeMillis();
-            handleLog(point, exception, obj, endTime - startTime);
+            handleLog(point,param, exception, obj, endTime - startTime);
         }
         return obj;
     }
 
-    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object retValue, long time) {
+    protected void handleLog(final JoinPoint joinPoint, List<Object> param, final Exception e, Object retValue, long time) {
         try {
             // 获得注解
             Signature signature = joinPoint.getSignature();
@@ -86,9 +93,9 @@ public class LogAspect {
             String methodName = joinPoint.getSignature().getName();
             sysLog.setRequsetMethod(className + "." + methodName + "()");
 
-            List<Object> objectList = Arrays.stream(joinPoint.getArgs()).filter(item -> Objects.nonNull(item) && !isFilterObject(item)).collect(Collectors.toList());
-            sysLog.setRequsetParams(JSONUtil.toJsonStr(objectList));
-            sysLog.setResponseResult(JSONUtil.toJsonStr(retValue));
+
+            sysLog.setRequsetParams(JSONUtil.toJsonStr(param));
+//            sysLog.setResponseResult(JSONUtil.toJsonStr(retValue));
             sysLog.setRequsetTime(String.valueOf(time));
 
             String ipAddress = IpUtil.getClientIp(request);
@@ -103,9 +110,18 @@ public class LogAspect {
             sysLog.setBrowser(userAgent.getBrowser().getName());
             sysLog.setOs(userAgent.getOs().getName());
 
-            if (StpUtil.isLogin()) {
-                sysLog.setOperName(SecurityUtil.getUsername());
+            try {
+                if (SecurityUtil.getStpLogic().isLogin()) {
+                    try {
+                        sysLog.setOperName(SecurityUtil.getUsername());
+                    }catch (Exception __){
+
+                    }
+                }
+            }catch (Exception __){
+
             }
+
             if (e != null) {
                 sysLog.setLogStatus(LogStatusEnum.FAILURE.getCode());
                 sysLog.setException(e.getMessage());

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

@@ -3,6 +3,7 @@ package com.nb.common.websocket;
 import cn.hutool.json.JSONUtil;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nb.common.websocket.msg.MessageResponse;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.redisson.api.RPatternTopic;
@@ -35,7 +36,7 @@ public class DefaultMessageListener implements PatternMessageListener<TopicMessa
             String json = null;
             try {
                 json = objectMapper.writeValueAsString(MessageResponse.of(id, "result", msg.getParam(),
-                        msg.getMessage()));
+                        msg.getMessage(),msg.getKey()));
                 Tio.send(channelContext, WsResponse.fromText(json, WsPacket.CHARSET_NAME));
                 if(log.isDebugEnabled()){
                     log.debug("通道【{}】发送消息【{}】",channelContext.toString(),json);
@@ -43,7 +44,8 @@ public class DefaultMessageListener implements PatternMessageListener<TopicMessa
             } catch (JsonProcessingException e) {
                 log.error("ws消息订阅,解析失败,message:【】", JSONUtil.toJsonStr(msg));
             }
-        }else {
+        }
+        else {
             channelContext.setClosed(true);
             rPatternTopic.removeListener(this);
             Tio.remove(channelContext,"通道已关闭,移除该通道");

+ 0 - 20
nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultRedisCallBack.java

@@ -1,20 +0,0 @@
-package com.nb.common.websocket;
-
-import org.springframework.dao.DataAccessException;
-import org.springframework.data.redis.connection.RedisConnection;
-import org.springframework.data.redis.core.RedisCallback;
-
-/**
- * @author lifang
- * @version 1.0.0
- * @ClassName DefaultRedisCallBack.java
- * @Description TODO
- * @createTime 2022年03月25日 16:14:00
- */
-public class DefaultRedisCallBack implements RedisCallback<Object> {
-    @Override
-    public Object doInRedis(RedisConnection connection) throws DataAccessException {
-
-        return null;
-    }
-}

+ 13 - 7
nb-common/ws-common/src/main/java/com/nb/common/websocket/DefaultWebSocketMsgHandler.java

@@ -3,8 +3,12 @@ package com.nb.common.websocket;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.text.CharSequenceUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.hutool.json.JSONUtil;
+import com.nb.auth.bean.LoginUser;
+import com.nb.common.websocket.event.ConnectedEvent;
 import com.nb.common.websocket.handler.HisMsgHandler;
+import com.nb.common.websocket.msg.MessagingRequest;
 import com.nb.common.websocket.ws.IWebSocketAuthFilter;
 import com.nb.core.Constants;
 import com.nb.common.websocket.handler.WsHandler;
@@ -25,6 +29,8 @@ import org.tio.websocket.server.handler.IWsMsgHandler;
 import java.util.*;
 import java.util.stream.Collectors;
 
+import static com.nb.core.Constants.LOGIN_USER_KEY;
+
 @Component
 @Slf4j
 @AllArgsConstructor
@@ -33,7 +39,6 @@ public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
     private final List<WsHandler> messageHandlers;
     private final List<HisMsgHandler> hisMsgHandlers;
     private final List<IWebSocketAuthFilter> authFilters;
-
     @Override
     public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) {
         return httpResponse;
@@ -54,7 +59,11 @@ public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
         }
         boolean match = authFilters.stream().anyMatch(filter -> Boolean.TRUE.equals(filter.auth(httpRequest, httpResponse, channelContext)));
         if(match){
-            Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(R.success("连接成功")),"utf-8"));
+            Object loginUser = channelContext.get(LOGIN_USER_KEY);
+            if(loginUser!=null&&loginUser instanceof LoginUser){
+                SpringUtil.publishEvent(new ConnectedEvent(this, (LoginUser) loginUser,channelContext));
+            }
+            Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(R.success("连接成功")),WsPacket.CHARSET_NAME));
         }else {
             Tio.send(channelContext,WsResponse.fromText(JSONUtil.toJsonStr(R.fail("授权失败")), WsPacket.CHARSET_NAME));
             Thread.sleep(20);
@@ -87,7 +96,7 @@ public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
         try {
             synchronized (channelContext.getId()){
                 if(log.isDebugEnabled()){
-                    log.debug("websocket 接收到消息,message:{},token:{},userId:{}",message,channelContext.getToken(),JSONUtil.toJsonStr(channelContext.get(Constants.LOGIN_USER_KEY)));
+                    log.debug("websocket 接收到消息,message:{},token:{},assistId:{}",message,channelContext.getToken(),JSONUtil.toJsonStr(channelContext.get(Constants.LOGIN_USER_KEY)));
                 }
                 Object user = channelContext.get(Constants.LOGIN_USER_KEY);
                 if(ObjectUtil.isNotNull(user)){
@@ -97,7 +106,7 @@ public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
                 }
             }
         }catch (Exception e){
-            log.warn("websocket 接收到异常请求,token:{},message:{},userId:{}",channelContext.getToken(),message,JSONUtil.toJsonStr(channelContext.get(Constants.LOGIN_USER_KEY)));
+            log.warn("websocket 接收到异常请求,token:{},message:{},assistId:{}",channelContext.getToken(),message,JSONUtil.toJsonStr(channelContext.get(Constants.LOGIN_USER_KEY)));
             channelContext.setCloseCode(ChannelContext.CloseCode.HEARTBEAT_TIMEOUT);
             Tio.remove(channelContext,  "接受数据不符合业务规范");
         }
@@ -116,9 +125,6 @@ public class DefaultWebSocketMsgHandler implements IWsMsgHandler {
      */
     private void handleUserMessage( String message, ChannelContext channelContext){
         MessagingRequest messagingRequest = JSONUtil.toBean(message, MessagingRequest.class);
-        if(messagingRequest.isFromHis()){
-            return;
-        }
         List<WsHandler> collect = messageHandlers
                 .parallelStream()
                 .filter(handler -> messagingRequest.getId().equals(handler.getId()))

+ 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 key;
+    private boolean success;
+    private String errorMsg;
+
+
+    public static PubResponse success(String key){
+        return PubResponse.of(key,true,"");
+    }
+
+    public static PubResponse fail(String key,String errorMsg){
+        return PubResponse.of(key,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;
 
 @Data
-@AllArgsConstructor(staticName = "of")
+@AllArgsConstructor
 @NoArgsConstructor
 public class TopicMessage implements Serializable {
     private Object message;
     private String param;
+    private String key;
+
+
+    public static TopicMessage of(Object message,String param){
+        return of(message,param,null);
+    }
+
+
+    public static TopicMessage of(Object message,String param,String key){
+        return new TopicMessage(message,param,key);
+    }
 
 }

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

@@ -28,6 +28,7 @@ public class WebSocketConstant {
 
     public static final String UNSUB_ALL ="all";
 
+    public static final String IM ="im";
     /**
      * 病人监控订阅
      */
@@ -47,7 +48,7 @@ public class WebSocketConstant {
      */
     public static TopicWrapper getTopic(String id,String productName,String param,String tenantId){
         productName=StrUtil.isEmptyIfStr(productName)?"default":productName;
-        tenantId=StrUtil.isNullOrUndefined(tenantId)?"*":tenantId;
+        tenantId=StrUtil.isNullOrUndefined(tenantId)||StrUtil.isBlank(tenantId)?"*":tenantId;
         return TopicWrapper.of(id+"-"+productName+"-"+param+"-"+tenantId,param);
     }
 

+ 68 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/WebSocketSessionLifeCycleManage.java

@@ -0,0 +1,68 @@
+package com.nb.common.websocket;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.nb.auth.bean.LoginUser;
+import com.nb.common.websocket.event.ConnectedEvent;
+import com.nb.common.websocket.event.DisConnectedEvent;
+import com.nb.core.cache.ConfigStorage;
+import com.nb.core.cache.manager.ConfigStorageManager;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName WebSocketSessionLifeCycleManage.java
+ * @Description websocket生命周期管理
+ * @createTime 2022年08月13日 16:46:00
+ */
+@Component
+public class WebSocketSessionLifeCycleManage {
+    private final ConfigStorage configStorage;
+
+    public WebSocketSessionLifeCycleManage (ConfigStorageManager configStorageManager){
+        configStorage=configStorageManager.getStorage("ws-lifecycle",false);
+    }
+
+
+    public boolean isOnline(String userId){
+        List channelIds = configStorage.getConfig(userId).as(ArrayList.class);
+        return CollectionUtil.isNotEmpty(channelIds);
+    }
+
+    /**
+     * 描述: 看护人上线
+     * @author lifang
+     * @date 2022/8/13 16:41
+     * @param event
+     * @return void
+     */
+    @EventListener
+    public void connected(ConnectedEvent event){
+        LoginUser loginUser = event.getLoginUser();
+        String key=String.valueOf(loginUser.getId());
+        List channelIds = Optional.ofNullable(configStorage.getConfig(key).as(ArrayList.class)).orElse(new ArrayList());
+        channelIds.add(key);
+        configStorage.setConfig(key,channelIds);
+    }
+
+    /**
+     * 描述: 看护人下线
+     * @author lifang
+     * @date 2022/8/13 16:41
+     * @param event
+     * @return void
+     */
+    @EventListener
+    public void disconnected(DisConnectedEvent event){
+        LoginUser loginUser = event.getLoginUser();
+        String key=String.valueOf(loginUser.getId());
+        List channelIds = configStorage.getConfig(key).as(ArrayList.class);
+        if (CollectionUtil.isNotEmpty(channelIds)) {
+            channelIds.remove(key);
+            configStorage.setConfig(key,channelIds);
+        }
+    }
+}

+ 24 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/event/ConnectedEvent.java

@@ -0,0 +1,24 @@
+package com.nb.common.websocket.event;
+
+import com.nb.auth.bean.LoginUser;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+import org.tio.core.ChannelContext;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ConnectEvent.java
+ * @Description TODO
+ * @createTime 2022年08月13日 16:35:00
+ */
+@Getter
+public class ConnectedEvent  extends ApplicationEvent {
+    private LoginUser loginUser;
+    private ChannelContext channelContext;
+    public ConnectedEvent(Object source, LoginUser loginUser, ChannelContext channelContext) {
+        super(source);
+        this.channelContext=channelContext;
+        this.loginUser=loginUser;
+    }
+}

+ 24 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/event/DisConnectedEvent.java

@@ -0,0 +1,24 @@
+package com.nb.common.websocket.event;
+
+import com.nb.auth.bean.LoginUser;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+import org.tio.core.ChannelContext;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ConnectEvent.java
+ * @Description TODO
+ * @createTime 2022年08月13日 16:35:00
+ */
+@Getter
+public class DisConnectedEvent extends ApplicationEvent {
+    private LoginUser loginUser;
+    private ChannelContext channelContext;
+    public DisConnectedEvent(Object source, LoginUser loginUser, ChannelContext channelContext) {
+        super(source);
+        this.channelContext=channelContext;
+        this.loginUser=loginUser;
+    }
+}

+ 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.msg.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;
+    }
+}

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

@@ -0,0 +1,41 @@
+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.TopicMessage;
+import com.nb.common.websocket.msg.MessageResponse;
+import com.nb.common.websocket.msg.MessagingRequest;
+import com.nb.common.websocket.PubResponse;
+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.isBlank(source.getKey())){
+            for (String param : source.getParams()) {
+                Tio.send(channelContext, WsResponse.fromText(JSONUtil.toJsonStr(
+                        MessageResponse.of("im","im-result",
+                                param,
+                                PubResponse.fail(source.getKey(),"发布消息时key不能为空"),
+                                source.getKey())
+
+                ), WsPacket.CHARSET_NAME));
+            }
+            return false;
+        }
+        return ObjectUtil.isNotNull(source.getPayload());
+    }
+}

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

@@ -0,0 +1,25 @@
+package com.nb.common.websocket.filter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.nb.common.websocket.msg.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);
+}

+ 43 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/handler/DefaultCloseHandler.java

@@ -0,0 +1,43 @@
+package com.nb.common.websocket.handler;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.nb.auth.bean.LoginUser;
+import com.nb.common.websocket.msg.MessagingRequest;
+import com.nb.common.websocket.event.DisConnectedEvent;
+import org.springframework.stereotype.Component;
+import org.tio.core.ChannelContext;
+
+import static com.nb.core.Constants.LOGIN_USER_KEY;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName DefaultCloseHandler.java
+ * @Description TODO
+ * @createTime 2022年08月13日 16:38:00
+ */
+@Component
+public class DefaultCloseHandler implements WsHandler {
+    @Override
+    public String getId() {
+        return null;
+    }
+
+    @Override
+    public void onMessage(MessagingRequest message, ChannelContext channelContext) {
+
+    }
+
+    @Override
+    public void close(ChannelContext channelContext) {
+        Object loginUser = channelContext.get(LOGIN_USER_KEY);
+        if(loginUser!=null&&loginUser instanceof LoginUser){
+            SpringUtil.publishEvent(new DisConnectedEvent(this, (LoginUser) loginUser,channelContext));
+        }
+    }
+
+    @Override
+    public boolean needParam() {
+        return false;
+    }
+}

+ 10 - 99
nb-common/ws-common/src/main/java/com/nb/common/websocket/handler/Subscribe.java

@@ -1,19 +1,15 @@
 package com.nb.common.websocket.handler;
 
 import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import com.nb.auth.bean.LoginUser;
+import com.nb.common.websocket.*;
+import com.nb.common.websocket.msg.handler.IMsgRequestHandler;
+import com.nb.common.websocket.msg.MessagingRequest;
+import com.nb.common.websocket.msg.handler.MsgRequestHandlerManager;
 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 lombok.extern.slf4j.Slf4j;
-import org.redisson.api.RPatternTopic;
-import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.util.ConcurrentReferenceHashMap;
 import org.tio.core.ChannelContext;
 
 import java.util.*;
@@ -28,16 +24,9 @@ import java.util.stream.Collectors;
  */
 @Slf4j
 public abstract class Subscribe implements WsHandler {
-    @Autowired
-    private ObjectMapper objectMapper;
 
     @Autowired
-    private RedissonClient redissonClient;
-    /**
-     * 存储主题与redis通道关联
-     */
-    private static Map<String, RPatternTopic> topicMap=new ConcurrentReferenceHashMap<>();
-
+    private MsgRequestHandlerManager requestHandlerManager;
 
     public TopicWrapper getTopic(String productName,String param,String tenantId){
         return  WebSocketConstant.getTopic(this.getId(),productName, param, tenantId);
@@ -67,90 +56,12 @@ public abstract class Subscribe implements WsHandler {
         else {
             subScribeTopic = Collections.singletonList(getTopic(message.getProductName(), null, message.getTenantId()));
         }
-
-        MessagingRequest.Type type = message.getType();
-        if(MessagingRequest.Type.sub==type){
-            //订阅主题
-            subScribeTopic.forEach(topicWrapper->this.subscribe(channelContext,topicWrapper));
-        }else {
-            //取消订阅主题
-            subScribeTopic.forEach(topicWrapper->this.unsubscribe(channelContext,topicWrapper.getTopic()));
+        IMsgRequestHandler handle = requestHandlerManager.getHandle(message.getType());
+        if(handle!=null){
+            subScribeTopic.forEach(topicWrapper->handle.handler(message,channelContext,topicWrapper));
         }
         if(log.isDebugEnabled()){
-            log.debug("订阅成功{}",subScribeTopic.stream().map(TopicWrapper::getTopic).collect(Collectors.toList()));
-        }
-    }
-
-    /**
-     * ws 订阅主题
-     * @param channelContext
-     * @param topicWrapper
-     */
-    public void subscribe(ChannelContext channelContext, TopicWrapper topicWrapper){
-        getChannelTopic(channelContext).add(topicWrapper.getTopic());
-        //同一主题只订阅一次
-        RPatternTopic rTopic = topicMap.computeIfAbsent(topicWrapper.getTopic(),topic->redissonClient.getPatternTopic(topicWrapper.getTopic()));
-        addTopicListener(rTopic,channelContext, topicWrapper.getTopic());
-    };
-
-    /**
-     * ws取消订阅主题
-     * @param channelContext
-     * @param topic
-     */
-    public void unsubscribe(ChannelContext channelContext, String topic){
-        if(StrUtil.isEmpty(topic)){
-            return;
-        }
-        Set<String> allTopics=new HashSet<>();
-
-        if(topic.startsWith("all")){
-            allTopics.addAll(getChannelTopic(channelContext));
-        }
-
-        //取消订阅
-        for (String subTopic : allTopics) {
-            topicMap.computeIfPresent(subTopic,(k,rTopic)->{
-                rTopic.removeListener( getTopicListener(channelContext,k));
-                return rTopic;
-            });
-            Map<String, DefaultMessageListener> topicListeners = getTopicListeners(channelContext);
-            topicListeners.remove(subTopic);
-        }
-
-    };
-
-
-    public Map<String,DefaultMessageListener> getTopicListeners(ChannelContext channelContext){
-        Object result = Optional.ofNullable(channelContext.get("topic")).orElse(new HashMap<String,DefaultMessageListener>());
-        channelContext.set("topic",result);
-        return  (Map<String,DefaultMessageListener>) result;
-    }
-
-    public DefaultMessageListener getTopicListener(ChannelContext channelContext,String topic){
-        Map<String,DefaultMessageListener> result = (Map<String, DefaultMessageListener>) Optional.ofNullable(channelContext.get("topic")).orElse(new HashMap<>());
-        return  result.get(topic);
-    }
-
-    public DefaultMessageListener addTopicListener(RPatternTopic rTopic,ChannelContext channelContext,String topic){
-        Map<String, DefaultMessageListener> topicByChannel = getTopicListeners(channelContext);
-        DefaultMessageListener messageListener = topicByChannel.computeIfAbsent(topic, k -> {
-            DefaultMessageListener defaultMessageListener = new DefaultMessageListener(getId(), objectMapper, channelContext,rTopic);
-            rTopic.addListenerAsync(TopicMessage.class, defaultMessageListener);
-            return defaultMessageListener;
-        });
-        return  messageListener;
-    }
-
-    private Set<String> getChannelTopic(ChannelContext channelContext){
-        Set<String> topics=null;
-        Object topicSet = channelContext.getAttribute("subtopic");
-        if(topicSet==null){
-            topics=new HashSet<>();
-        }else {
-            topics= (Set<String>) topicSet;
+            log.debug("ws数据处理成功{}", JSONUtil.toJsonStr(message));
         }
-        channelContext.setAttribute("subtopic",topics);
-        return topics;
     }
 }

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

@@ -1,6 +1,6 @@
 package com.nb.common.websocket.handler;
 
-import com.nb.common.websocket.MessagingRequest;
+import com.nb.common.websocket.msg.MessagingRequest;
 import org.tio.core.ChannelContext;
 
 /**

+ 2 - 1
nb-common/ws-common/src/main/java/com/nb/common/websocket/MessageResponse.java → nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/MessageResponse.java

@@ -1,4 +1,4 @@
-package com.nb.common.websocket;
+package com.nb.common.websocket.msg;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -19,4 +19,5 @@ public class MessageResponse implements Serializable {
     private String type;
     private String param;
     private Object payload;
+    private String key;
 }

+ 14 - 8
nb-common/ws-common/src/main/java/com/nb/common/websocket/MessagingRequest.java → nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/MessagingRequest.java

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

+ 88 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/IMsgRequestHandler.java

@@ -0,0 +1,88 @@
+package com.nb.common.websocket.msg.handler;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nb.common.websocket.DefaultMessageListener;
+import com.nb.common.websocket.TopicMessage;
+import com.nb.common.websocket.handler.TopicWrapper;
+import com.nb.common.websocket.msg.MessagingRequest;
+import org.redisson.api.RPatternTopic;
+import org.redisson.api.RTopic;
+import org.tio.core.ChannelContext;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName MsgRequestHandler.java
+ * @Description 消息处理器
+ * @createTime 2022年08月18日 18:33:00
+ */
+public interface IMsgRequestHandler {
+    /**
+     * 订阅主题缓存
+     */
+    Map<String, RPatternTopic> subTopicMap =new ConcurrentHashMap<>();
+    /**
+     * 发布主题缓存
+     */
+    Map<String, RTopic> pubTopicMap =new ConcurrentHashMap<>();
+
+    MessagingRequest.Type getType();
+    /**
+     * 描述:
+     * @author lifang
+     * @date 2022/8/18 18:35
+     * @param message 发送消息内容
+     * @param channelContext 发送消息通道
+     * @param topicWrapper 主题
+     * @return void
+     */
+    void handler(MessagingRequest message, ChannelContext channelContext, TopicWrapper topicWrapper);
+
+    /**
+     * 描述: 获取序列化方式
+     * @author lifang
+     * @date 2022/8/18 18:51
+     * @param
+     * @return ObjectMapper
+     */
+    ObjectMapper getObjectMapper();
+
+    default Set<String> getChannelTopic(ChannelContext channelContext){
+        Set<String> topics=null;
+        Object topicSet = channelContext.getAttribute("subtopic");
+        if(topicSet==null){
+            topics=new HashSet<>();
+        }else {
+            topics= (Set<String>) topicSet;
+        }
+        channelContext.setAttribute("subtopic",topics);
+        return topics;
+    }
+
+    default DefaultMessageListener addTopicListener(RPatternTopic rTopic, ChannelContext channelContext, String topic,String msgId){
+        Map<String, DefaultMessageListener> topicByChannel = getTopicListeners(channelContext);
+        DefaultMessageListener messageListener = topicByChannel.computeIfAbsent(topic, k -> {
+            DefaultMessageListener defaultMessageListener = new DefaultMessageListener(msgId, getObjectMapper(), channelContext,rTopic);
+            rTopic.addListenerAsync(TopicMessage.class, defaultMessageListener);
+            return defaultMessageListener;
+        });
+        return  messageListener;
+    }
+
+    default Map<String,DefaultMessageListener> getTopicListeners(ChannelContext channelContext){
+        Object result = Optional.ofNullable(channelContext.get("topic")).orElse(new HashMap<String,DefaultMessageListener>());
+        channelContext.set("topic",result);
+        return  (Map<String,DefaultMessageListener>) result;
+    }
+
+
+    default DefaultMessageListener getTopicListener(ChannelContext channelContext,String topic){
+        Map<String,DefaultMessageListener> result = (Map<String, DefaultMessageListener>) Optional.ofNullable(channelContext.get("topic")).orElse(new HashMap<>());
+        return  result.get(topic);
+    }
+
+}

+ 24 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/ISubSuccessProcessor.java

@@ -0,0 +1,24 @@
+package com.nb.common.websocket.msg.handler;
+
+import com.nb.common.websocket.msg.MessagingRequest;
+import org.tio.core.ChannelContext;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ISubSuccessProcessor.java
+ * @Description TODO
+ * @createTime 2022年09月15日 10:13:00
+ */
+public interface ISubSuccessProcessor {
+
+    /**
+     * 描述: 取消订阅成功后
+     * @author lifang
+     * @date 2022/9/15 10:15
+     * @param message
+     * @param channelContext
+     * @return void
+     */
+    void postSub(MessagingRequest message, ChannelContext channelContext);
+}

+ 25 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/IUnSubSuccessProcessor.java

@@ -0,0 +1,25 @@
+package com.nb.common.websocket.msg.handler;
+
+import com.nb.common.websocket.msg.MessagingRequest;
+import org.tio.core.ChannelContext;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ISubSuccessProcessor.java
+ * @Description TODO
+ * @createTime 2022年09月15日 10:13:00
+ */
+public interface IUnSubSuccessProcessor {
+
+    /**
+     * 描述: 取消订阅成功后
+     * @author lifang
+     * @date 2022/9/15 10:15
+     * @param message
+     * @param channelContext
+     * @return void
+     *
+     */
+    void postUnSub(MessagingRequest message, ChannelContext channelContext);
+}

+ 22 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/MsgRequestHandlerManager.java

@@ -0,0 +1,22 @@
+package com.nb.common.websocket.msg.handler;
+
+import com.nb.common.websocket.msg.MessagingRequest;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+import java.util.*;
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName MsgRequestHandlerManager.java
+ * @Description 消息处理管理器
+ * @createTime 2022年08月18日 18:42:00
+ */
+@AllArgsConstructor
+@Component
+public class MsgRequestHandlerManager {
+    private final List<IMsgRequestHandler>  handlers;
+
+    public IMsgRequestHandler getHandle(MessagingRequest.Type type){
+        return handlers.stream().filter(handler->type.equals(handler.getType())).findAny().orElse(null);
+    }
+}

+ 43 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/SubMsgRequestHandler.java

@@ -0,0 +1,43 @@
+package com.nb.common.websocket.msg.handler;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nb.common.websocket.handler.TopicWrapper;
+import com.nb.common.websocket.msg.MessagingRequest;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.redisson.api.RPatternTopic;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Component;
+import org.tio.core.ChannelContext;
+import java.util.*;
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName SubMsgRequestHandler.java
+ * @Description TODO
+ * @createTime 2022年08月18日 18:47:00
+ */
+@Component
+@AllArgsConstructor
+public class SubMsgRequestHandler implements IMsgRequestHandler {
+    @Getter
+    private final ObjectMapper objectMapper;
+
+    private final RedissonClient redissonClient;
+
+    private final List<ISubSuccessProcessor> processorList;
+    @Override
+    public MessagingRequest.Type getType() {
+        return MessagingRequest.Type.sub;
+    }
+
+    @Override
+    public void handler(MessagingRequest message, ChannelContext channelContext, TopicWrapper topicWrapper) {
+        getChannelTopic(channelContext).add(topicWrapper.getTopic());
+        //同一主题只订阅一次
+        RPatternTopic rTopic = subTopicMap.computeIfAbsent(topicWrapper.getTopic(), topic->redissonClient.getPatternTopic(topicWrapper.getTopic()));
+        addTopicListener(rTopic,channelContext, topicWrapper.getTopic(),message.getId());
+
+        processorList.forEach(processor->processor.postSub(message,channelContext));
+    }
+}

+ 60 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/msg/handler/UnSubMsgRequestHandler.java

@@ -0,0 +1,60 @@
+package com.nb.common.websocket.msg.handler;
+
+import cn.hutool.core.util.StrUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nb.common.websocket.DefaultMessageListener;
+import com.nb.common.websocket.handler.TopicWrapper;
+import com.nb.common.websocket.msg.MessagingRequest;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.stereotype.Component;
+import org.tio.core.ChannelContext;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName SubMsgRequestHandler.java
+ * @Description TODO
+ * @createTime 2022年08月18日 18:47:00
+ */
+@Component
+@AllArgsConstructor
+public class UnSubMsgRequestHandler implements IMsgRequestHandler {
+    @Getter
+    private final ObjectMapper objectMapper;
+    private final List<IUnSubSuccessProcessor> processorList;
+    @Override
+    public MessagingRequest.Type getType() {
+        return MessagingRequest.Type.unsub;
+    }
+
+    @Override
+    public void handler(MessagingRequest message, ChannelContext channelContext, TopicWrapper topicWrapper) {
+        String topic = topicWrapper.getTopic();
+        if(StrUtil.isEmpty(topic)){
+            return;
+        }
+        Set<String> allTopics=new HashSet<>();
+
+        if(topic.startsWith("all")){
+            allTopics.addAll(getChannelTopic(channelContext));
+        }
+
+        //取消订阅
+        for (String subTopic : allTopics) {
+            subTopicMap.computeIfPresent(subTopic,(k, rTopic)->{
+                rTopic.removeListener( getTopicListener(channelContext,k));
+                return rTopic;
+            });
+            Map<String, DefaultMessageListener> topicListeners = getTopicListeners(channelContext);
+            topicListeners.remove(subTopic);
+        }
+
+        processorList.forEach(processor->processor.postUnSub(message,channelContext));
+    }
+}

+ 342 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/ws/CloseRunnable.java

@@ -0,0 +1,342 @@
+/*
+	Apache License
+	Version 2.0, January 2004
+	http://www.apache.org/licenses/
+	
+	TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+	
+	1. Definitions.
+	
+	"License" shall mean the terms and conditions for use, reproduction, and
+	distribution as defined by Sections 1 through 9 of this document.
+	
+	"Licensor" shall mean the copyright owner or entity authorized by the copyright
+	owner that is granting the License.
+	
+	"Legal Entity" shall mean the union of the acting entity and all other entities
+	that control, are controlled by, or are under common control with that entity.
+	For the purposes of this definition, "control" means (i) the power, direct or
+	indirect, to cause the direction or management of such entity, whether by
+	contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+	outstanding shares, or (iii) beneficial ownership of such entity.
+	
+	"You" (or "Your") shall mean an individual or Legal Entity exercising
+	permissions granted by this License.
+	
+	"Source" form shall mean the preferred form for making modifications, including
+	but not limited to software source code, documentation source, and configuration
+	files.
+	
+	"Object" form shall mean any form resulting from mechanical transformation or
+	translation of a Source form, including but not limited to compiled object code,
+	generated documentation, and conversions to other media types.
+	
+	"Work" shall mean the work of authorship, whether in Source or Object form, made
+	available under the License, as indicated by a copyright notice that is included
+	in or attached to the work (an example is provided in the Appendix below).
+	
+	"Derivative Works" shall mean any work, whether in Source or Object form, that
+	is based on (or derived from) the Work and for which the editorial revisions,
+	annotations, elaborations, or other modifications represent, as a whole, an
+	original work of authorship. For the purposes of this License, Derivative Works
+	shall not include works that remain separable from, or merely link (or bind by
+	name) to the interfaces of, the Work and Derivative Works thereof.
+	
+	"Contribution" shall mean any work of authorship, including the original version
+	of the Work and any modifications or additions to that Work or Derivative Works
+	thereof, that is intentionally submitted to Licensor for inclusion in the Work
+	by the copyright owner or by an individual or Legal Entity authorized to submit
+	on behalf of the copyright owner. For the purposes of this definition,
+	"submitted" means any form of electronic, verbal, or written communication sent
+	to the Licensor or its representatives, including but not limited to
+	communication on electronic mailing lists, source code control systems, and
+	issue tracking systems that are managed by, or on behalf of, the Licensor for
+	the purpose of discussing and improving the Work, but excluding communication
+	that is conspicuously marked or otherwise designated in writing by the copyright
+	owner as "Not a Contribution."
+	
+	"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+	of whom a Contribution has been received by Licensor and subsequently
+	incorporated within the Work.
+	
+	2. Grant of Copyright License.
+	
+	Subject to the terms and conditions of this License, each Contributor hereby
+	grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+	irrevocable copyright license to reproduce, prepare Derivative Works of,
+	publicly display, publicly perform, sublicense, and distribute the Work and such
+	Derivative Works in Source or Object form.
+	
+	3. Grant of Patent License.
+	
+	Subject to the terms and conditions of this License, each Contributor hereby
+	grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+	irrevocable (except as stated in this section) patent license to make, have
+	made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+	such license applies only to those patent claims licensable by such Contributor
+	that are necessarily infringed by their Contribution(s) alone or by combination
+	of their Contribution(s) with the Work to which such Contribution(s) was
+	submitted. If You institute patent litigation against any entity (including a
+	cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+	Contribution incorporated within the Work constitutes direct or contributory
+	patent infringement, then any patent licenses granted to You under this License
+	for that Work shall terminate as of the date such litigation is filed.
+	
+	4. Redistribution.
+	
+	You may reproduce and distribute copies of the Work or Derivative Works thereof
+	in any medium, with or without modifications, and in Source or Object form,
+	provided that You meet the following conditions:
+	
+	You must give any other recipients of the Work or Derivative Works a copy of
+	this License; and
+	You must cause any modified files to carry prominent notices stating that You
+	changed the files; and
+	You must retain, in the Source form of any Derivative Works that You distribute,
+	all copyright, patent, trademark, and attribution notices from the Source form
+	of the Work, excluding those notices that do not pertain to any part of the
+	Derivative Works; and
+	If the Work includes a "NOTICE" text file as part of its distribution, then any
+	Derivative Works that You distribute must include a readable copy of the
+	attribution notices contained within such NOTICE file, excluding those notices
+	that do not pertain to any part of the Derivative Works, in at least one of the
+	following places: within a NOTICE text file distributed as part of the
+	Derivative Works; within the Source form or documentation, if provided along
+	with the Derivative Works; or, within a display generated by the Derivative
+	Works, if and wherever such third-party notices normally appear. The contents of
+	the NOTICE file are for informational purposes only and do not modify the
+	License. You may add Your own attribution notices within Derivative Works that
+	You distribute, alongside or as an addendum to the NOTICE text from the Work,
+	provided that such additional attribution notices cannot be construed as
+	modifying the License.
+	You may add Your own copyright statement to Your modifications and may provide
+	additional or different license terms and conditions for use, reproduction, or
+	distribution of Your modifications, or for any such Derivative Works as a whole,
+	provided Your use, reproduction, and distribution of the Work otherwise complies
+	with the conditions stated in this License.
+	
+	5. Submission of Contributions.
+	
+	Unless You explicitly state otherwise, any Contribution intentionally submitted
+	for inclusion in the Work by You to the Licensor shall be under the terms and
+	conditions of this License, without any additional terms or conditions.
+	Notwithstanding the above, nothing herein shall supersede or modify the terms of
+	any separate license agreement you may have executed with Licensor regarding
+	such Contributions.
+	
+	6. Trademarks.
+	
+	This License does not grant permission to use the trade names, trademarks,
+	service marks, or product names of the Licensor, except as required for
+	reasonable and customary use in describing the origin of the Work and
+	reproducing the content of the NOTICE file.
+	
+	7. Disclaimer of Warranty.
+	
+	Unless required by applicable law or agreed to in writing, Licensor provides the
+	Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+	including, without limitation, any warranties or conditions of TITLE,
+	NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+	solely responsible for determining the appropriateness of using or
+	redistributing the Work and assume any risks associated with Your exercise of
+	permissions under this License.
+	
+	8. Limitation of Liability.
+	
+	In no event and under no legal theory, whether in tort (including negligence),
+	contract, or otherwise, unless required by applicable law (such as deliberate
+	and grossly negligent acts) or agreed to in writing, shall any Contributor be
+	liable to You for damages, including any direct, indirect, special, incidental,
+	or consequential damages of any character arising as a result of this License or
+	out of the use or inability to use the Work (including but not limited to
+	damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+	any and all other commercial damages or losses), even if such Contributor has
+	been advised of the possibility of such damages.
+	
+	9. Accepting Warranty or Additional Liability.
+	
+	While redistributing the Work or Derivative Works thereof, You may choose to
+	offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+	other liability obligations and/or rights consistent with this License. However,
+	in accepting such obligations, You may act only on Your own behalf and on Your
+	sole responsibility, not on behalf of any other Contributor, and only if You
+	agree to indemnify, defend, and hold each Contributor harmless for any liability
+	incurred by, or claims asserted against, such Contributor by reason of your
+	accepting any such warranty or additional liability.
+	
+	END OF TERMS AND CONDITIONS
+	
+	APPENDIX: How to apply the Apache License to your work
+	
+	To apply the Apache License to your work, attach the following boilerplate
+	notice, with the fields enclosed by brackets "{}" replaced with your own
+	identifying information. (Don't include the brackets!) The text should be
+	enclosed in the appropriate comment syntax for the file format. We also
+	recommend that a file or class name and description of purpose be included on
+	the same "printed page" as the copyright notice for easier identification within
+	third-party archives.
+	
+	   Copyright 2018 JFinal
+	
+	   Licensed under the Apache License, Version 2.0 (the "License");
+	   you may not use this file except in compliance with the License.
+	   You may obtain a copy of the License at
+	
+	     http://www.apache.org/licenses/LICENSE-2.0
+	
+	   Unless required by applicable law or agreed to in writing, software
+	   distributed under the License is distributed on an "AS IS" BASIS,
+	   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+	   See the License for the specific language governing permissions and
+	   limitations under the License.
+*/
+package com.nb.common.websocket.ws;
+
+import java.util.concurrent.Executor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tio.client.ClientChannelContext;
+import org.tio.client.ClientTioConfig;
+import org.tio.client.ReconnConf;
+import org.tio.core.ChannelContext;
+import org.tio.core.maintain.MaintainUtils;
+import org.tio.utils.SystemTimer;
+import org.tio.utils.queue.FullWaitQueue;
+import org.tio.utils.queue.TioFullWaitQueue;
+import org.tio.utils.thread.pool.AbstractQueueRunnable;
+
+/**
+ * 
+ * @author tanyaowu 
+ * 2017年10月19日 上午9:39:59
+ */
+public class CloseRunnable extends AbstractQueueRunnable<ChannelContext> {
+
+	private static Logger log = LoggerFactory.getLogger(CloseRunnable.class);
+
+	public CloseRunnable(Executor executor) {
+		super(executor);
+		getMsgQueue();
+	}
+	//	long count = 1;
+
+	@Override
+	public void runTask() {
+		if (msgQueue.isEmpty()) {
+			return;
+		}
+		ChannelContext channelContext = null;
+		while ((channelContext = msgQueue.poll()) != null) {
+			//			System.out.println(count++);
+			try {
+				boolean isNeedRemove = channelContext.closeMeta.isNeedRemove;
+				String remark = channelContext.closeMeta.remark;
+				Throwable throwable = channelContext.closeMeta.throwable;
+
+				//				WriteLock writeLock = channelContext.closeLock.writeLock();
+				//				boolean isLock = writeLock.tryLock();
+				//				if (!isLock) {
+				//					if (isNeedRemove) {
+				//						if (channelContext.isRemoved) {
+				//							return;
+				//						} else {
+				//							writeLock.lock();
+				//							isLock = true;
+				//						}
+				//					} else {
+				//						return;
+				//					}
+				//				}
+
+				channelContext.stat.timeClosed = SystemTimer.currTime;
+				if (channelContext.tioConfig.getAioListener() != null) {
+					try {
+						channelContext.tioConfig.getAioListener().onBeforeClose(channelContext, throwable, remark, isNeedRemove);
+					} catch (Throwable e) {
+						log.error(e.toString(), e);
+					}
+				}
+
+				try {
+					//					channelContext.traceClient(ChannelAction.UNCONNECT, null, null);
+
+					if (channelContext.isClosed && !isNeedRemove) {
+						log.debug("{}, {}已经关闭,备注:{},异常:{}", channelContext.tioConfig, channelContext, remark, throwable == null ? "无" : throwable.toString());
+						return;
+					}
+
+					if (channelContext.isRemoved) {
+						log.debug("{}, {}已经删除,备注:{},异常:{}", channelContext.tioConfig, channelContext, remark, throwable == null ? "无" : throwable.toString());
+						return;
+					}
+
+					//必须先取消任务再清空队列
+					channelContext.decodeRunnable.setCanceled(true);
+					channelContext.handlerRunnable.setCanceled(true);
+					channelContext.sendRunnable.setCanceled(true);
+
+					channelContext.decodeRunnable.clearMsgQueue();
+					channelContext.handlerRunnable.clearMsgQueue();
+					channelContext.sendRunnable.clearMsgQueue();
+
+					log.debug("{}, {} 准备关闭连接, isNeedRemove:{}, {}", channelContext.tioConfig, channelContext, isNeedRemove, remark);
+
+					try {
+						if (isNeedRemove) {
+							MaintainUtils.remove(channelContext);
+						} else {
+							ClientTioConfig clientTioConfig = (ClientTioConfig) channelContext.tioConfig;
+							clientTioConfig.closeds.add(channelContext);
+							clientTioConfig.connecteds.remove(channelContext);
+							MaintainUtils.close(channelContext);
+						}
+
+						channelContext.setRemoved(isNeedRemove);
+						if (channelContext.tioConfig.statOn) {
+							channelContext.tioConfig.groupStat.closed.incrementAndGet();
+						}
+						channelContext.stat.timeClosed = SystemTimer.currTime;
+						channelContext.setClosed(true);
+					} catch (Throwable e) {
+						log.error(e.toString(), e);
+					} finally {
+						if (!isNeedRemove && channelContext.isClosed && !channelContext.isServer()) //不删除且没有连接上,则加到重连队列中
+						{
+							ClientChannelContext clientChannelContext = (ClientChannelContext) channelContext;
+							ReconnConf.put(clientChannelContext);
+						}
+					}
+				} catch (Throwable e) {
+					log.error(throwable.toString(), e);
+				}
+				//				finally {
+				//					writeLock.unlock();
+				//				}
+			} finally {
+				channelContext.isWaitingClose = false;
+			}
+		}
+	}
+
+	@Override
+	public String logstr() {
+		return super.logstr();
+	}
+
+	/** The msg queue. */
+	private FullWaitQueue<ChannelContext> msgQueue = null;
+
+	@Override
+	public FullWaitQueue<ChannelContext> getMsgQueue() {
+		if (msgQueue == null) {
+			synchronized (this) {
+				if (msgQueue == null) {
+					msgQueue = new TioFullWaitQueue<ChannelContext>(Integer.getInteger("tio.fullqueue.capacity", null), false);
+				}
+			}
+		}
+		return msgQueue;
+	}
+}

+ 2 - 0
nb-common/ws-common/src/main/java/com/nb/common/websocket/ws/HeartBeatConfig.java

@@ -4,6 +4,7 @@ import com.nb.common.websocket.handler.WsHandler;
 import lombok.AllArgsConstructor;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.context.annotation.Configuration;
+import org.tio.core.task.CloseRunnable;
 import org.tio.websocket.starter.TioWebSocketServerBootstrap;
 
 import java.util.List;
@@ -24,5 +25,6 @@ public class HeartBeatConfig implements CommandLineRunner {
     @Override
     public void run(String... args) {
         serverTioConfig.getServerTioConfig().setServerAioListener(new DefaultWsServerAioListener(wsHandlers));
+//        serverTioConfig.getServerTioConfig().closeRunnable=new CloseRunnable(serverTioConfig.getServerTioConfig().tioExecutor);
     }
 }

+ 7 - 3
nb-core/pom.xml

@@ -75,8 +75,12 @@
             <artifactId>easyexcel</artifactId>
         </dependency>
         <dependency>
-        <groupId>org.hibernate.validator</groupId>
-        <artifactId>hibernate-validator</artifactId>
-    </dependency>
+            <groupId>org.hibernate.validator</groupId>
+            <artifactId>hibernate-validator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.belerweb</groupId>
+            <artifactId>pinyin4j</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 1 - 1
nb-core/src/main/java/com/nb/core/Constants.java

@@ -15,7 +15,7 @@ public class Constants {
     /**
      * sa token, token session, user key
      */
-    public static final String LOGIN_USER_KEY = "user";
+    public static final String LOGIN_USER_KEY = "loginUser";
 
     public static final String HOSPITAL_ID = "hospital_id";
 

+ 1 - 0
nb-core/src/main/java/com/nb/core/GlobalExceptionHandler.java

@@ -53,6 +53,7 @@ public class GlobalExceptionHandler {
 
     @ExceptionHandler(RuntimeException.class)
     public R handleRuntimeException(RuntimeException e) {
+        log.error("runtimeException:",e);
         return R.fail(e.getLocalizedMessage());
     }
 

+ 44 - 0
nb-core/src/main/java/com/nb/core/annotation/Phone.java

@@ -0,0 +1,44 @@
+package com.nb.core.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import javax.validation.ReportAsSingleViolation;
+import javax.validation.constraints.Null;
+import javax.validation.constraints.Pattern;
+
+import org.hibernate.validator.constraints.CompositionType;
+import org.hibernate.validator.constraints.ConstraintComposition;
+import org.hibernate.validator.constraints.Length;
+
+/**
+ * 验证手机号,空和正确的手机号都能验证通过<br/>
+ * 正确的手机号由11位数字组成,第一位为1
+ * 第二位为 3、4、5、7、8
+ * 
+ */
+@ConstraintComposition(CompositionType.OR)
+@Pattern(regexp = "1[3|4|5|6|7|8|9][0-9]\\d{8}")
+@Null
+@Length(min = 0, max = 0)
+@Documented
+@Constraint(validatedBy = {})
+@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER })
+@Retention(RUNTIME)
+@ReportAsSingleViolation
+public @interface Phone {
+    String message() default "手机号校验错误";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 9 - 4
nb-core/src/main/java/com/nb/core/cache/manager/ClusterStorageManager.java

@@ -1,10 +1,8 @@
 package com.nb.core.cache.manager;
 
 import com.nb.core.AppProperties;
-import com.nb.core.Constants;
 import com.nb.core.cache.ClusterConfigStorage;
 import com.nb.core.cache.ConfigStorage;
-import org.redisson.api.RedissonClient;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
@@ -28,7 +26,7 @@ public class ClusterStorageManager implements ConfigStorageManager {
 
     private final AppProperties appProperties;
 
-    private Map<String,ClusterConfigStorage > storageMap=new ConcurrentHashMap<>();
+    private Map<String,ConfigStorage > storageMap=new ConcurrentHashMap<>();
 
     public ClusterStorageManager(RedisTemplate redisTemplate,AppProperties appProperties) {
         this.redisTemplate=redisTemplate;
@@ -37,7 +35,14 @@ public class ClusterStorageManager implements ConfigStorageManager {
 
     @Override
     public ConfigStorage getStorage(String id) {
-        return  storageMap.computeIfAbsent(id, __ -> new ClusterConfigStorage(redisTemplate, appProperties.getCachePrefix()+":"+id));
+        return  getStorage(id,false);
+    }
+
+    @Override
+    public ConfigStorage getStorage(String id, boolean ignorePrefix) {
+        return ignorePrefix?
+                storageMap.computeIfAbsent(id, __ -> new ClusterConfigStorage(redisTemplate, id)):
+                storageMap.computeIfAbsent(id, __ -> new ClusterConfigStorage(redisTemplate, appProperties.getCachePrefix()+":"+id));
     }
 
 }

+ 1 - 0
nb-core/src/main/java/com/nb/core/cache/manager/ConfigStorageManager.java

@@ -14,4 +14,5 @@ public interface ConfigStorageManager {
 
     ConfigStorage getStorage(String id);
 
+    ConfigStorage getStorage(String id,boolean ignorePrefix);
 }

+ 2 - 1
nb-core/src/main/java/com/nb/core/doc/SwaggerConfig.java

@@ -56,7 +56,8 @@ public class SwaggerConfig implements WebMvcConfigurer {
 
 
     public List<SecurityScheme> security() {
-        return Arrays.asList(new ApiKey("BASE_TOKEN", "Authorization",   In.HEADER.toValue()),
+        return Arrays.asList(new ApiKey("授权码", "Authorization",   In.HEADER.toValue()),
+                new ApiKey("权限体系,从login接口中得到LoginType中获取","Login-Type",In.HEADER.toValue()),
                 new ApiKey("租户id", "Tenant-Id",   In.HEADER.toValue()));
     }
 

+ 4 - 0
nb-core/src/main/java/com/nb/core/entity/GenericEntity.java

@@ -25,15 +25,19 @@ public abstract class  GenericEntity<PK> implements Entity,RecordModifierEntity,
     private PK id;
 
     @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建时间",readOnly = true)
     private Date createTime;
 
     @TableField(fill = FieldFill.INSERT)
+    @ApiModelProperty(value = "创建人",readOnly = true)
     private String createBy;
 
     @TableField(fill = FieldFill.INSERT_UPDATE)
+    @ApiModelProperty(value = "修改人",readOnly = true)
     private String updateBy;
 
     @TableField(fill = FieldFill.INSERT_UPDATE)
+    @ApiModelProperty(value = "修改时间",readOnly = true)
     private Date updateTime;
 
     /* 分组校验 */

+ 2 - 0
nb-core/src/main/java/com/nb/core/entity/QueryParamEntity.java

@@ -3,6 +3,7 @@ package com.nb.core.entity;
 import cn.hutool.db.sql.Order;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.nb.core.entity.param.Term;
+import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
@@ -14,6 +15,7 @@ import java.util.Set;
  * @Description 仅用来进行简单的查询,若需要构建复杂查询语句,请直接使用mp函数方法进行构建
  **/
 @Data
+@ApiModel("查询参数")
 public class QueryParamEntity<T> {
 
     private static final long serialVersionUID = 8097500947924037523L;

+ 2 - 0
nb-core/src/main/java/com/nb/core/entity/param/Term.java

@@ -2,6 +2,7 @@ package com.nb.core.entity.param;
 
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Getter;
 import lombok.Setter;
@@ -13,6 +14,7 @@ import java.util.List;
  */
 @Getter
 @Setter
+@ApiModel("查询条件")
 public class Term implements Cloneable, Serializable {
     private static final long serialVersionUID = 1L;
     /**

+ 0 - 38
nb-core/src/main/java/com/nb/core/enums/GrantTypeEnum.java

@@ -1,38 +0,0 @@
-package com.nb.core.enums;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-/**
- * 授权类型
- *
- * @author Kevin
- */
-@Getter
-@AllArgsConstructor
-public enum GrantTypeEnum {
-
-    /**
-     * 用户名密码模式
-     */
-    USERNAME_PASSWORD("1", "用户名密码模式"),
-    /**
-     * 手机号短信模式
-     */
-    MOBILE_CODE("2", "手机号短信模式"),
-
-    /**
-     * appkey、appSecret模式
-     */
-    APPKEY_APPSECRET("3", "第三方应用登录"),;
-
-    private String code;
-    private String desc;
-
-
-    public static Boolean contains(String code) {
-        return Arrays.stream(GrantTypeEnum.values()).anyMatch(temp -> temp.getCode().equals(code));
-    }
-}

+ 0 - 1
nb-core/src/main/java/com/nb/core/enums/SexEnum.java

@@ -31,5 +31,4 @@ public enum SexEnum  implements IEnum<Integer> {
 
     private Integer value;
     private String text;
-
 }

+ 8 - 5
nb-core/src/main/java/com/nb/core/enums/StatusEnum.java

@@ -1,5 +1,7 @@
 package com.nb.core.enums;
 
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
@@ -10,17 +12,18 @@ import lombok.Getter;
  */
 @Getter
 @AllArgsConstructor
-public enum StatusEnum {
+@JsonFormat(shape = JsonFormat.Shape.OBJECT)
+public enum StatusEnum implements IEnum<Integer> {
     /**
      * 正常
      */
-    YES("0", "正常"),
+    YES(0, "正常"),
     /**
      * 停用
      */
-    NO("1", "停用");
+    NO(1, "停用");
 
-    private String code;
-    private String desc;
+    private Integer value;
+    private String text;
 
 }

+ 5 - 1
nb-core/src/main/java/com/nb/core/enums/UserPlatformEnum.java

@@ -24,7 +24,11 @@ public enum UserPlatformEnum {
     /**
      * 第三方应用
      */
-    OPENAPI("API", "第三方应用"),;
+    OPENAPI("API", "第三方应用"),
+
+    APP_DOCTOR("DOCTOR","app医生端"),
+
+    APP_ASSIST("ASSIST","疼痛小助手");
 
     private String code;
     private String desc;

+ 3 - 0
nb-core/src/main/java/com/nb/core/result/R.java

@@ -1,5 +1,7 @@
 package com.nb.core.result;
 
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
@@ -18,6 +20,7 @@ import java.io.Serializable;
 @Setter
 @ToString
 @NoArgsConstructor
+@ApiModel("通用返回结果")
 public class R<T> implements Serializable {
 
     private static final long serialVersionUID = 1L;

+ 41 - 0
nb-im/pom.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>nb-root</artifactId>
+        <groupId>com.tuoren</groupId>
+        <version>1.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>nb-im</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>delay-queue-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>web-service-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.tuoren</groupId>
+            <artifactId>ws-common</artifactId>
+        </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>
+
+</project>

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

@@ -0,0 +1,16 @@
+package com.nb.im;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ConfigAutoConfiguration.java
+ * @Description TODO
+ * @createTime 2022年08月02日 10:03:00
+ */
+@Configuration
+@ComponentScan("com.nb.im")
+public class ImAutoConfiguration {
+}

+ 15 - 0
nb-im/src/main/java/com/nb/im/constant/ImRoomConstant.java

@@ -0,0 +1,15 @@
+package com.nb.im.constant;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ImRoomConstant.java
+ * @Description TODO
+ * @createTime 2022年09月08日 17:20:00
+ */
+public class ImRoomConstant {
+    /**
+     * 聊天室自动结束
+     */
+    public static final String AUTO_FINISH_DELAY="auto_finish";
+}

+ 182 - 0
nb-im/src/main/java/com/nb/im/controller/ImRoomController.java

@@ -0,0 +1,182 @@
+package com.nb.im.controller;
+
+import cn.dev33.satoken.stp.StpLogic;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.Mapper;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.nb.auth.utils.SecurityUtil;
+import com.nb.common.crud.BaseService;
+import com.nb.common.crud.controller.BaseSaveController;
+import com.nb.core.exception.CustomException;
+import com.nb.core.result.R;
+import com.nb.im.controller.vo.RemarkReadVo;
+import com.nb.im.controller.vo.UnreadVo;
+import com.nb.im.entity.ImMsgEntity;
+import com.nb.im.entity.ImRoomEntity;
+import com.nb.im.room.ImRoomOperator;
+import com.nb.im.room.ImRoomOperatorManager;
+import com.nb.im.service.LocalImMsgService;
+import com.nb.im.service.dto.ImRoomDetailDto;
+import com.nb.im.service.dto.ImRoomDto;
+import com.nb.im.enums.ImStatusEnum;
+import com.nb.im.enums.SponsorEnum;
+import com.nb.im.service.LocalImRoomService;
+import com.nb.im.service.dto.ImRoomQuery;
+import com.nb.im.service.dto.ImRoomResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @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 ImRoomController implements BaseSaveController<ImRoomEntity,String> {
+    private final LocalImRoomService imRoomService;
+    private final ImRoomOperatorManager roomOperatorManager;
+    private final LocalImMsgService msgService;
+    @Override
+    public BaseService<? extends Mapper<ImRoomEntity>, ImRoomEntity, String> getService() {
+        return imRoomService;
+    }
+
+
+    @ApiOperation(value = "查询并自动创建与病人看护人的聊天室",notes = "若存在,则返回聊天室信息,若不存在,则返回空")
+    @PostMapping("/look")
+    public R<ImRoomEntity> getChatRoom(@RequestBody@Validated ImRoomDto source){
+        if(Boolean.TRUE.equals(source.isAutoCreate())&&StrUtil.isEmpty(source.getDoctorNickname())){
+            throw new CustomException("医生昵称不可为空");
+        }
+        ImRoomEntity chatRoom = imRoomService.getOne(new QueryWrapper<ImRoomEntity>()
+                .lambda()
+                .eq(ImRoomEntity::getAssistId, source.getAssistId())
+                .eq(ImRoomEntity::getDoctorId, source.getDoctorId())
+                .eq(ImRoomEntity::getPatientId, source.getPatientId())
+                .nested(i->i.eq(ImRoomEntity::getStatus, ImStatusEnum.WAITING)
+                        .or()
+                        .eq(ImRoomEntity::getStatus,ImStatusEnum.SUCCESS))
+                .last("limit 1"));
+        if(chatRoom==null&&source.isAutoCreate()){
+            source.setTotalCount(0);
+            source.setSponsorType(SponsorEnum.doctor);
+            source.setStatus(ImStatusEnum.SUCCESS);
+            chatRoom=imRoomService.createChatRoom(source);
+        }else if(chatRoom!=null&&ImStatusEnum.WAITING.equals(chatRoom.getStatus())&&source.isAutoCreate()){
+            imRoomService.successChatRoom(chatRoom.getId(),source.getDoctorId());
+        }
+        return R.success(chatRoom);
+    }
+
+
+    @PostMapping("/finished/{roomId}")
+    @ApiOperation(value = "结束咨询")
+    public R<Boolean> finished(@PathVariable String roomId){
+        return R.success(imRoomService.finished(roomId));
+    }
+
+    @PostMapping("/no_page")
+    @ApiOperation(value = "聊天室列表查询",notes = "走非条件查询,条件查询请于安卓端自行实现")
+    public R<List<ImRoomResult>> list(@RequestBody @Validated ImRoomQuery query){
+        List<ImRoomResult> result= imRoomService.getBaseMapper().queryPageNoneMsgBlurry(query);
+        String userId = String.valueOf(SecurityUtil.getId());
+        result.forEach(room->
+                room.setUnreadCount( roomOperatorManager.getRoomOperator(room.getId())
+                        .unReadCount(userId))
+        );
+        return R.success(result);
+    }
+
+    @GetMapping("/view/{id}")
+    @ApiOperation(value = "根据id查询聊天室")
+    public R<ImRoomDetailDto> getById(@PathVariable("id") String id){
+        ImRoomDetailDto result= BeanUtil.toBean(imRoomService.getById(id),ImRoomDetailDto.class);
+        if(result!=null){
+            result.setUnreadCount( roomOperatorManager.getRoomOperator(result.getId())
+                    .unReadCount( String.valueOf(SecurityUtil.getId())));
+        }
+        return R.success(result);
+    }
+
+
+    @PostMapping("/no_page/unread/{roomId}")
+    @ApiOperation(value = "获取所有未读消息(需手动调用接口设置为已读)")
+    public R<List<ImMsgEntity>> unReadMsg(@PathVariable("roomId") String roomId){
+        String userId = String.valueOf(SecurityUtil.getId());
+        ImRoomOperator roomOperator = roomOperatorManager.getRoomOperator(roomId);
+        Set<String> keySet = roomOperator.allUnreadMsgKey(userId);
+        if(CollectionUtil.isEmpty(keySet)){
+            return R.success(new ArrayList<>());
+        }
+        List<ImMsgEntity> result = msgService.list(new QueryWrapper<ImMsgEntity>()
+                .lambda()
+                .eq(ImMsgEntity::getRoomId,roomId)
+                .in(ImMsgEntity::getKey,keySet));
+        return R.success(result);
+    }
+
+    @PostMapping("/read")
+    @ApiOperation("标记信息为已读信息")
+    public R<Boolean> readMsg(@RequestBody RemarkReadVo vo){
+        roomOperatorManager.getRoomOperator(vo.getRoomId())
+                .readUnreadMsg(vo.getUerId(),vo.getKey());
+        return R.success(true);
+    }
+
+    @PostMapping("/read/all")
+    @ApiOperation("标记所有信息为已读信息")
+    public R<Boolean> readAll(@RequestBody RemarkReadVo vo){
+        roomOperatorManager.getRoomOperator(vo.getRoomId())
+                .readUnreadMsgAll(vo.getUerId());
+        return R.success(true);
+    }
+
+    @PostMapping("/read/unread")
+    @ApiOperation("获取未读消息总数量")
+    public R<Long> unReadCount(@RequestBody UnreadVo vo){
+        if (StrUtil.isEmpty(vo.getAssistId()) && StrUtil.isEmpty(vo.getDoctorId())) {
+            throw new CustomException("看护人id和医生id不能同时为空");
+        }
+        String userId=StrUtil.isEmpty(vo.getAssistId())?vo.getDoctorId():vo.getAssistId();
+        AtomicLong result=new AtomicLong(0);
+        List<ImRoomEntity> rooms = imRoomService.list(new QueryWrapper<ImRoomEntity>()
+                .lambda()
+                .select(ImRoomEntity::getId)
+                .eq(StrUtil.isNotEmpty(vo.getAssistId()), ImRoomEntity::getAssistId, vo.getAssistId())
+                .eq(StrUtil.isNotEmpty(vo.getDoctorId()), ImRoomEntity::getDoctorId, vo.getDoctorId())
+                .eq(ImRoomEntity::getStatus,ImStatusEnum.SUCCESS)
+        );
+        if(CollectionUtil.isNotEmpty(rooms)){
+            rooms.stream()
+                    .map(ImRoomEntity::getId).map(roomOperatorManager::getRoomOperator)
+                    .forEach(operator->{
+                        result.addAndGet(operator.unReadCount(userId));
+                    });
+        }
+        return R.success(result.get());
+    }
+
+    @Override
+    public boolean isAuth() {
+        return false;
+    }
+
+    @Override
+    public String getPermissionPrefix() {
+        return null;
+    }
+}

+ 50 - 0
nb-im/src/main/java/com/nb/im/controller/ImRoomMsgController.java

@@ -0,0 +1,50 @@
+package com.nb.im.controller;
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.nb.core.result.R;
+import com.nb.im.controller.vo.MsgExtendVo;
+import com.nb.im.entity.ImMsgEntity;
+import com.nb.im.service.LocalImMsgService;
+import com.nb.im.service.dto.ImRoomMsgQuery;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+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/msg")
+@Api(tags = "聊天室消息")
+public class ImRoomMsgController  {
+    private final LocalImMsgService chatRoomMsgService;
+
+
+    @PostMapping("/roam")
+    @ApiOperation("获取漫游消息(默认获取最新500条数据)")
+    public R list(@RequestBody@Validated ImRoomMsgQuery query){
+        return R.success(chatRoomMsgService.list(new QueryWrapper<ImMsgEntity>()
+                .lambda()
+                .eq(ImMsgEntity::getRoomId,query.getRoomId())));
+    }
+
+    @PostMapping("/edit")
+    @ApiOperation("修改消息扩展字段")
+    public R<Boolean> edit(@RequestBody@Validated MsgExtendVo vo){
+        return R.success(chatRoomMsgService.update(new UpdateWrapper<ImMsgEntity>()
+                .lambda()
+                .eq(ImMsgEntity::getId,vo.getId())
+                .set(ImMsgEntity::getExtend, JSONUtil.toJsonStr(vo.getExtend()))));
+    }
+}

+ 16 - 0
nb-im/src/main/java/com/nb/im/controller/vo/MsgExtendVo.java

@@ -0,0 +1,16 @@
+package com.nb.im.controller.vo;
+
+import lombok.Data;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName MsgExtentVo.java
+ * @Description TODO
+ * @createTime 2022年09月06日 08:14:00
+ */
+@Data
+public class MsgExtendVo {
+    private String id;
+    private Object extend;
+}

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

@@ -0,0 +1,31 @@
+package com.nb.im.controller.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.*;
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName RemarkReadVo.java
+ * @Description TODO
+ * @createTime 2022年08月19日 10:24:00
+ */
+@Data
+@ApiModel("标记已读信息")
+public class RemarkReadVo {
+    @ApiModelProperty("聊天室id")
+    @NotNull(message = "聊天室id不能为空")
+    private String roomId;
+
+    @ApiModelProperty("已读消息key")
+    @NotNull(message = "消息key不能为空")
+    private List<String> key;
+
+
+    @ApiModelProperty("用户id")
+    @NotNull(message = "用户id不能为空")
+    private String uerId;
+}

+ 24 - 0
nb-im/src/main/java/com/nb/im/controller/vo/UnreadVo.java

@@ -0,0 +1,24 @@
+package com.nb.im.controller.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName UnreadVo.java
+ * @Description TODO
+ * @createTime 2022年08月19日 10:13:00
+ */
+@Data
+@ApiModel("未读消息数量")
+public class UnreadVo {
+
+    @ApiModelProperty(value = "看护人id")
+    private String assistId;
+
+    @ApiModelProperty(value = "医生id")
+    private String doctorId;
+
+}

+ 79 - 0
nb-im/src/main/java/com/nb/im/delay/ImRoomAutoFinishDelayHandler.java

@@ -0,0 +1,79 @@
+package com.nb.im.delay;
+
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.json.JSONUtil;
+import com.nb.app.msg.enums.MsgEnum;
+import com.nb.common.queue.delay.handler.DelayMessageHandler;
+import com.nb.common.queue.delay.message.DelayMessage;
+import com.nb.im.entity.ImRoomEntity;
+import com.nb.im.enums.ImMsgType;
+import com.nb.im.enums.ImStatusEnum;
+import com.nb.im.room.ImRoomOperator;
+import com.nb.im.room.ImRoomOperatorManager;
+import com.nb.im.service.LocalImRoomService;
+import com.nb.im.utils.ImUtils;
+import com.nb.im.ws.PubMsgInfo;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+import static com.nb.im.constant.ImRoomConstant.AUTO_FINISH_DELAY;
+
+/**
+ * @author lifang
+ * @version 1.0.0
+ * @ClassName ImRoomAutoFinishDelayHandler.java
+ * @Description TODO
+ * @createTime 2022年09月08日 17:21:00
+ */
+@Component
+@AllArgsConstructor
+@Slf4j
+public class ImRoomAutoFinishDelayHandler implements DelayMessageHandler {
+    private final LocalImRoomService roomService;
+    private final ImRoomOperatorManager roomOperatorManager;
+    private final ImUtils imUtils;
+    @Override
+    public String getId() {
+        return AUTO_FINISH_DELAY;
+    }
+
+    @Override
+    public String description() {
+        return "聊天室超过24小时自动结束";
+    }
+
+    @Override
+    public void handle(DelayMessage message) {
+        log.info("尝试处理聊天室超过24小时自动关闭,{}", JSONUtil.toJsonStr(message));
+        String roomId = message.getBody().asString();
+        ImRoomEntity room = roomService.getById(roomId);
+        if(ImStatusEnum.SUCCESS.equals(room.getStatus())){
+            Date successTime = room.getSuccessTime();
+            if(successTime!=null){
+                if (DateUtil.between(successTime,new Date(), DateUnit.SECOND)>=60*24) {
+                    room.setStatus(ImStatusEnum.AUTO_LIST);
+                    if (roomService.updateById(room)) {
+                        ImRoomOperator roomOperator = roomOperatorManager.getRoomOperator(roomId);
+                        imUtils.send(room.getId(), PubMsgInfo.builder()
+                                .senderId("1")
+                                .msgType(ImMsgType.hyperlinks)
+                                .roomId(room.getId())
+                                .operationType(MsgEnum.AUTO_FINISHED)
+                                .build()
+                        );
+                        roomOperator.close();
+                        log.info("聊天室{}24小时自动关闭",roomId);
+                    }
+                }
+            }else {
+                log.error("聊天室{}成功建立但是缺少successTime,无法24小时自动关闭");
+            }
+        }else {
+            log.info("聊天室未处于成功建立状态,处理聊天室超过24小时自动关闭失败,{}", roomId);
+        }
+    }
+}

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor