Ver Fonte

企微聊天代码调整

wangxy há 2 dias atrás
pai
commit
53caf741c6
100 ficheiros alterados com 2456 adições e 1659 exclusões
  1. 11 46
      fs-ad-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  2. 11 25
      fs-ad-api/src/main/java/com/fs/framework/config/RedisConfig.java
  3. 3 0
      fs-admin/src/main/java/com/fs/his/controller/FsUserOperationLogController.java
  4. 11 45
      fs-common-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  5. 12 25
      fs-common-api/src/main/java/com/fs/framework/config/RedisConfig.java
  6. 11 45
      fs-company-app/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java
  7. 52 41
      fs-company-app/src/main/java/com/fs/core/config/RedisConfig.java
  8. 4 0
      fs-company/pom.xml
  9. 3 0
      fs-company/src/main/java/com/fs/company/controller/course/FsUserOperationLogController.java
  10. 100 0
      fs-company/src/main/java/com/fs/company/controller/crm/ComprehensiveStatisticsController.java
  11. 5 4
      fs-company/src/main/java/com/fs/framework/config/DataSourceConfig.java
  12. 12 47
      fs-company/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  13. 0 20
      fs-company/src/main/java/com/fs/framework/config/RedisConfig.java
  14. 1 1
      fs-company/src/main/resources/application.yml
  15. 11 45
      fs-doctor-app/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  16. 10 27
      fs-doctor-app/src/main/java/com/fs/framework/config/RedisConfig.java
  17. 6 2
      fs-framework/src/main/java/com/fs/framework/config/DataSourceConfig.java
  18. 11 45
      fs-framework/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  19. 10 27
      fs-framework/src/main/java/com/fs/framework/config/RedisConfig.java
  20. 116 0
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  21. 11 45
      fs-ipad-task/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  22. 12 29
      fs-ipad-task/src/main/java/com/fs/framework/config/RedisConfig.java
  23. 11 45
      fs-live-app/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  24. 11 26
      fs-live-app/src/main/java/com/fs/framework/config/RedisConfig.java
  25. 144 62
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  26. 85 45
      fs-qw-api-msg/src/main/java/com/fs/app/socket/QwImSocket.java
  27. 14 2
      fs-qw-api-msg/src/main/java/com/fs/app/socket/configurator/QwImConfigurator.java
  28. 18 0
      fs-qw-api-msg/src/main/java/com/fs/app/socket/task/QwImSessionCleaner.java
  29. 5 4
      fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java
  30. 11 45
      fs-qw-api-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  31. 1 10
      fs-qw-api-msg/src/main/java/com/fs/framework/config/RedisConfig.java
  32. 14 0
      fs-qw-api-msg/src/main/java/com/fs/framework/service/TokenService.java
  33. 1 1
      fs-qw-api-msg/src/main/resources/application.yml
  34. 11 45
      fs-qw-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  35. 19 35
      fs-qw-api/src/main/java/com/fs/framework/config/RedisConfig.java
  36. 11 45
      fs-qw-mq/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  37. 11 27
      fs-qw-mq/src/main/java/com/fs/framework/config/RedisConfig.java
  38. 11 45
      fs-qw-task/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  39. 11 28
      fs-qw-task/src/main/java/com/fs/framework/config/RedisConfig.java
  40. 11 45
      fs-qw-voice/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  41. 11 25
      fs-qw-voice/src/main/java/com/fs/framework/config/RedisConfig.java
  42. 11 45
      fs-qwhook-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  43. 12 28
      fs-qwhook-msg/src/main/java/com/fs/framework/config/RedisConfig.java
  44. 11 45
      fs-qwhook-sop/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  45. 11 26
      fs-qwhook-sop/src/main/java/com/fs/framework/config/RedisConfig.java
  46. 11 45
      fs-qwhook/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  47. 11 28
      fs-qwhook/src/main/java/com/fs/framework/config/RedisConfig.java
  48. 11 45
      fs-redis/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  49. 30 27
      fs-redis/src/main/java/com/fs/framework/config/RedisConfig.java
  50. 11 45
      fs-repeat-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  51. 19 33
      fs-repeat-api/src/main/java/com/fs/framework/config/RedisConfig.java
  52. 4 0
      fs-service/pom.xml
  53. 27 0
      fs-service/src/main/java/com/fs/company/service/ICrmStatisticManageService.java
  54. 46 0
      fs-service/src/main/java/com/fs/company/service/impl/CrmStatisticManageServiceImpl.java
  55. 5 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  56. 5 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  57. 8 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  58. 41 18
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  59. 3 0
      fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtApiResponse.java
  60. 3 0
      fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtBaseResponseDTO.java
  61. 3 0
      fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtStockRespDTO.java
  62. 3 0
      fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtTradeQueryResponse.java
  63. 2 7
      fs-service/src/main/java/com/fs/erp/service/impl/WdtErpGoodsServiceImpl.java
  64. 6 20
      fs-service/src/main/java/com/fs/erp/service/impl/WdtErpOrderServiceImpl.java
  65. 24 8
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  66. 474 63
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  67. 8 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserOperationLogMapper.java
  68. 5 0
      fs-service/src/main/java/com/fs/his/service/IFsUserOperationLogService.java
  69. 18 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserOperationLogServiceImpl.java
  70. 1 0
      fs-service/src/main/java/com/fs/ipad/vo/BaseVo.java
  71. 5 0
      fs-service/src/main/java/com/fs/qw/domain/QwMsg.java
  72. 17 0
      fs-service/src/main/java/com/fs/qw/domain/QwSession.java
  73. 23 0
      fs-service/src/main/java/com/fs/qw/dto/QwImUserDTO.java
  74. 1 0
      fs-service/src/main/java/com/fs/qw/enums/MsgType.java
  75. 110 9
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  76. 6 0
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java
  77. 16 2
      fs-service/src/main/java/com/fs/qw/mapper/QwMsgMapper.java
  78. 22 5
      fs-service/src/main/java/com/fs/qw/mapper/QwSessionMapper.java
  79. 9 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java
  80. 10 4
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  81. 9 0
      fs-service/src/main/java/com/fs/qw/param/QwMandatoryRegistrParam.java
  82. 3 1
      fs-service/src/main/java/com/fs/qw/param/QwMsgSendParam.java
  83. 1 1
      fs-service/src/main/java/com/fs/qw/param/QwSessionParam.java
  84. 17 3
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  85. 5 7
      fs-service/src/main/java/com/fs/qw/service/IQwMsgService.java
  86. 10 0
      fs-service/src/main/java/com/fs/qw/service/IQwTagService.java
  87. 5 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  88. 65 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  89. 238 88
      fs-service/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java
  90. 16 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagServiceImpl.java
  91. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  92. 1 2
      fs-service/src/main/java/com/fs/qw/vo/QwMessageListVO.java
  93. 52 0
      fs-service/src/main/java/com/fs/statis/dto/ComprehensiveStatisticsDTO.java
  94. 21 0
      fs-service/src/main/java/com/fs/statis/param/ComprehensiveStatisticsParam.java
  95. 15 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxRoomHeaderDTO.java
  96. 15 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxRoomHeaderResp.java
  97. 46 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxSendTextAtMsgTwoDTO.java
  98. 20 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxWorkChatId2RoomIdDTO.java
  99. 15 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxWorkChatId2RoomIdResp.java
  100. 20 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxWorkRoomId2ChatIdDTO.java

+ 11 - 46
fs-ad-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,10 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
-
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +13,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
-
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
+    private final Class<T> clazz;
 
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return com.alibaba.fastjson2.JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 11 - 25
fs-ad-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,36 +41,37 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -94,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);

+ 3 - 0
fs-admin/src/main/java/com/fs/his/controller/FsUserOperationLogController.java

@@ -1,8 +1,10 @@
 package com.fs.his.controller;
 
+import com.fs.common.annotation.DataSource;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.DataSourceType;
 import com.fs.his.domain.FsUserOperationLog;
 import com.fs.his.service.IFsUserOperationLogService;
 import com.fs.his.vo.FsUserOperationLogParamVo;
@@ -29,6 +31,7 @@ public class FsUserOperationLogController extends BaseController {
     private IFsUserOperationLogService fsUserOperationLogService;
 
 //    @PreAuthorize("@ss.hasPermi('his:userOperationLog:list')")
+    @DataSource(DataSourceType.SHARDING)
     @GetMapping("/list")
     public TableDataInfo list(FsUserOperationLog fsUserOperationLog)
     {

+ 11 - 45
fs-common-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 12 - 25
fs-common-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,40 +41,42 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
@@ -93,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);

+ 11 - 45
fs-company-app/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.core.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 52 - 41
fs-company-app/src/main/java/com/fs/core/config/RedisConfig.java

@@ -1,14 +1,12 @@
 package com.fs.core.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
@@ -32,37 +30,13 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
-        serializer.setObjectMapper(mapper);
-
-        template.setValueSerializer(serializer);
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // Hash的key也采用StringRedisSerializer的序列化方式 这个才是redis的hash值的序列化方式 一直都没有序列化进去
-        template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(serializer);
-        template.afterPropertiesSet();
-
-        // Hash的key也采用StringRedisSerializer的序列化方式 这个才是redis的hash值的序列化方式 一直都没有序列化进去
-        template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(serializer);
-        return template;
-    }
-    @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
-        template.setConnectionFactory(connectionFactory);
-
-        // 使用StringRedisSerializer来序列化和反序列化redis的key值
-        template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setValueSerializer(serializer);
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(serializer);
 
         template.afterPropertiesSet();
         return template;
@@ -86,21 +60,18 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -114,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -130,4 +96,49 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+    @Bean
+    public DefaultRedisScript<Long> limitScript()
+    {
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+        redisScript.setScriptText(limitScriptText());
+        redisScript.setResultType(Long.class);
+        return redisScript;
+    }
+
+    /**
+     * 限流脚本
+     */
+    private String limitScriptText()
+    {
+        return "local key = KEYS[1]\n" +
+                "local count = tonumber(ARGV[1])\n" +
+                "local time = tonumber(ARGV[2])\n" +
+                "local current = redis.call('get', key);\n" +
+                "if current and tonumber(current) > count then\n" +
+                "    return current;\n" +
+                "end\n" +
+                "current = redis.call('incr', key)\n" +
+                "if tonumber(current) == 1 then\n" +
+                "    redis.call('expire', key, time)\n" +
+                "end\n" +
+                "return current;";
+    }
 }

+ 4 - 0
fs-company/pom.xml

@@ -112,6 +112,10 @@
             <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
             <version>3.1.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 3 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsUserOperationLogController.java

@@ -1,8 +1,10 @@
 package com.fs.company.controller.course;
 
+import com.fs.common.annotation.DataSource;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.DataSourceType;
 import com.fs.his.domain.FsUserOperationLog;
 import com.fs.his.service.IFsUserOperationLogService;
 import com.fs.his.vo.FsUserOperationLogVo;
@@ -26,6 +28,7 @@ public class FsUserOperationLogController extends BaseController {
     private IFsUserOperationLogService fsUserOperationLogService;
 
     //@PreAuthorize("@ss.hasPermi('his:userOperationLog:list')")
+    @DataSource(DataSourceType.SHARDING)
     @GetMapping("/list")
     public TableDataInfo list(FsUserOperationLog fsUserOperationLog)
     {

+ 100 - 0
fs-company/src/main/java/com/fs/company/controller/crm/ComprehensiveStatisticsController.java

@@ -0,0 +1,100 @@
+package com.fs.company.controller.crm;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.service.ICrmStatisticManageService;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.security.SecurityUtils;
+import com.fs.statis.dto.ComprehensiveStatisticsDTO;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/13 下午3:19
+ */
+@RestController
+@RequestMapping("/crm/ComprehensiveStatistics")
+public class ComprehensiveStatisticsController extends BaseController {
+
+    @Resource
+    private ICrmStatisticManageService iCrmStatisticManageService;
+
+    /**
+     * 获取综合统计信息
+     * @param param
+     * @return
+     */
+    @PostMapping("/statisticMainN")
+    public R statisticMainN(@RequestBody ComprehensiveStatisticsParam param) {
+        Assert.notNull(param.getDimension(), "请选择统计维度");
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+        param.setTimeGroupFlag(false);
+        return R.ok().put("data", iCrmStatisticManageService.statisticMain(param));
+    }
+
+    /**
+     * 日综合获取统计信息(需要按时间分组)
+     * @param param
+     * @return
+     */
+    @PostMapping("/statisticMain")
+    public R statisticMain(@RequestBody ComprehensiveStatisticsParam param) {
+        Assert.notNull(param.getDimension(), "请选择统计维度");
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        Long companyId = loginUser.getCompany().getCompanyId();
+        param.setCompanyId(companyId);
+        param.setTimeGroupFlag(true);
+        return R.ok().put("data", iCrmStatisticManageService.statisticMain(param));
+    }
+
+    /**
+     * 获取公司下拉
+     * @return
+     */
+    @GetMapping("/getSearchCompanyInfo")
+    public R getSearchCompanyInfo(){
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        Long companyId = loginUser.getCompany().getCompanyId();
+        return R.ok().put("data", iCrmStatisticManageService.getSearchCompanyInfo(companyId));
+    }
+
+    /**
+     * 根据公司id获取部门下拉
+     * @return
+     */
+    @GetMapping("/getSearchDeptInfo")
+    public R getSearchDeptInfo(@RequestParam("id") Long id){
+        return R.ok().put("data", iCrmStatisticManageService.getSearchDeptInfo(id));
+    }
+
+    /**
+     * 根据部门id获取用户下拉信息
+     * @return
+     */
+    @GetMapping("/getSearchUserInfo")
+    public R getSearchUserInfo(@RequestParam("id") Long id){
+        return R.ok().put("data", iCrmStatisticManageService.getSearchUserInfo(id));
+    }
+
+    /**
+     * 导出综合统计列表
+     */
+    @GetMapping("/export")
+    public AjaxResult export(@RequestBody ComprehensiveStatisticsParam param) {
+        R r = statisticMain(param);
+        List<ComprehensiveStatisticsDTO> data = (List<ComprehensiveStatisticsDTO>)r.get("data");
+        ExcelUtil<ComprehensiveStatisticsDTO> util = new ExcelUtil<ComprehensiveStatisticsDTO>(ComprehensiveStatisticsDTO.class);
+        return util.exportExcel(data, "综合统计");
+    }
+
+}

+ 5 - 4
fs-company/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -34,13 +34,14 @@ public class DataSourceConfig {
         return new DruidDataSource();
     }
 
-
-
     @Bean
     @Primary
-    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+    public DynamicDataSource dataSource(@Qualifier("shardingSphereDataSource") DataSource shardingSphereDataSource,
+                                        @Qualifier("masterDataSource") DataSource masterDataSource,
+                                        @Qualifier("sopDataSource") DataSource sopDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
-        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
+        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
+        targetDataSources.put(DataSourceType.SHARDING.name(), shardingSphereDataSource);
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }

+ 12 - 47
fs-company/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,72 +1,37 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
-
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
- * 
+ *
 
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
-
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
+    private final Class<T> clazz;
 
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 0 - 20
fs-company/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,16 +1,7 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
-import com.fs.framework.config.FastJson2JsonRedisSerializer;
-import org.springframework.cache.CacheManager;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.cache.concurrent.ConcurrentMapCache;
-import org.springframework.cache.support.SimpleCacheManager;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -20,7 +11,6 @@ import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
 import java.math.BigDecimal;
-import java.util.Arrays;
 
 /**
  * redis配置
@@ -40,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -100,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);

+ 1 - 1
fs-company/src/main/resources/application.yml

@@ -3,7 +3,7 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-jnsyj-test
+    active: dev
 #    active: druid-jnmy-test
 #    active: druid-jzzx-test
 #    active: druid-hdt

+ 11 - 45
fs-doctor-app/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 10 - 27
fs-doctor-app/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -53,35 +43,35 @@ public class RedisConfig extends CachingConfigurerSupport
     }
 
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -95,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -111,7 +96,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
@@ -130,7 +114,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 6 - 2
fs-framework/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -41,11 +41,15 @@ public class DataSourceConfig {
 
     @Bean
     @Primary
-    public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+    public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,
+                                        @Qualifier("masterDataSource") DataSource masterDataSource,
+                                        @Qualifier("sopDataSource") DataSource sopDataSource,
+                                        @Qualifier("shardingSphereDataSource") DataSource shardingSphereDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
-        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
+        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
         targetDataSources.put(DataSourceType.CLICKHOUSE.name(), clickhouseDataSource);
+        targetDataSources.put(DataSourceType.SHARDING.name(), shardingSphereDataSource);
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
 

+ 11 - 45
fs-framework/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,15 +1,10 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import com.alibaba.fastjson.parser.ParserConfig;
-import org.springframework.util.Assert;
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -18,54 +13,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 10 - 27
fs-framework/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -9,11 +9,6 @@ import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 
 import java.math.BigDecimal;
 
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -53,35 +43,35 @@ public class RedisConfig extends CachingConfigurerSupport
     }
 
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -95,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -111,7 +96,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
@@ -130,7 +114,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 116 - 0
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -1,16 +1,20 @@
 package com.fs.app.service;
 
+import cn.hutool.json.JSONObject;
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.common.utils.http.HttpUtils;
 import com.fs.company.domain.CompanyMiniapp;
 import com.fs.company.service.ICompanyMiniappService;
+import com.fs.config.ai.AiHostProper;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.fastGpt.service.AiHookService;
 import com.fs.ipad.IpadSendUtils;
 import com.fs.ipad.vo.*;
 import com.fs.qw.domain.QwUser;
@@ -19,6 +23,7 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.service.IQwUserVideoService;
+import com.fs.qw.vo.QwMessageListVO;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qwApi.param.QwExternalContactHParam;
 import com.fs.sop.domain.QwSopLogs;
@@ -32,6 +37,7 @@ import org.springframework.stereotype.Service;
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.*;
 
 @Slf4j
 @Service
@@ -47,6 +53,23 @@ public class IpadSendServer {
     private final IQwUserVideoService qwUserVideoService;
     private final RedisCache redisCache;
     private final ICompanyMiniappService companyMiniappService;
+    private final AiHookService aiHookService;
+    private final AiHostProper aiHostProper;
+    private final ExecutorService executor = new ThreadPoolExecutor(
+            8, 32, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(1000),
+            new ThreadFactory() {
+                private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
+                private int count = 1;
+                @Override
+                public Thread newThread(Runnable r) {
+                    Thread thread = defaultFactory.newThread(r);
+                    thread.setName("sop-im-exec-" + count++);
+                    return thread;
+                }
+            },
+            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
+    );
     private void sendMiniProgram(BaseVo vo, QwSopCourseFinishTempSetting.Setting content, Map<String, FsCoursePlaySourceConfig> miniMap, Long companyId) {
         // 发送参数原本的appid
         String appid = content.getMiniprogramAppid();
@@ -86,6 +109,35 @@ public class IpadSendServer {
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + resp.getErrmsg());
         }
+
+        try {
+            WxWorkSendAppMsgRespDTO data = resp.getData();
+
+            JSONObject json = new JSONObject();
+            json.put("appid", courseMaConfig.getAppid());
+            json.put("appName", courseMaConfig.getName());
+            json.put("weappIconUrl", courseMaConfig.getImg());
+            json.put("desc", content.getMiniprogramTitle());
+            json.put("pagepath", content.getMiniprogramPage());
+            json.put("title",courseMaConfig.getName());
+            json.put("thumbnail", content.getMiniprogramPicUrl());
+
+            // 保存聊天消息
+            QwMessageListVO message = aiHookService.saveQwMsg(vo.getQwUserId(), vo.getExId(), json.toString(), 2, vo.isRoom(), data.getMsg_id(), data.getApp_info());
+            if (message == null) {
+                return;
+            }
+            // 发送webSocket
+            executor.execute(() -> {
+                try {
+                    HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                } catch (Exception e) {
+                    log.error("转发sop发送消息失败 err: {}", e.getMessage(), e);
+                }
+            });
+        } catch (Exception e) {
+            log.error("保存sop发送消息失败 err: {}", e.getMessage(), e);
+        }
     }
 
     private void sendFile(BaseVo vo, QwSopCourseFinishTempSetting.Setting content) {
@@ -110,6 +162,26 @@ public class IpadSendServer {
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + resp.getErrmsg());
         }
+
+        try {
+            WxWorkSendTextMsgRespDTO data = resp.getData();
+
+            // 保存聊天消息
+            QwMessageListVO message = aiHookService.saveQwMsg(vo.getQwUserId(), vo.getExId(), content.getValue(), 1, vo.isRoom(), data.getMsg_id(), data.getApp_info());
+            if (message == null) {
+                return;
+            }
+            // 发送webSocket
+            executor.execute(() -> {
+                try {
+                    HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                } catch (Exception e) {
+                    log.error("转发sop发送消息失败 err: {}", e.getMessage(), e);
+                }
+            });
+        } catch (Exception e) {
+            log.error("保存sop发送消息失败 err: {}", e.getMessage(), e);
+        }
     }
 
     public void sendImg(BaseVo vo, QwSopCourseFinishTempSetting.Setting content) {
@@ -121,6 +193,26 @@ public class IpadSendServer {
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + resp.getErrmsg());
         }
+
+        try {
+            WxwSendCDNImgMsgRespDTO data = resp.getData();
+
+            // 保存聊天消息
+            QwMessageListVO message = aiHookService.saveQwMsg(vo.getQwUserId(), vo.getExId(), content.getImgUrl(), 2, vo.isRoom(), data.getMsg_id(), data.getApp_info());
+            if (message == null) {
+                return;
+            }
+            // 发送webSocket
+            executor.execute(() -> {
+                try {
+                    HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                } catch (Exception e) {
+                    log.error("转发sop发送消息失败 err: {}", e.getMessage(), e);
+                }
+            });
+        } catch (Exception e) {
+            log.error("保存sop发送消息失败 err: {}", e.getMessage(), e);
+        }
     }
 
     public void sendLink(BaseVo vo, QwSopCourseFinishTempSetting.Setting content) {
@@ -192,6 +284,30 @@ public class IpadSendServer {
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + resp.getErrmsg());
         }
+
+        try {
+            WxwSendCDNVoiceMsgRespDTO data = resp.getData();
+
+            // 保存聊天消息
+            JSONObject json = new JSONObject();
+            json.put("url", content.getVoiceUrl());
+            json.put("content", content.getValue());
+
+            QwMessageListVO message = aiHookService.saveQwMsg(vo.getQwUserId(), vo.getExId(), json.toString(), 4, vo.isRoom(), data.getMsg_id(), data.getApp_info());
+            if (message == null) {
+                return;
+            }
+            // 发送webSocket
+            executor.execute(() -> {
+                try {
+                    HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                } catch (Exception e) {
+                    log.error("转发sop发送消息失败 err: {}", e.getMessage(), e);
+                }
+            });
+        } catch (Exception e) {
+            log.error("保存sop发送消息失败 err: {}", e.getMessage(), e);
+        }
     }
 
     public boolean isSend(QwUser qwUser, BaseVo parentVo) {

+ 11 - 45
fs-ipad-task/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 12 - 29
fs-ipad-task/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,41 +41,42 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
@@ -94,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -110,7 +96,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
@@ -129,8 +114,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
-
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 11 - 45
fs-live-app/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 11 - 26
fs-live-app/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,36 +41,37 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -94,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -110,7 +96,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();

+ 144 - 62
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -37,9 +37,11 @@ import io.swagger.annotations.Api;
 import lombok.extern.slf4j.Slf4j;
 import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.*;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -83,6 +85,9 @@ public class QwMsgController {
     @Autowired
     private IFsStoreOrderService storeOrderService;
 
+    @Qualifier("threadPoolTaskExecutor")
+    private Executor executor;
+
     @GetMapping("/sendExpressInfo/{orderId}")
     public R sendExpressInfo(@PathVariable Long orderId){
         String isSend = redisCache.getCacheObject("fs:express:info:send:" +orderId);
@@ -307,32 +312,30 @@ public class QwMsgController {
                 break;
             case 102000:
                 WxWorkMessageDTO wxWorkMessageDTO = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
-                if (wxWorkMessageDTO.getIs_room()!=0){
-                    break;
-                }
                 if (wxWorkMessageDTO.getReferid()!=0){
                     break;
                 }
 
+                try {
+                    executor.execute(() -> processImMessage(wxWorkMsgResp, wxWorkMessageDTO, id, serverId));
+                } catch (Exception e) {
+                    log.error("接收处理群消息失败 info: {} err: {}", json, e.getMessage(), e);
+                }
+
+                if (wxWorkMessageDTO.getIs_room()!=0){
+                    break;
+                }
 
                 Long receiver = wxWorkMessageDTO.getReceiver();
                 Long sender = wxWorkMessageDTO.getSender();
 
-                // 1客户 2销售
-                int sendType = 2;
-
-                // 消息发送者用户ID
-                Long userId = receiver;
-                if (2000000000000000L - receiver > 0){
-                    sendType = 1;
-                    userId = sender;
-                }
-                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104){
+                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104||wxWorkMessageDTO.getMsgtype()==141){
 
                     String content = wxWorkMessageDTO.getContent();
                     log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
                     log.info("id:{}, 发送人:"+wxWorkMessageDTO.getSender(), id);
                     log.info("id:{}, 内容:"+content, id);
+
                     if(wxWorkMessageDTO.getMsgtype()==16){
                         WxwSpeechToTextEntityDTO ste = new WxwSpeechToTextEntityDTO();
                         ste.setMsgid(wxWorkMessageDTO.getMsg_id());
@@ -452,26 +455,6 @@ public class QwMsgController {
                     qwUserVoiceLogService.addQuUserVoiceByIpadCallback(id,extId,recordType,totalSeconds,wxWorkMsgResp.getUuid());
                 }
 
-                // 处理文本消息
-                if (wxWorkMessageDTO.getMsgtype() == 2 || wxWorkMessageDTO.getMsgtype() == 0) {
-                    processTextMessage(id, userId, wxWorkMessageDTO.getContent(), wxWorkMsgResp, sendType);
-                }
-                // 语音消息
-                if (wxWorkMessageDTO.getMsgtype() == 16) {
-                    processVoiceMessage(serverId, wxWorkMessageDTO.getContent(), wxWorkMessageDTO, wxWorkMsgResp, id, userId, sendType);
-                }
-                // 图片消息
-                if (wxWorkMessageDTO.getMsgtype() == 101){
-                    processImageMessage(serverId, wxWorkMessageDTO, wxWorkMsgResp, id, userId, sendType);
-                }
-                // gif 表情消息
-                if (wxWorkMessageDTO.getMsgtype() == 104){
-                    processEmotionDynamicMessage(wxWorkMessageDTO, wxWorkMsgResp, id, userId, sendType);
-                }
-                // 小程序消息
-                if (wxWorkMessageDTO.getMsgtype() == 78) {
-                    processMiniAppMessage(serverId, wxWorkMessageDTO, wxWorkMsgResp, id, userId, sendType);
-                }
 
                 break;
 
@@ -483,6 +466,62 @@ public class QwMsgController {
         return map;
     }
 
+    /**
+     * Im消息存档回显
+     */
+    private void processImMessage(WxWorkMsgResp wxWorkMsgResp, WxWorkMessageDTO wxWorkMessageDTO, Long qwUserId, Long serverId) {
+        log.debug("接收到消息 msg: {}", JSON.toJSONString(wxWorkMessageDTO));
+        Long receiver = wxWorkMessageDTO.getReceiver();
+        Long sender = wxWorkMessageDTO.getSender();
+        boolean isRoom = wxWorkMessageDTO.getIs_room() != 0;
+        String chatId = null;
+        String chatAvatar = null;
+        if (isRoom) {
+            WxWorkRoomId2ChatIdDTO roomId2ChatIdDTO = new WxWorkRoomId2ChatIdDTO();
+            roomId2ChatIdDTO.setUuid(wxWorkMsgResp.getUuid());
+            roomId2ChatIdDTO.setRoom_id(wxWorkMessageDTO.getRoom_conversation_id());
+            WxWorkResponseDTO<WxWorkChatId2RoomIdResp> roomId2ChatIdResp = wxWorkService.roomId2ChatId(roomId2ChatIdDTO, serverId);
+            if (roomId2ChatIdResp.getErrcode() != 0) {
+                log.warn("接收群消息  rooId2ChatId失败: {}", roomId2ChatIdResp.getErrmsg());
+                return;
+            }
+            chatId = roomId2ChatIdResp.getData().getChatid();
+            WxRoomHeaderDTO roomHeaderDTO = new WxRoomHeaderDTO();
+            roomHeaderDTO.setRoomid(roomId2ChatIdResp.getData().getRoom_id());
+            roomHeaderDTO.setUuid(wxWorkMsgResp.getUuid());
+            WxWorkResponseDTO<WxRoomHeaderResp> roomHeaderResp = wxWorkService.wxRoomHeader(roomHeaderDTO, serverId);
+            if (roomHeaderResp.getErrcode() == 0) {
+                chatAvatar = roomHeaderResp.getData().getImage_url();
+            }
+        }
+
+        // 处理文本消息
+        if (wxWorkMessageDTO.getMsgtype() == 2 || wxWorkMessageDTO.getMsgtype() == 0) {
+            processTextMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+        // 语音消息
+        if (wxWorkMessageDTO.getMsgtype() == 16) {
+            processVoiceMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+        // 图片消息
+        if (wxWorkMessageDTO.getMsgtype() == 101){
+            processImageMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+        // gif 表情消息
+        if (wxWorkMessageDTO.getMsgtype() == 104){
+            processEmotionDynamicMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+        // 小程序消息
+        if (wxWorkMessageDTO.getMsgtype() == 78) {
+            processMiniAppMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+
+        // 撤回消息
+        if (wxWorkMessageDTO.getMsgtype() == 2063) {
+            processCancelMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+    }
+
     /**
      * 处理被拉黑的用户
      * @param qwUserId
@@ -599,29 +638,35 @@ public class QwMsgController {
 
     /**
      * 处理文本消息
-     * @param id                企微用户ID
-     * @param userId            消息发送者ID
-     * @param content           消息内容
+     * @param qwUserId          企微用户ID
+     * @param senderVid         消息发送者ID
+     * @param receiverVid       消息接收者ID
+     * @param serverId          服务器ID
+     * @param wxWorkMessageDTO  消息内容
      * @param wxWorkMsgResp     回调信息对象
-     * @param sendType          发送者类型 1客户 2销售
+     * @param isRoom            是否群聊
+     * @param chatId            会话ID(群聊才有)
+     * @param chatAvatar        群头像(群聊才有)
      */
-    private void processTextMessage(Long id, Long userId, String content, WxWorkMsgResp wxWorkMsgResp, Integer sendType) {
+    private void processTextMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 1);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, wxWorkMessageDTO.getContent(), wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 1, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
     /**
      * 处理语音消息
+     * @param qwUserId          企微用户ID
+     * @param senderVid         消息发送者ID
+     * @param receiverVid       消息接收者ID
      * @param serverId          服务器ID
      * @param wxWorkMessageDTO  消息DTO
-     * @param content           翻译后的内容
      * @param wxWorkMsgResp     回调信息对象
-     * @param id                企微用户ID
-     * @param userId            消息发送者ID
-     * @param sendType          发送者类型 1客户 2销售
+     * @param isRoom            是否群聊
+     * @param chatId            会话ID(群聊才有)
+     * @param chatAvatar        群头像(群聊才有)
      */
-    private void processVoiceMessage(Long serverId, String content, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, Long id, Long userId, Integer sendType) {
+    private void processVoiceMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
         String voiceFileName = IdUtils.fastSimpleUUID() + ".silk";
         WxWorkResponseDTO<String> fileUrlResp =
                 aiHookService.getFileUrl(wxWorkMsgResp.getUuid(), wxWorkMessageDTO.getVoice_id(), wxWorkMessageDTO.getAes_key(), 5, voiceFileName, wxWorkMessageDTO.getVoice_size(), serverId);
@@ -638,33 +683,39 @@ public class QwMsgController {
         }
 
         // 转换内容为空时再尝试一次
+        String content = wxWorkMessageDTO.getContent();
         if (StringUtils.isBlank(content)) {
             WxwSpeechToTextEntityDTO ste = new WxwSpeechToTextEntityDTO();
             ste.setMsgid(wxWorkMessageDTO.getMsg_id());
             ste.setUuid(wxWorkMsgResp.getUuid());
             WxWorkResponseDTO<WxwSpeechToTextEntityRespDTO> dto = wxWorkService.SpeechToTextEntity(ste, serverId);
-            content = dto.getData().getText();
+            if (dto.getErrcode() == 0) {
+                content = dto.getData().getText();
+            }
         }
 
-        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
+        JSONObject json = new JSONObject();
         json.put("url", url);
         json.put("content", content);
 
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, json.toString(), wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 4);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, json.toString(), wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 4, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
     /**
      * 处理图片消息
+     * @param qwUserId          企微用户ID
+     * @param senderVid         消息发送者ID
+     * @param receiverVid       消息接收者ID
      * @param serverId          服务器ID
      * @param wxWorkMessageDTO  消息DTO
      * @param wxWorkMsgResp     回调信息对象
-     * @param id                企微用户ID
-     * @param userId            消息发送者ID
-     * @param sendType          发送者类型 1客户 2销售
+     * @param isRoom            是否群聊
+     * @param chatId            会话ID(群聊才有)
+     * @param chatAvatar        群头像(群聊才有)
      */
-    private void processImageMessage(Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, Long id, Long userId, Integer sendType) {
+    private void processImageMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
         String fileName = IdUtils.fastSimpleUUID() + ".jpg";
         WxWorkResponseDTO<String> fileUrlResp =
                 aiHookService.getFileUrl(wxWorkMsgResp.getUuid(), wxWorkMessageDTO.getFile_id(), wxWorkMessageDTO.getAes_key(), wxWorkMessageDTO.getOpenim_cdn_authkey(), fileName, wxWorkMessageDTO.getFile_size(), serverId);
@@ -675,35 +726,42 @@ public class QwMsgController {
 
         String content = fileUrlResp.getData();
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 2);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, content, wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 2, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
     /**
      * 处理动态表情消息
+     * @param qwUserId          企微用户ID
+     * @param senderVid         消息发送者ID
+     * @param receiverVid       消息接收者ID
+     * @param serverId          服务器ID
      * @param wxWorkMessageDTO  消息DTO
      * @param wxWorkMsgResp     回调信息对象
-     * @param id                企微用户ID
-     * @param userId            消息发送者ID
-     * @param sendType          发送者类型 1客户 2销售
+     * @param isRoom            是否群聊
+     * @param chatId            会话ID(群聊才有)
+     * @param chatAvatar        群头像(群聊才有)
      */
-    private void processEmotionDynamicMessage(WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, Long id, Long userId, int sendType) {
+    private void processEmotionDynamicMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
         String content = wxWorkMessageDTO.getUrl();
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, content, wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 3);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, content, wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 3, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
     /**
      * 小程序消息处理
+     * @param qwUserId          企微用户ID
+     * @param senderVid         消息发送者ID
+     * @param receiverVid       消息接收者ID
      * @param serverId          服务器ID
      * @param wxWorkMessageDTO  消息DTO
      * @param wxWorkMsgResp     回调信息对象
-     * @param id                企微用户ID
-     * @param userId            消息发送者ID
-     * @param sendType          发送者类型 1客户 2销售
+     * @param isRoom            是否群聊
+     * @param chatId            会话ID(群聊才有)
+     * @param chatAvatar        群头像(群聊才有)
      */
-    private void processMiniAppMessage(Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, Long id, Long userId, int sendType) {
+    private void processMiniAppMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
         String thumbName = IdUtils.fastSimpleUUID() + ".jpg";
         WxWorkResponseDTO<String> fileUrlResp =
                 aiHookService.getFileUrl(wxWorkMsgResp.getUuid(), wxWorkMessageDTO.getThumbFileId(), wxWorkMessageDTO.getThumbAESKey(), 1, thumbName, wxWorkMessageDTO.getSize(), serverId);
@@ -722,8 +780,32 @@ public class QwMsgController {
         json.put("thumbnail", fileUrlResp.getData());
 
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(id, userId, json.toString(), wxWorkMsgResp.getUuid(), sendType, wxWorkMsgResp.getJson(), 5);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, json.toString(), wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 5, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
+        QwImSocket.broadcast(message);
+    }
+
+    /**
+     * 小程序消息处理
+     * @param qwUserId          企微用户ID
+     * @param senderVid         消息发送者ID
+     * @param receiverVid       消息接收者ID
+     * @param serverId          服务器ID
+     * @param wxWorkMessageDTO  消息DTO
+     * @param wxWorkMsgResp     回调信息对象
+     * @param isRoom            是否群聊
+     * @param chatId            会话ID(群聊才有)
+     * @param chatAvatar        群头像(群聊才有)
+     */
+    private void processCancelMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
+        // 修改聊天消息
+        QwMessageListVO message = aiHookService.updateQwMsg(qwUserId, senderVid, receiverVid, serverId, wxWorkMsgResp.getUuid(), isRoom, chatId, wxWorkMessageDTO.getRevoke_ref_appinfo());
+        QwImSocket.broadcast(message);
+    }
+
+    @PostMapping("/sendQwImMsg")
+    public void sendQwImMsg(@RequestBody QwMessageListVO message) {
         QwImSocket.broadcast(message);
     }
 
+
 }

+ 85 - 45
fs-qw-api-msg/src/main/java/com/fs/app/socket/QwImSocket.java

@@ -3,71 +3,89 @@ package com.fs.app.socket;
 import com.alibaba.fastjson.JSON;
 import com.fs.app.socket.configurator.QwImConfigurator;
 import com.fs.qw.vo.QwMessageListVO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-import javax.websocket.OnClose;
-import javax.websocket.OnError;
-import javax.websocket.OnOpen;
-import javax.websocket.Session;
+import javax.websocket.*;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpoint;
-import java.io.IOException;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
 
-@ServerEndpoint(value = "/qwImSocket/{companyId}", configurator = QwImConfigurator.class)
+@Slf4j
+@ServerEndpoint(value = "/qwImSocket/{companyUserId}", configurator = QwImConfigurator.class)
 @Component
 public class QwImSocket {
 
-    private static final ConcurrentHashMap<Long, CopyOnWriteArraySet<Session>> companySessions = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<Long, Set<Session>> companyUserSessions = new ConcurrentHashMap<>();
 
     /**
      * 连接建立成功调用的方法
      * @param session   连接会话
-     * @param companyId 公司ID
+     * @param companyUserId 公司ID
      */
     @OnOpen
-    public void onOpen(Session session, @PathParam("companyId") Long companyId) {
+    public void onOpen(Session session, @PathParam("companyUserId") Long companyUserId) {
+        session.setMaxIdleTimeout(300000);
+        session.getAsyncRemote().setSendTimeout(30000);
+
         // 将当前会话加入到会话池中
-        companySessions.computeIfAbsent(companyId, k -> new CopyOnWriteArraySet<>()).add(session);
+        // 限制单用户连接数
+        Set<Session> sessions = companyUserSessions.computeIfAbsent(
+                companyUserId,
+                k -> ConcurrentHashMap.newKeySet()
+        );
+
+        if (sessions.size() >= 10) {
+            log.warn("用户 {} 连接数超限: {}", companyUserId, sessions.size());
+            // 移除最旧的连接
+            sessions.stream().findFirst().ifPresent(oldSession -> {
+                try {
+                    oldSession.close(new CloseReason(
+                            CloseReason.CloseCodes.TRY_AGAIN_LATER,
+                            "连接数超限"
+                    ));
+                } catch (Exception e) {
+                    log.error("关闭旧连接失败", e);
+                }
+                sessions.remove(oldSession);
+            });
+        }
+
+        sessions.add(session);
     }
 
     /**
      * 连接关闭调用的方法
      * @param session   连接会话
-     * @param companyId 公司ID
+     * @param companyUserId 公司ID
      */
     @OnClose
-    public void onClose(Session session, @PathParam("companyId") Long companyId) {
+    public void onClose(Session session, @PathParam("companyUserId") Long companyUserId) {
         // 从会话池中移除当前会话
-        CopyOnWriteArraySet<Session> sessions = companySessions.get(companyId);
-        if (sessions != null) {
-            sessions.remove(session);
-            // 如果直播间没人了,可以移除该直播间
-            if (sessions.isEmpty()) {
-                companySessions.remove(companyId);
-            }
-        }
+        removeSession(companyUserId, session);
     }
 
     /**
      * 发生错误时调用的方法
      * @param session   连接会话
-     * @param companyId 公司ID
+     * @param companyUserId 公司ID
      * @param error     错误对象
      */
     @OnError
-    public void onError(Session session, @PathParam("companyId") Long companyId, Throwable error) {
-        System.err.println("发生错误!会话ID: " + session.getId());
-        CopyOnWriteArraySet<Session> sessions = companySessions.get(companyId);
-        if (sessions != null) {
+    public void onError(Session session, @PathParam("companyUserId") Long companyUserId, Throwable error) {
+        log.error("发生错误!会话ID: {}", session.getId());
+        removeSession(companyUserId, session);
+    }
+
+    private void removeSession(Long companyUserId, Session session) {
+        if (companyUserId == null || session == null) return;
+        companyUserSessions.computeIfPresent(companyUserId, (k, sessions) -> {
             sessions.remove(session);
-            // 如果直播间没人了,可以移除该直播间
-            if (sessions.isEmpty()) {
-                companySessions.remove(companyId);
-            }
-        }
+            log.debug("移除会话,剩余连接数: {}", sessions.size());
+            return sessions.isEmpty() ? null : sessions;
+        });
     }
 
     /**
@@ -75,27 +93,49 @@ public class QwImSocket {
      * @param message   要发送的消息
      */
     public static void broadcast(QwMessageListVO message) {
-        if (Objects.isNull(message)) {
+        if (Objects.isNull(message) || message.getCompanyUserId() == null) {
             return;
         }
 
         String msg = JSON.toJSONString(message);
-        CopyOnWriteArraySet<Session> sessions = companySessions.get(message.getCompanyId());
-        if (sessions != null) {
-            for (Session session : sessions) {
-                if (session.isOpen()) {
-                    try {
-                        session.getBasicRemote().sendText(msg);
-                    } catch (IOException e) {
-                        System.err.println("发送消息给会话[" + session.getId() + "]失败: " + e.getMessage());
-                        // 移除无效会话
-                        sessions.remove(session);
+        Set<Session> sessions = companyUserSessions.get(message.getCompanyUserId());
+
+        if (sessions == null || sessions.isEmpty()) {
+            return;
+        }
+
+        sessions.removeIf(session -> !session.isOpen());
+        for (Session session : sessions) {
+            if (session.isOpen()) {
+                session.getAsyncRemote().sendText(msg, result -> {
+                    if (!result.isOK()) {
+                        log.warn("发送失败: {}", result.getException().getMessage());
                     }
-                } else {
-                    sessions.remove(session); // 移除已关闭的会话
-                }
+                });
             }
         }
     }
 
+    /**
+     * 定时检查连接是否存活
+     */
+    public static void clean() {
+        companyUserSessions.entrySet().removeIf(entry -> {
+            Set<Session> sessions = entry.getValue();
+            sessions.removeIf(session -> !session.isOpen());
+            return sessions.isEmpty();
+        });
+    }
+
+    /**
+     * 获取统计信息
+     */
+    public static String getStats() {
+        int totalSessions = companyUserSessions.values().stream()
+                .mapToInt(Set::size)
+                .sum();
+        return String.format("用户数: %d, 总连接数: %d",
+                companyUserSessions.size(), totalSessions);
+    }
+
 }

+ 14 - 2
fs-qw-api-msg/src/main/java/com/fs/app/socket/configurator/QwImConfigurator.java

@@ -1,7 +1,11 @@
 package com.fs.app.socket.configurator;
 
 import com.fs.app.exception.FSException;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.websocket.HandshakeResponse;
 import javax.websocket.server.HandshakeRequest;
 import javax.websocket.server.ServerEndpointConfig;
@@ -11,11 +15,19 @@ import java.util.Objects;
 
 public class QwImConfigurator extends ServerEndpointConfig.Configurator {
 
+    private final TokenService tokenService = SpringUtils.getBean(TokenService.class);
+
     @Override
     public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
         Map<String, List<String>> parameterMap = request.getParameterMap();
-        List<String> token = parameterMap.get("token");
-        if (Objects.isNull(token)) {
+        List<String> tokens = parameterMap.get("token");
+        if (tokens == null || tokens.isEmpty() || tokens.get(0).isEmpty()) {
+            throw new FSException("Unauthorized access to WebSocket endpoint.");
+        }
+
+        String token = tokens.get(0);
+        LoginUser loginUser = tokenService.getLoginUser(token);
+        if (loginUser == null) {
             throw new FSException("Unauthorized access to WebSocket endpoint.");
         }
     }

+ 18 - 0
fs-qw-api-msg/src/main/java/com/fs/app/socket/task/QwImSessionCleaner.java

@@ -0,0 +1,18 @@
+package com.fs.app.socket.task;
+
+import com.fs.app.socket.QwImSocket;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class QwImSessionCleaner {
+
+    @Scheduled(fixedDelay = 60000)
+    public void clean() {
+        log.info("定时清理无效会话");
+        QwImSocket.clean();
+        log.info("当前会话数: {}", QwImSocket.getStats());
+    }
+}

+ 5 - 4
fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -51,11 +51,12 @@ public class DataSourceConfig {
     public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,
                                         @Qualifier("masterDataSource") DataSource masterDataSource,
                                         @Qualifier("sopDataSource") DataSource sopDataSource,
-                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
+                                        @Qualifier("slaveDataSource") DataSource slaveDataSource,
+                                        @Qualifier("shardingSphereDataSource") DataSource shardingSphereDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
-        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
-
-        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
+        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
+        targetDataSources.put(DataSourceType.SHARDING.name(), shardingSphereDataSource);
+        targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
         targetDataSources.put(DataSourceType.CLICKHOUSE.name(), clickhouseDataSource); // Ensure matching key
         return new DynamicDataSource(masterDataSource, targetDataSources);

+ 11 - 45
fs-qw-api-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length <= 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 1 - 10
fs-qw-api-msg/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -13,6 +13,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
 import java.math.BigDecimal;
@@ -35,11 +36,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -113,11 +109,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);

+ 14 - 0
fs-qw-api-msg/src/main/java/com/fs/framework/service/TokenService.java

@@ -82,6 +82,20 @@ public class TokenService
         return null;
     }
 
+    /**
+     * 获取用户身份信息
+     *
+     * @return 用户信息
+     */
+    public LoginUser getLoginUser(String token) {
+        Claims claims = parseToken(token);
+        // 解析对应的权限以及用户信息
+        String uuid = (String) claims.get(Constants.COMPANY_LOGIN_USER_KEY);
+        String userKey = getTokenKey(uuid);
+        return redisCache.getCacheObject(userKey);
+
+    }
+
     /**
      * 设置用户身份信息
      */

+ 1 - 1
fs-qw-api-msg/src/main/resources/application.yml

@@ -7,4 +7,4 @@ spring:
 #    active: druid-jzzx
 #    active: druid-hdt
 #    active: druid-sxjz
-    active: druid-hzyy-test
+    active: dev

+ 11 - 45
fs-qw-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 19 - 35
fs-qw-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,66 +41,61 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    @SuppressWarnings(value = { "unchecked", "rawtypes" })
-    public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Object> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
-        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
-
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(serializer);
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(serializer);
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(serializer);
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(serializer);
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
@@ -129,7 +114,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 11 - 45
fs-qw-mq/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 11 - 27
fs-qw-mq/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,37 +41,37 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -95,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -129,7 +114,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 11 - 45
fs-qw-task/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 11 - 28
fs-qw-task/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,37 +41,37 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -95,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -111,7 +96,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
@@ -130,7 +114,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 11 - 45
fs-qw-voice/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 11 - 25
fs-qw-voice/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,36 +41,37 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -94,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);

+ 11 - 45
fs-qwhook-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 12 - 28
fs-qwhook-msg/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,41 +41,42 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
@@ -94,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -110,7 +96,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
@@ -129,7 +114,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 11 - 45
fs-qwhook-sop/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 11 - 26
fs-qwhook-sop/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,36 +41,37 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -94,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -110,7 +96,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();

+ 11 - 45
fs-qwhook/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 11 - 28
fs-qwhook/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,37 +41,37 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
@@ -95,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -111,7 +96,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
         RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
@@ -130,7 +114,6 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 11 - 45
fs-redis/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 30 - 27
fs-redis/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -12,10 +7,11 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
-import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -34,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -52,40 +43,40 @@ public class RedisConfig extends CachingConfigurerSupport
     }
 
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
@@ -94,11 +85,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -110,7 +96,24 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
 
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 11 - 45
fs-repeat-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -1,16 +1,11 @@
 package com.fs.framework.config;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.parser.ParserConfig;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
-import org.springframework.util.Assert;
 
-import java.nio.charset.Charset;
 
 /**
  * Redis使用FastJson序列化
@@ -19,54 +14,25 @@ import java.nio.charset.Charset;
  */
 public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
 {
-    @SuppressWarnings("unused")
-    private ObjectMapper objectMapper = new ObjectMapper();
+    private final Class<T> clazz;
 
-    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
-
-    private Class<T> clazz;
-
-    static
-    {
-        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
-    }
-
-    public FastJson2JsonRedisSerializer(Class<T> clazz)
-    {
-        super();
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
         this.clazz = clazz;
     }
 
     @Override
-    public byte[] serialize(T t) throws SerializationException
-    {
-        if (t == null)
-        {
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
             return new byte[0];
         }
-        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+        return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 
     @Override
-    public T deserialize(byte[] bytes) throws SerializationException
-    {
-        if (bytes == null || bytes.length <= 0)
-        {
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length == 0) {
             return null;
         }
-        String str = new String(bytes, DEFAULT_CHARSET);
-
-        return JSON.parseObject(str, clazz);
-    }
-
-    public void setObjectMapper(ObjectMapper objectMapper)
-    {
-        Assert.notNull(objectMapper, "'objectMapper' must not be null");
-        this.objectMapper = objectMapper;
-    }
-
-    protected JavaType getJavaType(Class<?> clazz)
-    {
-        return TypeFactory.defaultInstance().constructType(clazz);
+        return JSON.parseObject(bytes, clazz, JSONReader.Feature.SupportAutoType, JSONReader.Feature.SupportClassForName);
     }
 }

+ 19 - 33
fs-repeat-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -1,10 +1,5 @@
 package com.fs.framework.config;
 
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -35,11 +30,6 @@ public class RedisConfig extends CachingConfigurerSupport
 
         FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
 
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
         template.setValueSerializer(serializer);
@@ -51,61 +41,57 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Integer> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
 
         template.afterPropertiesSet();
         return template;
     }
-
     @Bean
-    @SuppressWarnings(value = { "unchecked", "rawtypes" })
-    public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Object> template = new RedisTemplate<>();
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
-        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
-
-        ObjectMapper mapper = new ObjectMapper();
-        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
-        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
-        serializer.setObjectMapper(mapper);
-
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-        template.setValueSerializer(serializer);
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(serializer);
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
 
         template.afterPropertiesSet();
         return template;
     }
+
     @Bean
-    public RedisTemplate<String, Integer> redisTemplateForInteger(RedisConnectionFactory connectionFactory) {
-        RedisTemplate<String, Integer> template = new RedisTemplate<>();
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
         template.setConnectionFactory(connectionFactory);
 
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
         // 使用StringRedisSerializer来序列化和反序列化redis的key值
         template.setKeySerializer(new StringRedisSerializer());
-
-        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
-        template.setValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setValueSerializer(serializer);
 
         // Hash的key也采用StringRedisSerializer的序列化方式
         template.setHashKeySerializer(new StringRedisSerializer());
-        template.setHashValueSerializer(new GenericToStringSerializer<>(Integer.class));
+        template.setHashValueSerializer(serializer);
 
         template.afterPropertiesSet();
         return template;

+ 4 - 0
fs-service/pom.xml

@@ -298,6 +298,10 @@
             <version>1.0.250</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 27 - 0
fs-service/src/main/java/com/fs/company/service/ICrmStatisticManageService.java

@@ -0,0 +1,27 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.statis.dto.ComprehensiveStatisticsDTO;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
+
+import java.util.List;
+
+/**
+ * @description: 销售端的统计服务
+ * @author: Guos
+ * @time: 2025/11/13 下午4:21
+ */
+public interface ICrmStatisticManageService {
+
+    Object getSearchUserInfo(Long id);
+
+    List<CompanyDeptUserInfo> getSearchCompanyInfo(Long companyId);
+
+    /**
+     * @param id
+     * @return
+     */
+    List<CompanyDeptUserInfo> getSearchDeptInfo(Long id);
+
+    List<ComprehensiveStatisticsDTO> statisticMain(ComprehensiveStatisticsParam param);
+}

+ 46 - 0
fs-service/src/main/java/com/fs/company/service/impl/CrmStatisticManageServiceImpl.java

@@ -0,0 +1,46 @@
+package com.fs.company.service.impl;
+
+
+import com.fs.company.domain.CompanyDeptUserInfo;
+import com.fs.company.service.ICrmStatisticManageService;
+
+import com.fs.statis.dto.ComprehensiveStatisticsDTO;
+import com.fs.statis.param.ComprehensiveStatisticsParam;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/13 下午4:21
+ */
+@Service
+public class CrmStatisticManageServiceImpl implements ICrmStatisticManageService {
+
+
+    @Override
+    public Object getSearchUserInfo(Long id) {
+        return null;
+    }
+
+    @Override
+    public List<CompanyDeptUserInfo> getSearchCompanyInfo(Long companyId) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<CompanyDeptUserInfo> getSearchDeptInfo(Long id) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<ComprehensiveStatisticsDTO> statisticMain(ComprehensiveStatisticsParam param) {
+        return Collections.emptyList();
+    }
+}

+ 5 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -811,4 +811,9 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
      */
     @Select("select count(log_id) from fs_course_watch_log where user_id = #{userId} and project = #{projectId}")
     Long getCountByUserIdAndProjectId(@Param("userId") Long userId, @Param("projectId") Long projectId);
+
+    /**
+     * 查询用户看课记录
+     */
+    List<FsCourseWatchLogIMVO> selectWatchLogIMVOListByMap(@Param("params") Map<String, Object> params);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -170,4 +170,9 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
      * @return
      */
     List<Long> getExContactIdsIdsByWatchLogIds(List<Long> watchLogIds);
+
+    /**
+     * 查询用户看课记录
+     */
+    List<FsCourseWatchLogIMVO> selectWatchLogIMVOListByMap(Map<String, Object> params);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -1857,5 +1857,13 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.getExContactIdsIdsByWatchLogIds(watchLogIds);
     }
 
+    /**
+     * 查询用户看课记录
+     */
+    @Override
+    public List<FsCourseWatchLogIMVO> selectWatchLogIMVOListByMap(Map<String, Object> params) {
+        return fsCourseWatchLogMapper.selectWatchLogIMVOListByMap(params);
+    }
+
 
 }

+ 41 - 18
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -747,26 +747,49 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService {
                 return R.error(567, "群聊通用链接").put("qwExternalId", contact.getId());
             }
         }
-        if ("今正科技".equals(cloudHostProper.getCompanyName())) {
-            QwExternalContact UnionEXt = qwExternalContactMapper.selectQwExternalByUnionID(user.getUnionId());
-            if (UnionEXt != null) {
-                log.info("匹配到的第一个企微用户:" + UnionEXt.getUserId());
-                log.info("企微id:" + UnionEXt.getId());
-                log.info("用户:" + param.getVideoId());
-                log.info("企微用户:" + param.getQwUserId());
-                param.setQwExternalId(UnionEXt.getId());
-                FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(UnionEXt.getId(), param.getVideoId(), param.getQwUserId());
-                if (log == null) {
-                    param.setUserId(user.getUserId());
-                    createWatchLog(param);
-                } else {
-                    if (log.getUserId() == null || log.getUserId().equals(0L) || !log.getUserId().equals(param.getUserId())) {
-                        log.setUserId(param.getUserId());
+
+        boolean bool1 = "今正科技".equals(cloudHostProper.getCompanyName());
+        boolean bool2 = "弘珍医药".equals(cloudHostProper.getCompanyName());
+        if (bool1 || bool2) {
+            //一个都找不到
+            List<QwExternalContact> qwExternalContactList = qwExternalContactMapper.selectQwExternalByUnionID(user.getUnionId());
+
+            if (qwExternalContactList != null && !qwExternalContactList.isEmpty()) {
+                QwExternalContact UnionEXt = qwExternalContactList.get(0);
+                for (QwExternalContact qwExternalContact : qwExternalContactList) {
+                    if (qwExternalContact.getFsUserId() == null || qwExternalContact.getFsUserId() != 0L) {
+                        qwExternalContact.setFsUserId(user.getUserId());
+                        qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+                    }
+                    try {
+                        if (qwExternalContact.getQwUserId().equals(Long.parseLong(param.getQwUserId()))) {
+                            UnionEXt = qwExternalContact;
+                        }
+                    } catch (Exception e) {
+                        log.error("群聊链接匹配销售失败");
+                    }
+
+                    log.info("匹配到的第一个企微用户:" + UnionEXt.getUserId());
+                    log.info("企微id:" + UnionEXt.getId());
+                    log.info("用户:" + param.getVideoId());
+                    log.info("企微用户:" + param.getQwUserId());
+                    param.setQwExternalId(UnionEXt.getId());
+//                    param.setQwUserId(String.valueOf(UnionEXt.getQwUserId()));
+//                    param.setCompanyUserId(UnionEXt.getCompanyUserId());
+//                    param.setCompanyId(UnionEXt.getCompanyId());
+                    FsCourseWatchLog log = courseWatchLogMapper.getWatchCourseVideoByExt(UnionEXt.getId(), param.getVideoId(), param.getQwUserId());
+                    if (log == null) {
+                        param.setUserId(user.getUserId());
+                        createWatchLog(param);
+                    } else {
+                        if (log.getUserId() == null || log.getUserId().equals(0L) || !log.getUserId().equals(param.getUserId())) {
+                            log.setUserId(param.getUserId());
+                        }
+                        log.setUpdateTime(new Date());
+                        courseWatchLogMapper.updateFsCourseWatchLog(log);
                     }
-                    log.setUpdateTime(new Date());
-                    courseWatchLogMapper.updateFsCourseWatchLog(log);
+                    return R.error(567, "群聊通用链接").put("qwExternalId", UnionEXt.getId());
                 }
-                return R.error(567, "群聊通用链接").put("qwExternalId", UnionEXt.getId());
             }
         }
 

+ 3 - 0
fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtApiResponse.java

@@ -1,5 +1,7 @@
 package com.fs.erp.dto.wdt;
 
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.annotation.JSONType;
 import lombok.Data;
 import org.apache.commons.lang3.StringEscapeUtils;
 
@@ -9,6 +11,7 @@ import org.apache.commons.lang3.StringEscapeUtils;
  * @date 2025-02-27
  */
 @Data
+@JSONType(naming = PropertyNamingStrategy.CamelCase)
 public class ErpWdtApiResponse {
 
     /**

+ 3 - 0
fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtBaseResponseDTO.java

@@ -1,5 +1,7 @@
 package com.fs.erp.dto.wdt;
 
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.annotation.JSONType;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -13,6 +15,7 @@ import java.util.List;
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
+@JSONType(naming = PropertyNamingStrategy.CamelCase)
 public class ErpWdtBaseResponseDTO {
 
     /**

+ 3 - 0
fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtStockRespDTO.java

@@ -1,5 +1,7 @@
 package com.fs.erp.dto.wdt;
 
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.annotation.JSONType;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
@@ -7,6 +9,7 @@ import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 import java.util.List;
 
+@JSONType(naming = PropertyNamingStrategy.CamelCase)
 @Data
 public class ErpWdtStockRespDTO implements Serializable {
     /**

+ 3 - 0
fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtTradeQueryResponse.java

@@ -1,5 +1,7 @@
 package com.fs.erp.dto.wdt;
 
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.annotation.JSONType;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
@@ -21,6 +23,7 @@ import java.util.List;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
+@JSONType(naming = PropertyNamingStrategy.CamelCase)
 public class ErpWdtTradeQueryResponse {
 
     /**

+ 2 - 7
fs-service/src/main/java/com/fs/erp/service/impl/WdtErpGoodsServiceImpl.java

@@ -47,10 +47,7 @@ public class WdtErpGoodsServiceImpl implements IErpGoodsService {
         Asserts.notNull(param.getCode(),"barcode不能为空!");
         try {
             String response = client.execute("goods_query.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtBaseResponseDTO erpWdtBaseResponseDTO =
-                    JSON.parseObject(response, ErpWdtBaseResponseDTO.class, config);
+            ErpWdtBaseResponseDTO erpWdtBaseResponseDTO = JSON.parseObject(response, ErpWdtBaseResponseDTO.class);
             if(ObjectUtils.equals(0, erpWdtBaseResponseDTO.getCode())){
                 List<ErpGoods> list = new ArrayList<>();
 
@@ -100,9 +97,7 @@ public class WdtErpGoodsServiceImpl implements IErpGoodsService {
         map.put("spec_no",barcode);
         try {
             String response = client.execute("stock_query.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtStockRespDTO erpWdtStockRespDTO = JSON.parseObject(response, ErpWdtStockRespDTO.class,config);
+            ErpWdtStockRespDTO erpWdtStockRespDTO = JSON.parseObject(response, ErpWdtStockRespDTO.class);
             List<ErpGoodsStock> list = new ArrayList<>();
             if(ObjectUtils.equals(0,erpWdtStockRespDTO.getCode())){
                 List<ErpWdtStockDTO> stocks = erpWdtStockRespDTO.getStocks();

+ 6 - 20
fs-service/src/main/java/com/fs/erp/service/impl/WdtErpOrderServiceImpl.java

@@ -269,9 +269,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
@@ -449,9 +447,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
@@ -624,9 +620,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
@@ -958,10 +952,8 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
              log.info("Erp推送传参: {}", map);
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
@@ -1002,8 +994,6 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
         map.put("src_tid",param.getCode());
         try {
             String execute = client.execute("sales_trade_query.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
             ErpWdtTradeQueryResponse tradeQueryResponseDTO = JSON.parseObject(execute, ErpWdtTradeQueryResponse.class);
             if(ObjectUtil.equal(0,tradeQueryResponseDTO.getCode())){
 //                if (tradeQueryResponseDTO.getTrades().isEmpty()){
@@ -1097,9 +1087,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
         map.put("api_refund_list",convertToSnakeCase(erpWdtApiRefunds));
         try {
             String execute = client.execute("sales_refund_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(execute, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(execute, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0, erpWdtApiResponse.getCode())){
                 log.info("退款单更新成功: {}", erpWdtApiResponse);
                 return new BaseResponse();
@@ -1274,9 +1262,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();

+ 24 - 8
fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java

@@ -43,13 +43,29 @@ public interface AiHookService {
     /**
      * 保存企微聊天信息
      *
-     * @param qwUserId 企微用户ID
-     * @param userId   用户ID
-     * @param content  聊天内容
-     * @param uuid     UUID
-     * @param sendType 发送者类型 1用户 2客服
-     * @param json     消息json
-     * @param msgType  消息类型 1文本 2图片 3动态表情 4语音 5小程序
+     * @param qwUserId      企微用户ID
+     * @param senderVid     消息发送者ID
+     * @param receiverVid   消息接收者ID
+     * @param serverId      服务器ID
+     * @param content       聊天内容
+     * @param uuid          UUID
+     * @param json          消息json
+     * @param msgType       消息类型 1文本 2图片 3动态表情 4语音 5小程序
+     * @param isRoom        是否群聊
+     * @param chatId        会话ID(群聊才有)
+     * @param chatAvatar    群头像(群聊才有)
+     * @param qwMsgId       企微消息ID
+     * @param qwAppInfo       企微appInfo
      */
-    QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json, int msgType);
+    QwMessageListVO saveQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String content, String uuid, String json, int msgType, boolean isRoom, String chatId, String chatAvatar, Long qwMsgId, String qwAppInfo);
+
+    /**
+     * 保存企微聊天信息
+     */
+    QwMessageListVO saveQwMsg(Long qwUserId, String exId, String content, int msgType, boolean isRoom, Long qwMsgId, String qwAppInfo);
+
+    /**
+     * 修改消息
+     */
+    QwMessageListVO updateQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String uuid, boolean isRoom, String chatId, String qwAppInfo);
 }

+ 474 - 63
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -3,11 +3,14 @@ package com.fs.fastGpt.service.impl;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONArray;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.fs.common.annotation.DataSource;
 import com.fs.common.annotation.Excel;
-import com.fs.common.config.FSConfig;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.enums.DataSourceType;
+import com.fs.common.utils.PinYinUtil;
+import com.fs.common.utils.http.HttpUtils;
 import com.fs.company.domain.CompanyConfig;
 import com.fs.company.mapper.CompanyConfigMapper;
 import com.fs.config.ai.AiHostProper;
@@ -38,13 +41,12 @@ import com.fs.his.domain.FsStoreOrder;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.dto.TracesDTO;
 import com.fs.his.enums.ShipperCodeEnum;
-import com.fs.his.mapper.FsStoreMapper;
 import com.fs.his.mapper.FsStoreOrderMapper;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsStoreOrderService;
-import com.fs.im.dto.OpenImMsgDTO;
 import com.fs.im.vo.OpenImMsgCallBackVO;
 import com.fs.qw.domain.*;
+import com.fs.qw.dto.QwImUserDTO;
 import com.fs.qw.enums.MsgType;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwAutoTagsRulesTags;
@@ -65,23 +67,23 @@ import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
 import com.vdurmont.emoji.EmojiParser;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.Nullable;
+import org.json.JSONObject;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
-import javax.annotation.Resource;
 import java.lang.reflect.Field;
 import java.time.DayOfWeek;
 import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -173,10 +175,29 @@ public class AiHookServiceImpl implements AiHookService {
     private QwSessionMapper qwSessionMapper;
     @Autowired
     private QwMsgMapper qwMsgMapper;
+    @Autowired
+    private QwGroupChatMapper qwGroupChatMapper;
+    @Autowired
+    private RedissonClient redissonClient;
 
     private static final String AI_REPLY = "AI_REPLY:";
     private static final String AI_REPLY_TAG = "AI_REPLY_TAG:";
 
+    private final ExecutorService executor = new ThreadPoolExecutor(
+            8, 32, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(1000),
+            new ThreadFactory() {
+                private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
+                private int count = 1;
+                @Override
+                public Thread newThread(Runnable r) {
+                    Thread thread = defaultFactory.newThread(r);
+                    thread.setName("ai-im-exec-" + count++);
+                    return thread;
+                }
+            },
+            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
+    );
 
     /** Ai半小时未回复提醒 **/
     /**
@@ -296,7 +317,7 @@ public class AiHookServiceImpl implements AiHookService {
                             if (result.isLongText()){
                                 //新增用户信息
                                 addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
-                                sendAiMsg(content,sender,uid,serverId);
+                                sendAiMsg(content,sender,uid,serverId, user.getId(), qwExternalContacts.getExternalUserId());
 
                             }else {
                                 String sa = contentKh.replaceAll("】\n", "】").replaceAll("\n【", "【");
@@ -310,7 +331,7 @@ public class AiHookServiceImpl implements AiHookService {
                                 //新增用户信息
                                 addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
                                 for (String msg : countList) {
-                                    sendAiMsg(msg,sender,uid,serverId);
+                                    sendAiMsg(msg,sender,uid,serverId, user.getId(), qwExternalContacts.getExternalUserId());
                                     try {
                                         Thread.sleep(10000);
                                     } catch (InterruptedException e) {
@@ -572,9 +593,9 @@ public class AiHookServiceImpl implements AiHookService {
                     //新增用户信息
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
                     if (type==16){
-                        sendAiVoiceMsg(content,sender,uid,serverId,user);
+                        sendAiVoiceMsg(content,sender,uid,serverId,user, qwExternalContacts.getExternalUserId());
                     }else {
-                        sendAiMsg(content,sender,uid,serverId);
+                        sendAiMsg(content,sender,uid,serverId, user.getId(), qwExternalContacts.getExternalUserId());
                     }
 
                 }else {
@@ -591,9 +612,9 @@ public class AiHookServiceImpl implements AiHookService {
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
                     for (String msg : countList) {
                         if (type==16){
-                            sendAiVoiceMsg(msg,sender,uid,serverId,user);
+                            sendAiVoiceMsg(msg,sender,uid,serverId,user, qwExternalContacts.getExternalUserId());
                         }else {
-                            sendAiMsg(msg,sender,uid,serverId);
+                            sendAiMsg(msg,sender,uid,serverId, user.getId(), qwExternalContacts.getExternalUserId());
                         }
                         try {
                             Thread.sleep(10000);
@@ -958,7 +979,7 @@ public class AiHookServiceImpl implements AiHookService {
         return maskedContent;
     }
 
-    private void sendAiVoiceMsg(String content, Long sendId , String uuid,Long serverId,QwUser user) {
+    private void sendAiVoiceMsg(String content, Long sendId , String uuid,Long serverId,QwUser user, String extUserId) {
         if (content == null || content.trim().isEmpty()){
             System.out.println("输出为空格");
             return;
@@ -997,11 +1018,37 @@ public class AiHookServiceImpl implements AiHookService {
         WxWorkResponseDTO<WxwSendCDNVoiceMsgRespDTO> wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO = wxWorkService.SendCDNVoiceMsg(wxwSendCDNVoiceMsgDTO, serverId);
         System.out.println(wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO);
 
+        if (wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO.getErrcode() == 0) {
+            try {
+                WxwSendCDNVoiceMsgRespDTO dtoData = wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO.getData();
+
+                JSONObject json = new JSONObject();
+                json.put("url", data.getUrl());
+                json.put("content", content);
+
+                // 保存聊天消息
+                QwMessageListVO message = this.saveQwMsg(user.getId(), extUserId, json.toString(), 4, false, dtoData.getMsg_id(), dtoData.getApp_info());
+                if (message == null) {
+                    return;
+                }
+                // 发送webSocket
+                executor.execute(() -> {
+                    try {
+                        HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                    } catch (Exception e) {
+                        log.error("转发ai回复消息失败 err: {}", e.getMessage(), e);
+                    }
+                });
+            } catch (Exception e) {
+                log.error("保存ai回复消息失败 err: {}", e.getMessage(), e);
+            }
+        }
+
     }
 
     @Autowired
     QwSopLogsMapper qwSopLogsMapper;
-    private void sendAiMsg(String content, Long sendId , String uuid,Long serverId) {
+    private void sendAiMsg(String content, Long sendId , String uuid,Long serverId, Long qwUserId, String extUserId) {
         if (content == null || content.trim().isEmpty()){
             System.out.println("输出为空格");
             return;
@@ -1014,6 +1061,27 @@ public class AiHookServiceImpl implements AiHookService {
         WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> wxWorkSendTextMsgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(wxWorkSendTextMsgDTO,serverId);
         WxWorkSendTextMsgRespDTO data = wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getData();
 
+        if (wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getErrcode() == 0) {
+            try {
+                WxWorkSendTextMsgRespDTO dtoData = wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getData();
+
+                // 保存聊天消息
+                QwMessageListVO message = this.saveQwMsg(qwUserId, extUserId, content, 1, false, dtoData.getMsg_id(), dtoData.getApp_info());
+                if (message == null) {
+                    return;
+                }
+                // 发送webSocket
+                executor.execute(() -> {
+                    try {
+                        HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                    } catch (Exception e) {
+                        log.error("转发ai回复消息失败 err: {}", e.getMessage(), e);
+                    }
+                 });
+             } catch (Exception e) {
+                log.error("保存ai回复消息失败 err: {}", e.getMessage(), e);
+            }
+        }
 
     }
     /**
@@ -2165,93 +2233,195 @@ public class AiHookServiceImpl implements AiHookService {
     /**
      * 保存企微聊天信息
      *
-     * @param qwUserId 企微用户ID
-     * @param userId   用户ID
-     * @param content  聊天内容
-     * @param uuid     UUID
-     * @param sendType 发送者类型 1用户 2客服
-     * @param json     消息json
-     * @param msgType  消息类型 1文本 2图片 3动态表情 4语音
+     * @param qwUserId      企微用户ID
+     * @param senderVid     消息发送者ID
+     * @param receiverVid   消息接收者ID
+     * @param serverId      服务器ID
+     * @param content       聊天内容
+     * @param uuid          UUID
+     * @param json          消息json
+     * @param msgType       消息类型 1文本 2图片 3动态表情 4语音 5小程序
+     * @param isRoom        是否群聊
+     * @param chatId        会话ID(群聊才有)
+     * @param chatAvatar    群头像(群聊才有)
+     * @param qwMsgId       企微消息ID
+     * @param qwAppInfo       企微appInfo
      */
-    @Transactional(rollbackFor = Exception.class)
+    @DataSource(DataSourceType.SHARDING)
     @Override
-    public QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json, int msgType) {
+    public QwMessageListVO saveQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String content, String uuid, String json, int msgType, boolean isRoom, String chatId, String chatAvatar, Long qwMsgId, String qwAppInfo) {
         // 查询企微用户
         QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
-        if (Objects.isNull(qwUser)){
+        if (Objects.isNull(qwUser)) {
             log.warn("企微用户不存在 qwUserId: {}", qwUserId);
             return null;
         }
 
-        // 查询外部联系人
-        QwExternalContact qwExternalContact = getExternalContact(userId, uuid, qwUser.getServerId(), qwUser.getCorpId(), qwUser.getQwUserId());
-        if (Objects.isNull(qwExternalContact)){
-            log.warn("外部联系人不存在 userId: {}, uuid: {}, serverId: {}, corpId: {}, qwUserId: {}", userId, uuid, qwUser.getServerId(), qwUser.getCorpId(), qwUser.getQwUserId());
+        if (qwUser.getCompanyUserId() == null) {
+            log.warn("企微用户未归属销售 qwUserId: {}", qwUserId);
+            return null;
+        }
+
+        // 获取发送人
+        QwImUserDTO sender = getUserByVid(senderVid, uuid, serverId, qwUser.getCorpId(), qwUser.getQwUserId());
+        if (Objects.isNull(sender)) {
+            log.warn("sender用户不存在 senderVid: {}", senderVid);
             return null;
         }
 
+        // 获取接收者
+        QwImUserDTO receiver = getUserByVid(receiverVid, uuid, serverId, qwUser.getCorpId(), qwUser.getQwUserId());
+
         // 查询会话
-        QwSession qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(qwExternalContact.getId(), qwUser.getId());
+        QwSession qwSession = null;
+        if (isRoom) {
+            qwSession = getGroupQwSession(chatId, chatAvatar, qwUser);
+        } else {
+            QwImUserDTO extUser = null;
+            Long extWxId = null;
+
+            // 获取外部联系人
+            if (sender.getUserType() == 1) {
+                extUser = sender;
+                extWxId = senderVid;
+            }
+            if (receiver != null && receiver.getUserType() == 1) {
+                extUser = receiver;
+                extWxId = receiverVid;
+            }
+            if (extUser != null) {
+                qwSession = getQwSession(extUser, qwUser, extWxId);
+            }
+        }
+
         if (qwSession == null) {
-            qwSession = new QwSession();
-            String chatId = UUID.randomUUID().toString();
-            qwSession.setChatId(chatId);
-            qwSession.setCorpId(qwUser.getCorpId());
-            qwSession.setQwExtWxId(String.valueOf(userId));
-            qwSession.setQwExtId(qwExternalContact.getId().toString());
-            qwSession.setQwUserId(qwUser.getId().toString());
-            qwSession.setStatus(1);
-            qwSession.setAvatar(qwExternalContact.getAvatar());
-            qwSession.setNickName(qwExternalContact.getName());
-            qwSession.setCompanyId(qwUser.getCompanyId());
-            qwSession.setCompanyUserId(qwUser.getCompanyUserId());
-            qwSession.setCreateTime(new Date());
-            qwSession.setUpdateTime(new Date());
-            qwSessionMapper.insertQwSession(qwSession);
-        }else {
-            qwSession.setUpdateTime(new Date());
-            qwSession.setNickName(qwExternalContact.getName());
-            qwSessionMapper.updateQwSession(qwSession);
+            log.warn("获取session失败 senderVid: {}, receiverVid: {}", senderVid, receiverVid);
+            return null;
         }
 
         // 保存聊天消息
         QwMsg qwMsg = new QwMsg();
         qwMsg.setContent(content);
         qwMsg.setSessionId(qwSession.getSessionId());
-        qwMsg.setSendType(sendType);
+        qwMsg.setSendType(sender.getUserType());
         qwMsg.setCompanyId(qwUser.getCompanyId());
         qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
         qwMsg.setMsgType(msgType);
         qwMsg.setMsgJson(json);
         qwMsg.setStatus(0);
         qwMsg.setQwUserId(qwSession.getQwUserId());
-        qwMsg.setQwExtId(qwSession.getQwExtId());
-        qwMsg.setAvatar(qwExternalContact.getAvatar());
-        qwMsg.setNickName(qwExternalContact.getName());
         qwMsg.setCreateTime(new Date());
+        if (sender.getUserType() == 1) {
+            qwMsg.setQwExtId(sender.getUserId().toString());
+        }
+        qwMsg.setAvatar(sender.getAvatar());
+        qwMsg.setNickName(sender.getUserName());
+        qwMsg.setQwMsgId(qwMsgId);
+        qwMsg.setQwAppInfo(qwAppInfo);
         qwMsgMapper.insertQwMsg(qwMsg);
         log.debug("保存企微聊天记录 msgId: {}", qwMsg.getMsgId());
 
+        String type = "text";
+        MsgType messageType = MsgType.getMsgType(msgType);
+        if (Objects.nonNull(messageType)){
+            type = messageType.getValue();
+        }
+
+        qwSession.setLastMsgId(qwMsg.getMsgId());
+        qwSession.setLastMsgType(type);
+        qwSession.setLastSendTime(qwMsg.getCreateTime().getTime());
+        qwSession.setLastContent(qwMsg.getContent());
+        qwSessionMapper.updateQwSession(qwSession);
+
         // 组装返回消息结构
         QwMessageListVO listVO = new QwMessageListVO();
         QWFromUser qwFromUser = new QWFromUser();
-        if (sendType == 1) {
-            qwFromUser.setId(Long.parseLong(qwMsg.getQwExtId()));
-            qwFromUser.setAvatar(qwMsg.getAvatar());
-            qwFromUser.setDisplayName(qwMsg.getNickName());
-        }else if(sendType == 2){
-            qwFromUser.setId(Long.parseLong(qwMsg.getQwUserId()));
-            qwFromUser.setDisplayName(qwUser.getQwUserName());
-            qwFromUser.setAvatar(qwUser.getAvatar());
+        qwFromUser.setId(sender.getUserId());
+        qwFromUser.setAvatar(sender.getAvatar());
+        qwFromUser.setDisplayName(sender.getUserName());
+
+        listVO.setCompanyUserId(qwUser.getCompanyUserId());
+        listVO.setType(type);
+        listVO.setStatus("succeed");
+        listVO.setExtId(qwMsg.getQwExtId());
+        listVO.setFromUser(qwFromUser);
+        listVO.setSendTime(qwMsg.getCreateTime().getTime());
+        listVO.setId(qwMsg.getMsgId().toString());
+        listVO.setContent(qwMsg.getContent());
+        listVO.setToContactId(String.valueOf(qwSession.getSessionId()));
+        listVO.setAppKey(qwUser.getAppKey());
+        return listVO;
+    }
+
+    /**
+     * 保存企微聊天信息
+     */
+    @DataSource(DataSourceType.SHARDING)
+    @Override
+    public QwMessageListVO saveQwMsg(Long qwUserId, String exId, String content, int msgType, boolean isRoom, Long qwMsgId, String qwAppInfo) {
+        QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
+        if (Objects.isNull(qwUser)) {
+            log.warn("企微用户不存在 qwUserId: {}", qwUserId);
+            return null;
+        }
+
+        // 查询会话
+        QwSession qwSession;
+        if (isRoom) {
+            qwSession = qwSessionMapper.selectQwSessionByChatIdAndQwUserId(exId, qwUser.getId());
+        } else {
+            QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(exId, qwUser.getCorpId(), qwUser.getQwUserId());
+            if (Objects.isNull(externalContact)) {
+                log.warn("企微外部联系人不存在 extId: {}", exId);
+                return null;
+            }
+            qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(externalContact.getId(), qwUser.getId());
         }
 
-        listVO.setCompanyId(qwUser.getCompanyId());
+        if (qwSession == null) {
+            log.warn("获取session失败 qwUserId: {}, extId: {}", qwUserId, exId);
+            return null;
+        }
+
+        // 保存聊天消息
+        QwMsg qwMsg = new QwMsg();
+        qwMsg.setContent(content);
+        qwMsg.setSessionId(qwSession.getSessionId());
+        qwMsg.setSendType(2);
+        qwMsg.setCompanyId(qwUser.getCompanyId());
+        qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
+        qwMsg.setMsgType(msgType);
+        qwMsg.setMsgJson(content);
+        qwMsg.setStatus(0);
+        qwMsg.setQwUserId(qwSession.getQwUserId());
+        qwMsg.setCreateTime(new Date());
+        qwMsg.setAvatar(qwUser.getAvatar());
+        qwMsg.setNickName(qwUser.getQwUserName());
+        qwMsg.setQwMsgId(qwMsgId);
+        qwMsg.setQwAppInfo(qwAppInfo);
+        qwMsgMapper.insertQwMsg(qwMsg);
+        log.debug("保存企微聊天记录 msgId: {}", qwMsg.getMsgId());
+
         String type = "text";
         MsgType messageType = MsgType.getMsgType(msgType);
         if (Objects.nonNull(messageType)){
             type = messageType.getValue();
         }
 
+        qwSession.setLastMsgId(qwMsg.getMsgId());
+        qwSession.setLastMsgType(type);
+        qwSession.setLastSendTime(qwMsg.getCreateTime().getTime());
+        qwSession.setLastContent(qwMsg.getContent());
+        qwSessionMapper.updateQwSession(qwSession);
+
+        // 组装返回消息结构
+        QwMessageListVO listVO = new QwMessageListVO();
+        QWFromUser qwFromUser = new QWFromUser();
+        qwFromUser.setId(qwUser.getId());
+        qwFromUser.setAvatar(qwUser.getAvatar());
+        qwFromUser.setDisplayName(qwUser.getQwUserName());
+
+        listVO.setCompanyUserId(qwUser.getCompanyUserId());
         listVO.setType(type);
         listVO.setStatus("succeed");
         listVO.setExtId(qwMsg.getQwExtId());
@@ -2264,6 +2434,113 @@ public class AiHookServiceImpl implements AiHookService {
         return listVO;
     }
 
+    /**
+     * 获取单聊session
+     */
+    private QwSession getQwSession(QwImUserDTO extUser, QwUser qwUser, Long extWxId) {
+        QwSession qwSession;
+        qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(extUser.getUserId(), qwUser.getId());
+        String firstLetter = PinYinUtil.getFirstLetter(extUser.getUserName());
+        if (qwSession == null) {
+
+            RLock lock = redissonClient.getLock("addSession:" + extUser.getUserId() + ":" + qwUser.getId());
+            try {
+                boolean tryLock = lock.tryLock(2, 3, TimeUnit.SECONDS);
+                if (!tryLock) {
+                    log.warn("添加单聊Session获取锁失败");
+                    return null;
+                }
+
+                qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(extUser.getUserId(), qwUser.getId());
+                if (qwSession == null) {
+                    qwSession = new QwSession();
+                    String chatId = UUID.randomUUID().toString();
+                    qwSession.setChatId(chatId);
+                    qwSession.setCorpId(qwUser.getCorpId());
+                    qwSession.setQwExtWxId(String.valueOf(extWxId));
+                    qwSession.setQwExtId(extUser.getUserId().toString());
+                    qwSession.setQwUserId(qwUser.getId().toString());
+                    qwSession.setStatus(1);
+                    qwSession.setAvatar(extUser.getAvatar());
+                    qwSession.setNickName(extUser.getUserName());
+                    qwSession.setCompanyId(qwUser.getCompanyId());
+                    qwSession.setCompanyUserId(qwUser.getCompanyUserId());
+                    qwSession.setCreateTime(new Date());
+                    qwSession.setUpdateTime(new Date());
+                    qwSession.setIsRoom(0);
+                    qwSession.setFirstLetter(firstLetter);
+                    qwSessionMapper.insertQwSession(qwSession);
+                }
+            } catch (InterruptedException e) {
+                log.warn("获取锁失败 err: {}", e.getMessage(), e);
+                return null;
+            } finally {
+                lock.unlock();
+            }
+        }else {
+            qwSession.setUpdateTime(new Date());
+            qwSession.setNickName(extUser.getUserName());
+            qwSession.setFirstLetter(firstLetter);
+            qwSessionMapper.updateQwSession(qwSession);
+        }
+        return qwSession;
+    }
+
+    /**
+     * 获取群聊session
+     */
+    private QwSession getGroupQwSession(String chatId, String chatAvatar, QwUser qwUser) {
+        QwSession qwSession;
+        QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(chatId);
+        if (Objects.isNull(qwGroupChat)){
+            log.warn("群聊不存在 serverId: {}, corpId: {}, qwUserId: {}, chatId: {}", qwUser.getServerId(), qwUser.getCorpId(), qwUser.getQwUserId(), chatId);
+            return null;
+        }
+
+        qwSession = qwSessionMapper.selectQwSessionByChatIdAndQwUserId(chatId, qwUser.getId());
+        String firstLetter = PinYinUtil.getFirstLetter(qwGroupChat.getName());
+        if (qwSession == null) {
+            RLock lock = redissonClient.getLock("addSession:" + qwGroupChat.getChatId() + ":" + qwUser.getId());
+            try {
+                boolean tryLock = lock.tryLock(2, 3, TimeUnit.SECONDS);
+                if (!tryLock) {
+                    log.warn("添加群聊Session获取锁失败");
+                    return null;
+                }
+
+                qwSession = qwSessionMapper.selectQwSessionByChatIdAndQwUserId(chatId, qwUser.getId());
+                if (qwSession == null) {
+                    qwSession = new QwSession();
+                    qwSession.setChatId(chatId);
+                    qwSession.setCorpId(qwGroupChat.getCorpId());
+                    qwSession.setQwUserId(qwUser.getId().toString());
+                    qwSession.setStatus(1);
+                    qwSession.setAvatar(chatAvatar);
+                    qwSession.setNickName(qwGroupChat.getName());
+                    qwSession.setCompanyId(qwUser.getCompanyId());
+                    qwSession.setCreateTime(new Date());
+                    qwSession.setUpdateTime(new Date());
+                    qwSession.setIsRoom(1);
+                    qwSession.setFirstLetter(firstLetter);
+                    qwSessionMapper.insertQwSession(qwSession);
+                }
+            } catch (InterruptedException e) {
+                log.warn("获取锁失败 err: {}", e.getMessage(), e);
+                return null;
+            } finally {
+                lock.unlock();
+            }
+        }else {
+            qwSession.setUpdateTime(new Date());
+            qwSession.setQwUserId(qwUser.getId().toString());
+            qwSession.setNickName(qwGroupChat.getName());
+            qwSession.setAvatar(chatAvatar);
+            qwSession.setFirstLetter(firstLetter);
+            qwSessionMapper.updateQwSession(qwSession);
+        }
+        return qwSession;
+    }
+
     /**
      * 查询外部联系人
      * @param userId    用户ID
@@ -2277,4 +2554,138 @@ public class AiHookServiceImpl implements AiHookService {
         return qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(getExtId(userId, uuid, serverId), corpId, qwUserId);
     }
 
+    /**
+     * 根据vid获取用户信息
+     */
+    private QwImUserDTO getUserByVid(Long vid, String uuid, Long serverId, String corpId, String qwUserId) {
+        if (vid == 0) {
+            return null;
+        }
+
+        // 外部联系人
+        if (vid > 2000000000000000L) {
+            QwExternalContact qwExternalContact = getExternalContact(vid, uuid, serverId, corpId, qwUserId);
+            if (Objects.isNull(qwExternalContact)) {
+                log.warn("外部联系人不存在 vid: {}, uuid: {}, serverId: {}, corpId: {}, qwUserId: {}", vid, uuid, serverId, corpId, qwUserId);
+                return null;
+            }
+
+            QwImUserDTO userDTO = new QwImUserDTO();
+            userDTO.setUserId(qwExternalContact.getId());
+            userDTO.setUserName(qwExternalContact.getName());
+            userDTO.setAvatar(qwExternalContact.getAvatar());
+            userDTO.setUserType(1);
+            return userDTO;
+        }
+        // 企微用户
+        else {
+            QwUser qwUser = qwUserService.selectQwUserByVid(vid);
+            if (Objects.isNull(qwUser)){
+                log.warn("企微用户不存在 vid: {}", vid);
+                return null;
+            }
+
+            QwImUserDTO userDTO = new QwImUserDTO();
+            userDTO.setUserId(qwUser.getId());
+            userDTO.setUserName(qwUser.getQwUserName());
+            userDTO.setAvatar(qwUser.getAvatar());
+            userDTO.setUserType(2);
+            return userDTO;
+        }
+    }
+
+
+    /**
+     * 修改消息
+     */
+    @DataSource(DataSourceType.SHARDING)
+    @Override
+    public QwMessageListVO updateQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String uuid, boolean isRoom, String chatId, String qwAppInfo) {
+        QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
+        if (Objects.isNull(qwUser)) {
+            log.warn("企微用户不存在 qwUserId: {}", qwUserId);
+            return null;
+        }
+
+        // 获取发送人
+        QwImUserDTO sender = getUserByVid(senderVid, uuid, serverId, qwUser.getCorpId(), qwUser.getQwUserId());
+        if (Objects.isNull(sender)) {
+            log.warn("sender用户不存在 senderVid: {}", senderVid);
+            return null;
+        }
+
+        // 获取接收者
+        QwImUserDTO receiver = getUserByVid(receiverVid, uuid, serverId, qwUser.getCorpId(), qwUser.getQwUserId());
+
+        // 查询会话
+        QwSession qwSession = null;
+        if (isRoom) {
+            QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(chatId);
+            if (Objects.isNull(qwGroupChat)){
+                log.warn("群聊不存在 serverId: {}, corpId: {}, qwUserId: {}, chatId: {}", qwUser.getServerId(), qwUser.getCorpId(), qwUser.getQwUserId(), chatId);
+                return null;
+            }
+
+            qwSession = qwSessionMapper.selectQwSessionByChatIdAndQwUserId(chatId, qwUser.getId());
+        } else {
+            QwImUserDTO extUser = null;
+
+            // 获取外部联系人
+            if (sender.getUserType() == 1) {
+                extUser = sender;
+            }
+            if (receiver != null && receiver.getUserType() == 1) {
+                extUser = receiver;
+            }
+            if (extUser != null) {
+                qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(extUser.getUserId(), qwUser.getId());
+            }
+        }
+
+        if (qwSession == null) {
+            log.warn("获取session失败 senderVid: {}, receiverVid: {}", senderVid, receiverVid);
+            return null;
+        }
+
+        QwMsg qwMsg = qwMsgMapper.selectQwMsgBySessionIdAndQwAppInfo(qwSession.getSessionId(), qwAppInfo);
+        if (qwMsg == null) {
+            log.warn("消息不存在 sessionId: {}, appInfo: {}", qwSession.getSessionId(), qwAppInfo);
+            return null;
+        }
+
+        qwMsg.setMsgType(6);
+        qwMsgMapper.updateQwMsgByMsgIdAndSessionId(qwMsg);
+
+        String type = "text";
+        MsgType messageType = MsgType.getMsgType(qwMsg.getMsgType());
+        if (Objects.nonNull(messageType)){
+            type = messageType.getValue();
+        }
+
+        qwSession.setLastMsgId(qwMsg.getMsgId());
+        qwSession.setLastMsgType(type);
+        qwSession.setLastSendTime(qwMsg.getCreateTime().getTime());
+        qwSession.setLastContent("");
+        qwSessionMapper.updateQwSession(qwSession);
+
+        // 组装返回消息结构
+        QwMessageListVO listVO = new QwMessageListVO();
+        QWFromUser qwFromUser = new QWFromUser();
+        qwFromUser.setId(sender.getUserId());
+        qwFromUser.setAvatar(sender.getAvatar());
+        qwFromUser.setDisplayName(sender.getUserName());
+
+        listVO.setCompanyUserId(qwUser.getCompanyUserId());
+        listVO.setType(type);
+        listVO.setStatus("succeed");
+        listVO.setExtId(qwMsg.getQwExtId());
+        listVO.setFromUser(qwFromUser);
+        listVO.setSendTime(qwMsg.getCreateTime().getTime());
+        listVO.setId(qwMsg.getMsgId().toString());
+        listVO.setContent(qwMsg.getContent());
+        listVO.setToContactId(String.valueOf(qwSession.getSessionId()));
+        listVO.setAppKey(qwUser.getAppKey());
+        return listVO;
+    }
+
 }

+ 8 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserOperationLogMapper.java

@@ -6,6 +6,8 @@ import com.fs.his.domain.FsUserOperationLog;
 import com.fs.his.param.FsUserOperationLogQueryParam;
 import com.fs.his.vo.FsUserOperationLogPageVo;
 import com.fs.his.vo.FsUserOperationLogVo;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 
 /**
  * 用户操作日志Mapper接口
@@ -63,4 +65,10 @@ public interface FsUserOperationLogMapper extends BaseMapper<FsUserOperationLog>
     int deleteFsUserOperationLogByLogIds(Long[] logIds);
 
     List<FsUserOperationLogVo> selectFsUserOperationLogByList(FsUserOperationLog fsUserOperationLog);
+
+    /**
+     * 查询用户历史同行为操作
+     */
+    @Select("select * from fs_user_operation_log where user_id = #{userId} and operation_type = #{operationType} and param = #{param} limit 1")
+    FsUserOperationLog selectUserOldOperationLog(@Param("userId") Long userId, @Param("operationType") String operationType, @Param("param") String param);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserOperationLogService.java

@@ -67,4 +67,9 @@ public interface IFsUserOperationLogService extends IService<FsUserOperationLog>
     int deleteFsUserOperationLogByLogId(Long logId);
 
     List<String> getOperationType();
+
+    /**
+     * 新增或修改用户行为
+     */
+    void saveOrUpdateOperationLog(FsUserOperationLog operationLog);
 }

+ 18 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserOperationLogServiceImpl.java

@@ -8,6 +8,8 @@ import java.util.stream.Stream;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.DateUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.utils.StringUtils;
@@ -157,6 +159,22 @@ public class FsUserOperationLogServiceImpl extends ServiceImpl<FsUserOperationLo
         return labels;
     }
 
+    /**
+     * 新增或修改用户行为
+     */
+    @DataSource(DataSourceType.SHARDING)
+    @Override
+    public void saveOrUpdateOperationLog(FsUserOperationLog operationLog) {
+        FsUserOperationLog oldOperationLog = baseMapper.selectUserOldOperationLog(operationLog.getUserId(), operationLog.getOperationType(), operationLog.getParam());
+        if (oldOperationLog == null) {
+            baseMapper.insertFsUserOperationLog(operationLog);
+        } else {
+            oldOperationLog.setCreateTime(operationLog.getCreateTime());
+            oldOperationLog.setDetails(operationLog.getDetails());
+            baseMapper.updateFsUserOperationLog(oldOperationLog);
+        }
+    }
+
     @Override
     public List<FsUserOperationLogVo> selectFsUserOperationLogByList(FsUserOperationLog fsUserOperationLog) {
         List<FsUserOperationLogVo> list = baseMapper.selectFsUserOperationLogByList(fsUserOperationLog);

+ 1 - 0
fs-service/src/main/java/com/fs/ipad/vo/BaseVo.java

@@ -17,6 +17,7 @@ public class BaseVo{
     private boolean isRoom;
     private Long qwUserId;
 
+
     public void setBase(BaseVo vo){
         this.uuid = vo.getUuid();
         this.serverId = vo.getServerId();

+ 5 - 0
fs-service/src/main/java/com/fs/qw/domain/QwMsg.java

@@ -75,6 +75,11 @@ public class QwMsg extends BaseEntity implements Serializable
     @Excel(name = "昵称")
     private String avatar;
 
+    /** 企微消息ID **/
+    private Long qwMsgId;
+
+    /** 企微消息info **/
+    private String qwAppInfo;
 
 
 }

+ 17 - 0
fs-service/src/main/java/com/fs/qw/domain/QwSession.java

@@ -65,5 +65,22 @@ public class QwSession extends BaseEntity implements Serializable
 
     private String qwExtWxId;//企微外部联系人wxid
 
+    /** 是否群聊 0否 1是 **/
+    private Integer isRoom;
+
+    /** 首字母 **/
+    private String firstLetter;
+
+    /** 最后一条消息id **/
+    private Long lastMsgId;
+
+    /** 最后一条消息类型 **/
+    private String lastMsgType;
+
+    /** 最后一条消息发送时间 **/
+    private Long lastSendTime;
+
+    /** 最后一条消息内容 **/
+    private String lastContent;
 
 }

+ 23 - 0
fs-service/src/main/java/com/fs/qw/dto/QwImUserDTO.java

@@ -0,0 +1,23 @@
+package com.fs.qw.dto;
+
+import lombok.Data;
+
+@Data
+public class QwImUserDTO {
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    /**
+     * 用户类型 1外部联系人 2企微用户
+     */
+    private Integer userType;
+    /**
+     * 昵称
+     */
+    private String userName;
+    /**
+     * 头像
+     */
+    private String avatar;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/qw/enums/MsgType.java

@@ -11,6 +11,7 @@ public enum MsgType {
     EMOTION_DYNAMIC(3, "emotionDynamic"),
     VOICE(4, "voice"),
     MINI_PROGRAM(5, "miniprogram"),
+    CANCEL(6, "cancel"),
     ;
 
     private final Integer code;

+ 110 - 9
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -48,6 +48,8 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
 
     public List<QwExternalContact> selectQwExternalContactByIds(@Param("ids") List<Long> ids);
 
+    public List<QwExternalContact> selectQwExternalContactByIdsStatus(@Param("ids") List<Long> ids);
+
     @Select("SELECT id,stage_status,name,fs_user_id from qw_external_contact where id=#{id}")
     public QwExternalContact selectQwExternalContactByIdForStageStatus(@Param("id") Long id);
 
@@ -540,15 +542,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
      * */
     QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam);
 
-    void updateJoinGroup(List<Long> longs);
-
-
-    List<QwExternalContact> getGroupChatUserByChatIdAndUserName(@Param("userId")String userId,@Param("userName")String userName,@Param("corpId") String corpId,@Param("chatId") String chatId);
-    @Select("select * from qw_external_contact where unionid = #{unionID} order by create_time asc limit 1 ")
-    QwExternalContact selectQwExternalByUnionID(String unionId);
-
-    @Select("SELECT id,external_user_id,name,avatar,remark,description,fs_user_id FROM qw_external_contact " +
-            " WHERE id = #{qwExternalContactId}")
+    @Select("SELECT * FROM qw_external_contact WHERE id = #{qwExternalContactId}")
     QwExternalContact getQwExternalContactDetailsById(Long qwExternalContactId);
 
     /**
@@ -562,4 +556,111 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
      * 根据企微用户ID查询联系人列表
      */
     List<QwContactVO> getContactListByQwUserId(@Param("qwUserId") Long qwUserId, @Param("name") String name);
+
+    /**
+     * 查询群组指定企微关联外部联系人
+     */
+    List<QwExternalContact> selectGroupContactByChatIdAndQwUserId(@Param("chatId") String chatId, @Param("qwUserId") Long qwUserId);
+
+    void updateJoinGroup(List<Long> longs);
+
+
+    List<QwExternalContact> getGroupChatUserByChatIdAndUserName(@Param("userId")String userId,@Param("userName")String userName,@Param("corpId") String corpId,@Param("chatId") String chatId);
+
+    @Select("select * from qw_external_contact where unionid = #{unionID} order by create_time asc  ")
+    List<QwExternalContact> selectQwExternalByUnionID(String unionId);
+
+    @Select("<script> " +
+            "WITH contact_analysis AS (\n" +
+            "    SELECT \n" +
+            "        id,\n" +
+            "        external_user_id,\n" +
+            "        corp_id,\n" +
+            "        fs_user_id,\n" +
+            "        COUNT(CASE WHEN fs_user_id IS NOT NULL THEN 1 END) OVER (\n" +
+            "            PARTITION BY external_user_id, corp_id\n" +
+            "        ) as has_valid_fs_user,\n" +
+            "        COUNT(CASE WHEN fs_user_id IS NULL THEN 1 END) OVER (\n" +
+            "            PARTITION BY external_user_id, corp_id\n" +
+            "        ) as has_null_fs_user,\n" +
+            "        COUNT(*) OVER (\n" +
+            "            PARTITION BY external_user_id, corp_id\n" +
+            "        ) as total_records\n" +
+            "    FROM qw_external_contact\n" +
+            "    WHERE external_user_id IS NOT NULL \n" +
+            "          AND corp_id IS NOT NULL\n" +
+            "          AND create_time >='2025-10-01 00:00:00'\n" +
+            "     \n" +
+            "),\n" +
+            "filtered_contacts AS (\n" +
+            "    SELECT \n" +
+            "        id,\n" +
+            "        external_user_id,\n" +
+            "        corp_id,\n" +
+            "        fs_user_id\n" +
+            "    FROM contact_analysis\n" +
+            "    WHERE total_records >= 2\n" +
+            "        AND has_valid_fs_user >= 1\n" +
+            "        AND has_null_fs_user >= 1\n" +
+            "        AND fs_user_id IS NULL\n" +
+            ")\n" +
+            "SELECT \n" +
+            "    corp_id\n" +
+            "FROM filtered_contacts\n" +
+            "GROUP BY corp_id" +
+            "</script> ")
+    public List<String> selectQwExternalContactMandatoryRegistration();
+
+    @Select("<script>" +
+            "SELECT t1.id,t2.valid_fs_user_id as fs_user_id \n" +
+            "FROM qw_external_contact t1\n" +
+            "JOIN (\n" +
+            "    SELECT \n" +
+            "        external_user_id,\n" +
+            "        corp_id,\n" +
+            "        MAX(fs_user_id) as valid_fs_user_id\n" +
+            "    FROM qw_external_contact\n" +
+            "    WHERE external_user_id IS NOT NULL \n" +
+            "        AND corp_id IS NOT NULL \n" +
+            "        AND corp_id = #{corpId}\n" +
+            "        AND fs_user_id IS NOT NULL\n" +
+            "        AND create_time >='2025-10-01 00:00:00' \n" +
+            "    GROUP BY external_user_id, corp_id\n" +
+            ") t2 ON t1.external_user_id = t2.external_user_id \n" +
+            "    AND t1.corp_id = t2.corp_id\n" +
+            "JOIN (\n" +
+            "    SELECT \n" +
+            "        external_user_id,\n" +
+            "        corp_id\n" +
+            "    FROM qw_external_contact\n" +
+            "    WHERE external_user_id IS NOT NULL \n" +
+            "        AND corp_id IS NOT NULL \n" +
+            "        AND corp_id = #{corpId}\n" +
+            "        AND create_time >='2025-10-01 00:00:00' \n" +
+            "    GROUP BY external_user_id, corp_id\n" +
+            "    HAVING COUNT(*) >= 2\n" +
+            "        AND SUM(CASE WHEN fs_user_id IS NOT NULL THEN 1 ELSE 0 END) >= 1\n" +
+            "        AND SUM(CASE WHEN fs_user_id IS NULL THEN 1 ELSE 0 END) >= 1\n" +
+            ") t3 ON t1.external_user_id = t3.external_user_id \n" +
+            "    AND t1.corp_id = t3.corp_id\n" +
+            "WHERE t1.fs_user_id IS NULL\n" +
+            "    AND t1.corp_id = #{corpId}\n" +
+            "</script>")
+    public List<QwMandatoryRegistrParam> selectQwExternalContactMandatoryRegistrationByIds(@Param("corpId") String corpId);
+
+
+
+    @Update("<script>" +
+            "UPDATE qw_external_contact " +
+            "SET fs_user_id = CASE " +
+            "<foreach collection='map' item='item'>" +
+            "WHEN id = #{item.id} THEN #{item.fsUserId} " +
+            "</foreach>" +
+            "ELSE fs_user_id END " +
+            "WHERE id IN " +
+            "<foreach collection='map' item='item' open='(' separator=',' close=')'>" +
+            "#{item.id}" +
+            "</foreach>" +
+            "</script>")
+    public int batchUpdateQwExternalContactMandatoryRegistration(@Param("map") List<QwMandatoryRegistrParam> batchList);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java

@@ -2,6 +2,7 @@ package com.fs.qw.mapper;
 
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.param.QwGroupChatParam;
+import com.fs.qw.vo.QwContactVO;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
 import com.fs.qw.vo.QwGroupChatTransferVO;
 import com.fs.qw.vo.QwGroupChatVO;
@@ -129,5 +130,10 @@ public interface QwGroupChatMapper
      */
     List<QwGroupChatTransferVO> selectQwGroupChatTransferList(QwGroupChatParam qwGroupChat);
 
+    /**
+     * 根据企微用户ID查询群组列表
+     */
+    List<QwContactVO> getGroupListByQwUserId(@Param("qwUserId") Long qwUserId, @Param("name") String name);
+
     List<QwGroupChat> selectSopAndQwUser(@Param("qwUserId") String qwUserId, @Param("sopId") String sopId);
 }

+ 16 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwMsgMapper.java

@@ -63,6 +63,20 @@ public interface QwMsgMapper  extends BaseMapper<QwMsg>
      */
     public int deleteQwMsgByMsgIds(Long[] msgIds);
 
-    @Select("select * from qw_msg where session_id = #{sessionId} order by msg_id desc ")
-    List<QwMsg> selectQwMsgListBySession(@Param("sessionId") String conversationId);
+    /**
+     * 根据会话ID和QwAppInfo查询消息
+     */
+    @Select("select * from qw_msg where session_id = #{sessionId} and qw_app_info = #{qwAppInfo}")
+    QwMsg selectQwMsgBySessionIdAndQwAppInfo(@Param("sessionId") Long sessionId, @Param("qwAppInfo") String qwAppInfo);
+
+    /**
+     * 根据会话ID和消息ID查询消息
+     */
+    @Select("select * from qw_msg where msg_id = #{msgId} and session_id = #{sessionId}")
+    QwMsg selectQwMsgByMsgIdAndSessionId(@Param("msgId") Long msgId, @Param("sessionId") Long sessionId);
+
+    /**
+     * 修改企微聊天记录
+     */
+    int updateQwMsgByMsgIdAndSessionId(QwMsg qwMsg);
 }

+ 22 - 5
fs-service/src/main/java/com/fs/qw/mapper/QwSessionMapper.java

@@ -7,6 +7,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 企微会话Mapper接口
@@ -64,14 +65,30 @@ public interface QwSessionMapper extends BaseMapper<QwSession>
      */
     public int deleteQwSessionBySessionIds(Long[] sessionIds);
 
-
-    @Select("select * from qw_session where qw_ext_id = #{qwExtId} and qw_user_id = #{qwUserId}")
+    /**
+     * 查询单聊会话
+     */
+    @Select("select * from qw_session where qw_ext_id = #{qwExtId} and qw_user_id = #{qwUserId, jdbcType=VARCHAR} and is_room = 0")
     QwSession selectQwSessionByExtIdAndQwUserId(@Param("qwExtId") Long qwExtId, @Param("qwUserId") Long id);
 
     /**
      * 根据企微用户ID查询会话列表
-     * @param qwUserId  企微用户ID
-     * @return  list
      */
-    List<QwContactListVO> selectContactListByQwUserId(@Param("qwUserId") Long qwUserId);
+    List<QwContactListVO> selectQwConversationByMap(@Param("params") Map<String, Object> params);
+
+    /**
+     * 查询群聊会话
+     */
+    @Select("select * from qw_session where chat_id = #{chatId} and qw_user_id = #{qwUserId} and is_room = 1")
+    QwSession selectQwSessionByChatIdAndQwUserId(@Param("chatId") String chatId, @Param("qwUserId") Long id);
+
+    /**
+     * 根据群组ID查询会话
+     */
+    QwSession selectQwSessionByGroupChatId(@Param("groupChatId") String groupChatId);
+
+    /**
+     * 根据外部联系人ID查询会话
+     */
+    QwSession selectQwSessionByContactId(@Param("contactId") Long contactId);
 }

+ 9 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwTagMapper.java

@@ -107,4 +107,13 @@ public interface QwTagMapper
             "</script>")
     List<ExternalContactTagVO> selectQwTagListByTagIds(@Param("tagIds") List<String> tagIds);
 
+    /**
+     * 根据tagIds查询标签
+     */
+    List<QwTagVO> selectQwTagVOListByTagIds(@Param("tagIds") List<String> tagIds);
+
+    /**
+     * 根据ids查询标签列表
+     */
+    List<QwTag> selectQwTagListByCorpIdAndIds(@Param("ids") List<Long> ids, @Param("corpId") String corpId);
 }

+ 10 - 4
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -47,6 +47,9 @@ public interface QwUserMapper extends BaseMapper<QwUser>
 
     public List<QwUser> batchGetQwUser(@Param("list") List<QwUserKeyDTO> qwUserId);
 
+    @Select("select * from qw_user where vid = #{vid} order by id desc limit 1")
+    QwUser selectQwUserByVid(@Param("vid") Long vid);
+
     @Select("select company_user_id,company_id,welcome_text,qw_user_name,send_msg_type from qw_user where id = #{id}")
     public QwUser selectQwUserByIdByWeComeText(@Param("id") Long id);
 
@@ -461,6 +464,12 @@ public interface QwUserMapper extends BaseMapper<QwUser>
     @Select("  select company_id from qw_user where qw_user_id = #{owner} and corp_id = #{corpId}  limit 1")
     Long getCompanyIdByCorpIdAndOwner(@Param("corpId")String corpId, @Param("owner")String owner);
 
+    /**
+     * 根据销售ID查询企微用户列表
+     */
+    List<QwUserVO> selectQwUserVoListByCompanyUserId(@Param("companyUserId") Long companyUserId);
+
+
     @Select("<script>" +
             "select * from qw_user where qw_user_id in " +
             "<foreach collection='qwUserIdList' item='item' open='(' separator=',' close=')'> " +
@@ -469,9 +478,6 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "</script>")
     List<QwUser> selectQwUserByIds(@Param("qwUserIdList") List<Long> qwUserIdList);
 
-    /**
-     * 根据销售ID查询企微用户列表
-     */
-    List<QwUserVO> selectQwUserVoListByCompanyUserId(@Param("companyUserId") Long companyUserId);
+
 
 }

+ 9 - 0
fs-service/src/main/java/com/fs/qw/param/QwMandatoryRegistrParam.java

@@ -0,0 +1,9 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+@Data
+public class QwMandatoryRegistrParam {
+    private Long id;
+    private Long fsUserId;
+}

+ 3 - 1
fs-service/src/main/java/com/fs/qw/param/QwMsgSendParam.java

@@ -9,11 +9,13 @@ public class QwMsgSendParam implements Serializable {
     private Long sessionId;
     private String content;//内容
     private String appKey;
-    // 消息类型 1文本 2图片 3动态表情 4语音 5小程序
+    // 消息类型 1文本 2图片 3动态表情 4语音 5小程序 6撤销
     private Integer msgType;
     // 小程序标题
     private String title;
     // 小程序图片
     private String image;
+    // 消息ID
+    private Long msgId;
 
 }

+ 1 - 1
fs-service/src/main/java/com/fs/qw/param/QwSessionParam.java

@@ -5,7 +5,7 @@ import lombok.Data;
 
 @Data
 public class QwSessionParam extends BaseQueryParam {
-    private String conversationId;
+    private Long conversationId;
     private Long userId;//企微id
     // 消息ID
     private Long msgId;

+ 17 - 3
fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java

@@ -259,9 +259,6 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
      * 根据qw_user_id+crop_id+external_user_id查询外部联系人信息
      * */
     QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam);
-
-    List<QwExternalContact> selectQwUserAndLevel(List<Long> qwUserIdList, List<String> levelList, boolean isReg);
-
     /**
      * 根据id查询外部联系人信息
      * @param qwExternalContactId id
@@ -276,4 +273,21 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
      */
     Boolean getBuyStatusByExtId(Long qwExternalContactId);
 
+    /**
+     * 外部联系人添加标签
+     */
+    void addQwExternalContactTag(Long id, List<String> addTags);
+
+    /**
+     * 外部联系人删除标签
+     */
+    void delQwExternalContactTag(Long id, List<String> delTags);
+
+    List<QwExternalContact> selectQwUserAndLevel(List<Long> qwUserIdList, List<String> levelList, boolean isReg);
+
+    List<String> selectQwExternalContactMandatoryRegistration();
+
+    List<QwMandatoryRegistrParam> selectQwExternalContactMandatoryRegistrationByIds(String corpId);
+
+    int batchUpdateQwExternalContactMandatoryRegistration(List<QwMandatoryRegistrParam> batchList);
 }

+ 5 - 7
fs-service/src/main/java/com/fs/qw/service/IQwMsgService.java

@@ -13,6 +13,7 @@ import com.fs.qw.vo.QwMessageListVO;
 import com.fs.qwHookApi.vo.QwHookMsgVO;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * 企微聊天记录Service接口
@@ -77,12 +78,11 @@ public interface IQwMsgService extends IService<QwMsg>
      * @return
      */
     List<QwMsg> getQwMessageList(Long companyId);
+
     /**
-     * 查询会话
-     * @param userId
-     * @return
+     * 查询会话列表
      */
-    List<QwContactListVO> selectQwConversationByUserId(Long userId);
+    List<QwContactListVO> selectQwConversationByMap(Map<String, Object> params);
 
     List<QwMsg> selectQwMsgBySession(QwSessionParam param);
 
@@ -92,11 +92,9 @@ public interface IQwMsgService extends IService<QwMsg>
 
     R addAiMsg(QwSession session, String content, Integer msgType,QwUser user);
 
-    List<QwUser> qwUserList(Long userId);
-
     R sendMsg(QwMsgSendParam param);
 
-    QwContactListVO selectQwSessionBycId(String conversationId, Long qwUserId);
+    QwContactListVO selectQwSessionBycId(Long conversationId, Long qwUserId);
 
     /**
      * 根据企微用户ID查询联系人列表

+ 10 - 0
fs-service/src/main/java/com/fs/qw/service/IQwTagService.java

@@ -72,4 +72,14 @@ public interface IQwTagService
     public List<String> selectQwTagListByTagIds(QwTagSearchParam param);
 
     List<QwTagVO> getTagListByUserId(ContactTagListParam param);
+
+    /**
+     * 根据tagIds查询标签
+     */
+    List<QwTagVO> selectQwTagVOListByTagIds(List<String> tagIds);
+
+    /**
+     * 根据ids查询标签列表
+     */
+    List<QwTag> selectQwTagListByCorpIdAndIds(List<Long> ids, String corpId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -208,4 +208,9 @@ public interface IQwUserService
      * 根据销售ID查询企微用户列表
      */
     List<QwUserVO> selectQwUserVoListByCompanyUserId(Long companyUserId);
+
+    /**
+     * 根据Vid查询企微用户
+     */
+    QwUser selectQwUserByVid(Long vid);
 }

+ 65 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.TypeReference;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -12,6 +13,7 @@ import com.fs.ad.enums.AdUploadType;
 import com.fs.ad.service.IAdHtmlClickLogService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.service.ICompanyConfigService;
@@ -5934,6 +5936,21 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return list(queryCondition);
     }
 
+    @Override
+    public List<String> selectQwExternalContactMandatoryRegistration() {
+        return qwExternalContactMapper.selectQwExternalContactMandatoryRegistration();
+    }
+
+    @Override
+    public List<QwMandatoryRegistrParam> selectQwExternalContactMandatoryRegistrationByIds(String corpId) {
+        return qwExternalContactMapper.selectQwExternalContactMandatoryRegistrationByIds(corpId);
+    }
+
+    @Override
+    public int batchUpdateQwExternalContactMandatoryRegistration(List<QwMandatoryRegistrParam> batchList) {
+        return qwExternalContactMapper.batchUpdateQwExternalContactMandatoryRegistration( batchList);
+    }
+
     @Override
     public R getRepeat(RepeatParam param) {
         List<QwExternalContact> list = qwExternalContactMapper.selectList(new QueryWrapper<QwExternalContact>().eq("external_user_id", param.getExternalUserId()));
@@ -5997,4 +6014,52 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     public Boolean getBuyStatusByExtId(Long qwExternalContactId) {
         return qwExternalContactMapper.getBuyStatusByExtId(qwExternalContactId);
     }
+
+    /**
+     * 外部联系人添加标签
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void addQwExternalContactTag(Long id, List<String> addTags) {
+        QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(id);
+
+        QwEditUserTagParam param = new QwEditUserTagParam();
+        param.setAdd_tag(addTags);
+        param.setUserid(externalContact.getUserId());
+        param.setExternal_userid(externalContact.getExternalUserId());
+        QwResult qwResult = qwApiService.editUserTag(param, externalContact.getCorpId());
+        if (qwResult.getErrcode() != 0) {
+            throw new ServiceException(qwResult.getErrmsg());
+        }
+
+        List<String> oldTags = JSON.parseObject(externalContact.getTagIds(), new TypeReference<List<String>>(){}.getType());
+        oldTags.addAll(addTags);
+        externalContact.setTagIds(JSON.toJSONString(oldTags));
+        qwExternalContactMapper.updateById(externalContact);
+    }
+
+    /**
+     * 外部联系人删除标签
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void delQwExternalContactTag(Long id, List<String> delTags) {
+        QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(id);
+
+        QwEditUserTagParam param = new QwEditUserTagParam();
+        param.setRemove_tag(delTags);
+        param.setUserid(externalContact.getUserId());
+        param.setExternal_userid(externalContact.getExternalUserId());
+        QwResult qwResult = qwApiService.editUserTag(param, externalContact.getCorpId());
+        if (qwResult.getErrcode() != 0) {
+            throw new ServiceException(qwResult.getErrmsg());
+        }
+
+        List<String> oldTags = JSON.parseObject(externalContact.getTagIds(), new TypeReference<List<String>>(){}.getType());
+        delTags.forEach(oldTags::remove);
+        externalContact.setTagIds(JSON.toJSONString(oldTags));
+        qwExternalContactMapper.updateById(externalContact);
+    }
+
+
 }

+ 238 - 88
fs-service/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java

@@ -8,11 +8,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.domain.R;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PinYinUtil;
 import com.fs.common.utils.StringUtils;
-import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyMiniapp;
-import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyMiniappMapper;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.mapper.FsCoursePlaySourceConfigMapper;
@@ -21,10 +21,7 @@ import com.fs.his.utils.ConfigUtil;
 import com.fs.qw.Bean.MsgBean;
 import com.fs.qw.domain.*;
 import com.fs.qw.enums.MsgType;
-import com.fs.qw.mapper.QwExternalContactMapper;
-import com.fs.qw.mapper.QwMsgMapper;
-import com.fs.qw.mapper.QwSessionMapper;
-import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwMsgSendParam;
 import com.fs.qw.param.QwSessionParam;
 import com.fs.qw.service.IQwMsgService;
@@ -35,10 +32,14 @@ import com.fs.qw.vo.QwMessageListVO;
 import com.fs.qwHookApi.vo.QwHookMsgVO;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -47,6 +48,7 @@ import java.util.stream.Collectors;
  * @author fs
  * @date 2024-12-17
  */
+@Slf4j
 @Service
 public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements IQwMsgService {
     @Autowired
@@ -67,9 +69,13 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
     @Autowired
     private CompanyMiniappMapper companyMiniappMapper;
     @Autowired
+    private FsCoursePlaySourceConfigMapper playSourceConfigMapper;
+    @Autowired
     private QwExternalContactMapper externalContactMapper;
     @Autowired
-    private FsCoursePlaySourceConfigMapper playSourceConfigMapper;
+    private QwGroupChatMapper groupChatMapper;
+    @Autowired
+    private RedissonClient redissonClient;
 
     /**
      * 查询企微聊天记录
@@ -264,7 +270,7 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
 
     @Override
     public R sendMsg(QwMsgSendParam param) {
-        if (StringUtils.isBlank(param.getContent())) {
+        if (StringUtils.isBlank(param.getContent()) && param.getMsgType() != 6) {
             return R.error("消息内容不能为空");
         }
 
@@ -282,12 +288,6 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
             return R.error("会话不存在");
         }
 
-        // 外部联系人
-        QwExternalContact qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(Long.valueOf(qwSession.getQwExtId()));
-        if (Objects.isNull(qwExternalContact)) {
-            return R.error("联系人不存在");
-        }
-
         // 企微用户
         QwUser qwUser = qwUserMapper.selectQwUserById(Long.parseLong(qwSession.getQwUserId()));
         if (Objects.isNull(qwUser)) {
@@ -296,28 +296,50 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
 
         Long serverId = qwUser.getServerId();
         String uuid = qwUser.getUid();
-        String openId = qwExternalContact.getExternalUserId();
         String sCorpId = qwUser.getCorpId();
-
-        WxWorkUserId2VidDTO params = new WxWorkUserId2VidDTO();
-        params.setOpenid(Collections.singletonList(openId));
-        params.setUuid(uuid);
-        params.setScorpid(sCorpId);
-        WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> listWxWorkResponseDTO = wxWorkService.UserId2Vid(params, serverId);
-
-        if (listWxWorkResponseDTO.getErrcode() != 0) {
-            return R.error(listWxWorkResponseDTO.getErrmsg());
+        boolean isRoom = qwSession.getIsRoom() == 1;
+
+        long sendUserId;
+        QwExternalContact qwExternalContact = null;
+        if (isRoom) {
+            WxWorkChatId2RoomIdDTO chatId2RoomIdDTO = new WxWorkChatId2RoomIdDTO();
+            chatId2RoomIdDTO.setUuid(uuid);
+            chatId2RoomIdDTO.setChatid(qwSession.getChatId());
+            WxWorkResponseDTO<WxWorkChatId2RoomIdResp> chatId2RoomIdResp = wxWorkService.chatId2RoomId(chatId2RoomIdDTO, serverId);
+            if (chatId2RoomIdResp.getErrcode() != 0) {
+                return R.error(chatId2RoomIdResp.getErrmsg());
+            }
+            sendUserId = chatId2RoomIdResp.getData().getRoom_id();
+        } else {
+            // 外部联系人
+            qwExternalContact = qwExternalContactMapper.selectQwExternalContactById(Long.valueOf(qwSession.getQwExtId()));
+            if (Objects.isNull(qwExternalContact)) {
+                return R.error("联系人不存在");
+            }
+            String openId = qwExternalContact.getExternalUserId();
+            WxWorkUserId2VidDTO params = new WxWorkUserId2VidDTO();
+            params.setOpenid(Collections.singletonList(openId));
+            params.setUuid(uuid);
+            params.setScorpid(sCorpId);
+            WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> listWxWorkResponseDTO = wxWorkService.UserId2Vid(params, serverId);
+
+            if (listWxWorkResponseDTO.getErrcode() != 0) {
+                return R.error(listWxWorkResponseDTO.getErrmsg());
+            }
+            sendUserId = listWxWorkResponseDTO.getData().get(0).getUser_id();
         }
-        long sendUserId = listWxWorkResponseDTO.getData().get(0).getUser_id();
+
         String msgJson;
 
+        Long qwMsgId = null;
+        String qwAppInfo = null;
         MsgType msgType = MsgType.getMsgType(param.getMsgType());
         // 发送消息  文本
         if (MsgType.TEXT == msgType) {
             WxWorkSendTextMsgDTO textMsgDTO = new WxWorkSendTextMsgDTO();
             textMsgDTO.setUuid(uuid);
             textMsgDTO.setSend_userid(sendUserId);
-            textMsgDTO.setIsRoom(false);
+            textMsgDTO.setIsRoom(isRoom);
             textMsgDTO.setContent(param.getContent());
             WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> msgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(textMsgDTO, serverId);
 
@@ -325,6 +347,9 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
                 return R.error(msgRespDTOWxWorkResponseDTO.getErrmsg());
             }
             msgJson = JSONObject.toJSONString(textMsgDTO);
+            WxWorkSendTextMsgRespDTO data = msgRespDTOWxWorkResponseDTO.getData();
+            qwMsgId = data.getMsg_id();
+            qwAppInfo = data.getApp_info();
         }
 
         // 图片
@@ -342,7 +367,7 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
             WxwSendCDNImgMsgDTO imgMsgDTO = new WxwSendCDNImgMsgDTO();
             imgMsgDTO.setUuid(uuid);
             imgMsgDTO.setSend_userid(sendUserId);
-            imgMsgDTO.setIsRoom(false);
+            imgMsgDTO.setIsRoom(isRoom);
             imgMsgDTO.setCdnkey(data.getCdn_key());
             imgMsgDTO.setAeskey(data.getAes_key());
             imgMsgDTO.setMd5(data.getMd5());
@@ -352,6 +377,9 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
                 return R.error(imgMsgResp.getErrmsg());
             }
             msgJson = JSONObject.toJSONString(imgMsgDTO);
+            WxwSendCDNImgMsgRespDTO imgData = imgMsgResp.getData();
+            qwMsgId = imgData.getMsg_id();
+            qwAppInfo = imgData.getApp_info();
         }
         // 小程序
         else if (MsgType.MINI_PROGRAM == msgType) {
@@ -404,7 +432,7 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
             appMsgDTO.setMd5(data.getMd5());
             appMsgDTO.setAeskey(data.getAes_key());
             appMsgDTO.setFileSize(data.getSize());
-            appMsgDTO.setIsRoom(false);
+            appMsgDTO.setIsRoom(isRoom);
             WxWorkResponseDTO<WxWorkSendAppMsgRespDTO> appMsgResp = wxWorkService.SendAppMsg(appMsgDTO, serverId);
             if (appMsgResp.getErrcode() != 0) {
                 return R.error(appMsgResp.getErrmsg());
@@ -421,6 +449,49 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
 
             msgJson = json.toJSONString();
             param.setContent(msgJson);
+
+            WxWorkSendAppMsgRespDTO appData = appMsgResp.getData();
+            qwMsgId = appData.getMsg_id();
+            qwAppInfo = appData.getApp_info();
+        }
+        // 消息撤销
+        else if (MsgType.CANCEL == msgType) {
+            QwMsg qwMsg = qwMsgMapper.selectQwMsgByMsgIdAndSessionId(param.getMsgId(), param.getSessionId());
+            if (qwMsg == null || qwMsg.getQwMsgId() == null) {
+                return R.error("消息不存在");
+            }
+
+            if (!Objects.equals(qwSession.getSessionId(), param.getSessionId())) {
+                return R.error("会话错误,操作不允许");
+            }
+
+            WxwRevokeMsgDTO params = new WxwRevokeMsgDTO();
+            params.setUuid(uuid);
+            params.setMsgid(qwMsg.getQwMsgId().intValue());
+            params.setRoomid(isRoom ? sendUserId : 0);
+            WxWorkResponseDTO wxWorkResponseDTO = wxWorkService.RevokeMsg(params, serverId);
+            if (wxWorkResponseDTO.getErrcode() != 0) {
+                return R.error(wxWorkResponseDTO.getErrmsg());
+            }
+
+            qwMsg.setMsgType(6);
+            qwMsgMapper.updateQwMsgByMsgIdAndSessionId(qwMsg);
+
+            // 组装返回消息结构
+            QwMessageListVO listVO = new QwMessageListVO();
+            QWFromUser qwFromUser = new QWFromUser();
+            qwFromUser.setId(Long.parseLong(qwMsg.getQwUserId()));
+            qwFromUser.setDisplayName(qwUser.getQwUserName());
+            qwFromUser.setAvatar(qwUser.getAvatar());
+            listVO.setType(msgType.getValue());
+            listVO.setStatus("succeed");
+            listVO.setFromUser(qwFromUser);
+            listVO.setSendTime(qwMsg.getCreateTime().getTime());
+            listVO.setId(qwMsg.getMsgId().toString());
+            listVO.setContent(qwMsg.getContent());
+            listVO.setToContactId(String.valueOf(param.getSessionId()));
+            listVO.setAppKey(qwUser.getAppKey());
+            return R.ok().put("data", listVO);
         } else {
             return R.error("暂不支持的消息类型");
         }
@@ -436,12 +507,26 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         qwMsg.setMsgJson(msgJson);
         qwMsg.setStatus(0);
         qwMsg.setQwUserId(qwSession.getQwUserId());
-        qwMsg.setQwExtId(qwSession.getQwExtId());
-        qwMsg.setAvatar(qwExternalContact.getAvatar());
-        qwMsg.setNickName(qwExternalContact.getName());
         qwMsg.setCreateTime(new Date());
+        qwMsg.setQwMsgId(qwMsgId);
+        qwMsg.setQwAppInfo(qwAppInfo);
+
+        if (Objects.isNull(qwExternalContact)) {
+            qwMsg.setAvatar(qwUser.getAvatar());
+            qwMsg.setNickName(qwUser.getQwUserName());
+        } else {
+            qwMsg.setQwExtId(qwExternalContact.getId().toString());
+            qwMsg.setAvatar(qwExternalContact.getAvatar());
+            qwMsg.setNickName(qwExternalContact.getName());
+        }
         qwMsgMapper.insertQwMsg(qwMsg);
 
+        qwSession.setLastMsgId(qwMsg.getMsgId());
+        qwSession.setLastMsgType(msgType.getValue());
+        qwSession.setLastSendTime(qwMsg.getCreateTime().getTime());
+        qwSession.setLastContent(qwMsg.getContent());
+        qwSessionMapper.updateQwSession(qwSession);
+
         // 组装返回消息结构
         QwMessageListVO listVO = new QwMessageListVO();
         QWFromUser qwFromUser = new QWFromUser();
@@ -459,17 +544,6 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         return R.ok().put("data", listVO);
     }
 
-    @Override
-    public List<QwUser> qwUserList(Long userId) {
-        LambdaQueryWrapper<QwUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
-        lambdaQueryWrapper.eq(QwUser::getCompanyUserId, userId);
-        lambdaQueryWrapper.eq(QwUser::getLoginStatus,1);
-        lambdaQueryWrapper.eq(QwUser::getToolStatus,1);
-        List<QwUser> qwUsers = qwUserMapper.selectList(lambdaQueryWrapper);
-        return qwUsers;
-    }
-
-
     @Override
     public List<QwMsg> getQwMessageList(Long companyId) {
         LambdaQueryWrapper<QwUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
@@ -493,47 +567,12 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         return qwMsgs;
     }
 
+    /**
+     * 查询会话列表
+     */
     @Override
-    public List<QwContactListVO> selectQwConversationByUserId(Long userId) {
-        // 查询会话列表
-        List<QwContactListVO> contactList = qwSessionMapper.selectContactListByQwUserId(userId);
-        if (contactList.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        ArrayList<QwContactListVO> qwContactListVOS = new ArrayList<>();
-        for (QwContactListVO listVO : contactList) {
-            LambdaQueryWrapper<QwMsg> lambdaQueryWrapper = new LambdaQueryWrapper<>();
-            lambdaQueryWrapper.select(QwMsg.class, q -> !q.getColumn().equals("remark"));
-            lambdaQueryWrapper.eq(QwMsg::getSessionId, Integer.parseInt(listVO.getConversationId()));
-            lambdaQueryWrapper.orderByDesc(QwMsg::getMsgId);
-            lambdaQueryWrapper.last("limit 1");
-            List<QwMsg> qwMsgs = qwMsgMapper.selectList(lambdaQueryWrapper);
-            if (CollectionUtil.isEmpty(qwMsgs)){
-                listVO.setLastContent("");
-                listVO.setLastSendTime(new Date().getTime());
-                qwContactListVOS.add(listVO);
-                break;
-            }
-            QwMsg qwMsg = qwMsgs.get(0);
-            if (qwMsg.getMsgType() == 1) {
-                listVO.setType("text");
-            } else if (qwMsg.getMsgType() == 2) {
-                listVO.setType("image");
-            } else if (qwMsg.getMsgType() == 3) {
-                listVO.setType("emotionDynamic");
-            } else if (qwMsg.getMsgType() == 4) {
-                listVO.setType("voice");
-            } else if (qwMsg.getMsgType() == 5) {
-                listVO.setType("miniprogram");
-            }
-            listVO.setMsgId(qwMsg.getMsgId());
-            listVO.setLastContent(qwMsgs.get(0).getContent());
-            listVO.setLastSendTime(qwMsgs.get(0).getCreateTime().getTime());
-            listVO.setUnread(0);
-            qwContactListVOS.add(listVO);
-        }
-        return qwContactListVOS;
+    public List<QwContactListVO> selectQwConversationByMap(Map<String, Object> params) {
+        return qwSessionMapper.selectQwConversationByMap(params);
     }
 
     @Override
@@ -578,7 +617,8 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
             listVO.setSendTime(record.getCreateTime().getTime());
             listVO.setId(record.getMsgId().toString());
             listVO.setContent(record.getContent());
-            listVO.setToContactId(param.getConversationId());
+            listVO.setToContactId(String.valueOf(param.getConversationId()));
+            listVO.setAppKey(user.getAppKey());
             qwMessageVOS.add(listVO);
         }
         return qwMessageVOS;
@@ -586,8 +626,8 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
 
 
     @Override
-    public QwContactListVO selectQwSessionBycId(String sessionId, Long qwUserId) {
-        QwSession session = qwSessionMapper.selectQwSessionBySessionId(Long.parseLong(sessionId));
+    public QwContactListVO selectQwSessionBycId(Long sessionId, Long qwUserId) {
+        QwSession session = qwSessionMapper.selectQwSessionBySessionId(sessionId);
         QwContactListVO listVO = new QwContactListVO();
         listVO.setId(Long.parseLong(session.getQwUserId()));
         listVO.setAvatar(session.getAvatar());
@@ -610,18 +650,128 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         return listVO;
     }
 
+    /**
+     * 根据企微用户ID查询联系人列表
+     */
     @Override
     public List<QwContactVO> contactListByQwUserId(Long qwUserId, String name) {
         return externalContactMapper.getContactListByQwUserId(qwUserId, name);
     }
 
+    /**
+     * 根据企微用户ID查询群组列表
+     */
     @Override
     public List<QwContactVO> groupListByQwUserId(Long qwUserId, String name) {
-        return Collections.emptyList();
+        return groupChatMapper.getGroupListByQwUserId(qwUserId, name);
     }
 
+    /**
+     * 获取会话ID
+     */
     @Override
-    public QwContactListVO getConversationIdById(Long qwUserid, String id, Boolean isGroup) {
-        return null;
+    public QwContactListVO getConversationIdById(Long qwUserId, String id, Boolean isGroup) {
+
+        QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);
+        if (qwUser == null) {
+            throw new ServiceException("企微用户不存在");
+        }
+
+        QwSession qwSession;
+        if (isGroup) {
+            qwSession = qwSessionMapper.selectQwSessionByGroupChatId(id);
+            if (qwSession == null) {
+                QwGroupChat qwGroupChat = groupChatMapper.selectQwGroupChatByChatId(id);
+                if (qwGroupChat == null) {
+                    throw new ServiceException("群聊不存在");
+                }
+
+                RLock lock = redissonClient.getLock("addSession:" + id + ":" + qwUserId);
+                try {
+                    boolean tryLock = lock.tryLock(2, 3, TimeUnit.SECONDS);
+                    if (!tryLock) {
+                        throw new ServiceException("操作太频繁,请稍后再试");
+                    }
+
+                    qwSession = qwSessionMapper.selectQwSessionByGroupChatId(id);
+                    if (qwSession == null) {
+                        String firstLetter = PinYinUtil.getFirstLetter(qwGroupChat.getName());
+                        qwSession = new QwSession();
+                        qwSession.setChatId(qwGroupChat.getChatId());
+                        qwSession.setCorpId(qwGroupChat.getCorpId());
+                        qwSession.setQwUserId(qwUserId.toString());
+                        qwSession.setStatus(1);
+                        qwSession.setNickName(qwGroupChat.getName());
+                        qwSession.setCompanyId(qwGroupChat.getCompanyId());
+                        qwSession.setCreateTime(new Date());
+                        qwSession.setUpdateTime(new Date());
+                        qwSession.setIsRoom(1);
+                        qwSession.setFirstLetter(firstLetter);
+                        qwSessionMapper.insertQwSession(qwSession);
+                    }
+                } catch (InterruptedException e) {
+                    log.warn("获取锁失败 err: {}", e.getMessage(), e);
+                    throw new ServiceException("操作太频繁,请稍后再试");
+                } finally {
+                    lock.unlock();
+                }
+            }
+        } else {
+            qwSession = qwSessionMapper.selectQwSessionByContactId(Long.parseLong(id));
+            if (qwSession == null) {
+                QwExternalContact qwExternalContact = externalContactMapper.selectQwExternalContactById(Long.valueOf(id));
+                if (qwExternalContact == null) {
+                    throw new ServiceException("外部联系人不存在");
+                }
+
+                RLock lock = redissonClient.getLock("addSession:" + id + ":" + qwUserId);
+                try {
+                    boolean tryLock = lock.tryLock(2, 3, TimeUnit.SECONDS);
+                    if (!tryLock) {
+                        throw new ServiceException("操作太频繁,请稍后再试");
+                    }
+
+                    qwSession = qwSessionMapper.selectQwSessionByContactId(Long.parseLong(id));
+                    if (qwSession == null) {
+                        String firstLetter = PinYinUtil.getFirstLetter(qwExternalContact.getName());
+                        qwSession = new QwSession();
+                        String chatId = UUID.randomUUID().toString();
+                        qwSession.setChatId(chatId);
+                        qwSession.setCorpId(qwUser.getCorpId());
+                        qwSession.setQwExtId(qwExternalContact.getId().toString());
+                        qwSession.setQwUserId(qwUser.getId().toString());
+                        qwSession.setStatus(1);
+                        qwSession.setAvatar(qwExternalContact.getAvatar());
+                        qwSession.setNickName(qwExternalContact.getName());
+                        qwSession.setCompanyId(qwUser.getCompanyId());
+                        qwSession.setCompanyUserId(qwUser.getCompanyUserId());
+                        qwSession.setCreateTime(new Date());
+                        qwSession.setUpdateTime(new Date());
+                        qwSession.setIsRoom(0);
+                        qwSession.setFirstLetter(firstLetter);
+                        qwSessionMapper.insertQwSession(qwSession);
+                    }
+                } catch (InterruptedException e) {
+                    log.warn("获取锁失败 err: {}", e.getMessage(), e);
+                    throw new ServiceException("操作太频繁,请稍后再试");
+                } finally {
+                    lock.unlock();
+                }
+            }
+        }
+
+        QwContactListVO qwContactListVO = new QwContactListVO();
+        qwContactListVO.setId(qwSession.getSessionId());
+        qwContactListVO.setConversationId(String.valueOf(qwSession.getSessionId()));
+        qwContactListVO.setExtId(qwSession.getQwExtId());
+        qwContactListVO.setAvatar(qwSession.getAvatar());
+        qwContactListVO.setDisplayName(qwSession.getNickName());
+        qwContactListVO.setIndex(qwSession.getFirstLetter());
+        qwContactListVO.setIsGroup(isGroup);
+        qwContactListVO.setUnread(0);
+        qwContactListVO.setLastSendTime(new Date().getTime());
+        qwContactListVO.setLastContent("");
+
+        return qwContactListVO;
     }
 }

+ 16 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwTagServiceImpl.java

@@ -209,4 +209,20 @@ public class QwTagServiceImpl implements IQwTagService
         }
         return qwTagMapper.selectTagListByUserId(param, keywords);
     }
+
+    /**
+     * 根据tagIds查询标签
+     */
+    @Override
+    public List<QwTagVO> selectQwTagVOListByTagIds(List<String> tagIds) {
+        return qwTagMapper.selectQwTagVOListByTagIds(tagIds);
+    }
+
+    /**
+     * 根据ids查询标签列表
+     */
+    @Override
+    public List<QwTag> selectQwTagListByCorpIdAndIds(List<Long> ids, String corpId) {
+        return qwTagMapper.selectQwTagListByCorpIdAndIds(ids, corpId);
+    }
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -1631,4 +1631,9 @@ public class QwUserServiceImpl implements IQwUserService
     public List<QwUserVO> selectQwUserVoListByCompanyUserId(Long companyUserId) {
         return qwUserMapper.selectQwUserVoListByCompanyUserId(companyUserId);
     }
+
+    @Override
+    public QwUser selectQwUserByVid(Long vid) {
+        return qwUserMapper.selectQwUserByVid(vid);
+    }
 }

+ 1 - 2
fs-service/src/main/java/com/fs/qw/vo/QwMessageListVO.java

@@ -21,8 +21,7 @@ public class QwMessageListVO {
     private String toContactId;
     private QWFromUser fromUser;
     private String appKey;
-    @JSONField(serialize = false)
-    private Long companyId;
+    private Long companyUserId;
     private String extId;
 
     //获取fromUser

+ 52 - 0
fs-service/src/main/java/com/fs/statis/dto/ComprehensiveStatisticsDTO.java

@@ -0,0 +1,52 @@
+package com.fs.statis.dto;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/11/11 下午1:29
+ */
+@Data
+public class ComprehensiveStatisticsDTO {
+
+    private Long companyId;
+
+    @Excel(name = "公司名称")
+    private String companyName;
+
+    private Long companyUserId;
+
+    @Excel(name = "人员名称")
+    private String companyUserName;
+
+    private Integer deptId;
+
+    @Excel(name = "部门名称")
+    private String deptName;
+
+    @Excel(name = "统计时间")
+    private Integer sendCount;
+
+    private Integer logType;
+
+    @Excel(name = "完课数")
+    private Integer completeNum; //完成数
+
+    @Excel(name = "答题数")
+    private Integer answerNum;
+
+    @Excel(name = "红包数")
+    private Integer redPacketNum;
+
+    @Excel(name = "红包领取金额")
+    private BigDecimal redPacketAmount;
+
+    @Excel(name = "统计时间")
+    private Date statisticsTime;
+
+}

+ 21 - 0
fs-service/src/main/java/com/fs/statis/param/ComprehensiveStatisticsParam.java

@@ -36,5 +36,26 @@ public class ComprehensiveStatisticsParam {
      * id 在不同的维度下,id代表的意义不同
      */
     private Long id;
+
+    private Long deptId;
+
+    private Long userId;
+
+    private Long videoId;
+
+    private Long courseId;
+
+    private Long projectId;
+
+    private Long companyId;
+
+    private String companyName;
+
+    private String deptName;
+
+    private Long logType;
+
+    private Boolean timeGroupFlag; //时间分组条件是否生效
+
 }
 

+ 15 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxRoomHeaderDTO.java

@@ -0,0 +1,15 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxRoomHeaderDTO {
+    /**
+     * UUID
+     */
+    private String uuid;
+    /**
+     * RoomId
+     */
+    private Long roomid;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxRoomHeaderResp.java

@@ -0,0 +1,15 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxRoomHeaderResp {
+    /**
+     * 群头像
+     */
+    private String image_url;
+    /**
+     * RoomId
+     */
+    private Long roomid;
+}

+ 46 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxSendTextAtMsgTwoDTO.java

@@ -0,0 +1,46 @@
+package com.fs.wxwork.dto;
+
+import com.fs.ipad.vo.BaseVo;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class WxSendTextAtMsgTwoDTO extends BaseVo {
+    /**
+     * 消息的唯一标识符 (UUID)
+     */
+    private String uuid;
+
+    /**
+     * 要发送的人或群id
+     */
+    private Long send_userid;
+
+    /**
+     * 发送者用户 ID
+     */
+    private List<Contentva> contentva;
+    /**
+     * 是否为群组消息
+     * <p>
+     * true: 群组消息; false: 单聊消息
+     * </p>
+     */
+    private Boolean isRoom;
+
+    @Data
+    public static class Contentva {
+        private Integer msgtype;
+        private String content;
+        /**
+         * "msgtype":5,//@类型
+         * "vid":0 //填0就是所有人
+         * <p>
+         * "msgtype":5, //就是@人的类型
+         * "vid":788130xx38 //@人id
+         */
+        private Integer vid;
+    }
+
+}

+ 20 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxWorkChatId2RoomIdDTO.java

@@ -0,0 +1,20 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxWorkChatId2RoomIdDTO {
+
+    /**
+     * UUID
+     */
+    private String uuid;
+    /**
+     * 不传默认当前账号登录企业
+     */
+    private Long corpid;
+    /**
+     * ChatId
+     */
+    private String chatid;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxWorkChatId2RoomIdResp.java

@@ -0,0 +1,15 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxWorkChatId2RoomIdResp {
+    /**
+     * RoomID
+     */
+    private Long room_id;
+    /**
+     * ChatId
+     */
+    private String chatid;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxWorkRoomId2ChatIdDTO.java

@@ -0,0 +1,20 @@
+package com.fs.wxwork.dto;
+
+import lombok.Data;
+
+@Data
+public class WxWorkRoomId2ChatIdDTO {
+
+    /**
+     * uuid
+     */
+    private String uuid;
+    /**
+     * 企业id 不传默认当前账号登录企业
+     */
+    private Long corpid;
+    /**
+     * 房间ID
+     */
+    private Long room_id;
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff