Procházet zdrojové kódy

企微聊天最新消息获取改为webSocket、并添加校验、修改包名

Long před 2 týdny
rodič
revize
c699e0284a
49 změnil soubory, kde provedl 274 přidání a 74 odebrání
  1. 4 1
      fs-qw-api-msg/pom.xml
  2. 16 3
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  3. 101 0
      fs-qw-api-msg/src/main/java/com/fs/app/socket/QwImSocket.java
  4. 22 0
      fs-qw-api-msg/src/main/java/com/fs/app/socket/configurator/QwImConfigurator.java
  5. 3 3
      fs-qw-api-msg/src/main/java/com/fs/core/aspectj/DataScopeAspect.java
  6. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/aspectj/DataSourceAspect.java
  7. 5 5
      fs-qw-api-msg/src/main/java/com/fs/core/aspectj/LogAspect.java
  8. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java
  9. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/ApplicationConfig.java
  10. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/ArrayStringTypeHandler.java
  11. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/CaptchaConfig.java
  12. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/config/DataSourceConfig.java
  13. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java
  14. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/FilterConfig.java
  15. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/KaptchaTextCreator.java
  16. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/MyBatisConfig.java
  17. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/RedisConfig.java
  18. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/config/ResourcesConfig.java
  19. 6 5
      fs-qw-api-msg/src/main/java/com/fs/core/config/SecurityConfig.java
  20. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/ServerConfig.java
  21. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/SwaggerConfig.java
  22. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/ThreadPoolConfig.java
  23. 17 0
      fs-qw-api-msg/src/main/java/com/fs/core/config/WebSocketConfig.java
  24. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/config/properties/DruidProperties.java
  25. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/datasource/DynamicDataSource.java
  26. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java
  27. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/exception/GlobalExceptionHandler.java
  28. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java
  29. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java
  30. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/manager/AsyncManager.java
  31. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/manager/ShutdownManager.java
  32. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/manager/factory/AsyncFactory.java
  33. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/security/LoginBody.java
  34. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/security/LoginUser.java
  35. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/security/SecurityUtils.java
  36. 4 4
      fs-qw-api-msg/src/main/java/com/fs/core/security/filter/JwtAuthenticationTokenFilter.java
  37. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/security/handle/AuthenticationEntryPointImpl.java
  38. 5 5
      fs-qw-api-msg/src/main/java/com/fs/core/security/handle/LogoutSuccessHandlerImpl.java
  39. 4 4
      fs-qw-api-msg/src/main/java/com/fs/core/service/CompanyLoginService.java
  40. 1 1
      fs-qw-api-msg/src/main/java/com/fs/core/service/CompanyPermissionService.java
  41. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/service/PermissionService.java
  42. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/service/TokenService.java
  43. 2 2
      fs-qw-api-msg/src/main/java/com/fs/core/service/UserDetailsServiceImpl.java
  44. 1 1
      fs-qw-api-msg/src/main/resources/mybatis/mybatis-config.xml
  45. 2 1
      fs-service-system/src/main/java/com/fs/fastGpt/service/AiHookService.java
  46. 27 3
      fs-service-system/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  47. 9 0
      fs-service-system/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java
  48. 3 0
      fs-service-system/src/main/java/com/fs/qw/vo/QwMessageListVO.java
  49. 6 0
      fs-service-system/src/main/java/com/fs/wxwork/dto/WxWorkMessageDTO.java

+ 4 - 1
fs-qw-api-msg/pom.xml

@@ -117,7 +117,10 @@
             <artifactId>vosk</artifactId>
             <version>0.3.32</version>
         </dependency>
-
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 16 - 3
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -1,11 +1,13 @@
 package com.fs.app.controller;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.app.socket.QwImSocket;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.fastGpt.service.AiHookService;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwUserVoiceLogService;
+import com.fs.qw.vo.QwMessageListVO;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
 import io.swagger.annotations.Api;
@@ -132,7 +134,7 @@ public class QwMsgController {
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 break;
             case 102000:
-
+                System.out.println(wxWorkMsgResp.getJson());
                 WxWorkMessageDTO wxWorkMessageDTO = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
                 if (wxWorkMessageDTO.getIs_room()!=0){
                     break;
@@ -163,13 +165,15 @@ public class QwMsgController {
                         aiHookService.qwHookNotifyAiReply(id,sender,content,wxWorkMsgResp.getUuid(),wxWorkMessageDTO.getMsgtype());
 
                         // 保存聊天消息
-                        aiHookService.saveQwMsg(id, sender, content, wxWorkMsgResp.getUuid(), 1, wxWorkMsgResp.getJson());
+                        QwMessageListVO message = aiHookService.saveQwMsg(id, sender, content, wxWorkMsgResp.getUuid(), 1, wxWorkMsgResp.getJson());
+                        QwImSocket.broadcast(message);
                     }else {
                         System.out.println("销售发送");
                         aiHookService.qwHookNotifyAddMsg(id,receiver,content,wxWorkMsgResp.getUuid());
 
                         // 保存聊天消息
-                        aiHookService.saveQwMsg(id, receiver, content, wxWorkMsgResp.getUuid(), 2, wxWorkMsgResp.getJson());
+                        QwMessageListVO message = aiHookService.saveQwMsg(id, receiver, content, wxWorkMsgResp.getUuid(), 2, wxWorkMsgResp.getJson());
+                        QwImSocket.broadcast(message);
                     }
 
                 }
@@ -210,6 +214,15 @@ public class QwMsgController {
 
                     qwUserVoiceLogService.addQuUserVoiceByIpadCallback(id,extId,recordType,totalSeconds,wxWorkMsgResp.getUuid());
                 }
+                // 图片消息
+                else if (wxWorkMessageDTO.getMsgtype() == 101){
+                    Long receiver = wxWorkMessageDTO.getReceiver();
+                    if (2000000000000000L - receiver > 0){
+                        System.out.println("客户发起");
+                    }else {
+                        System.out.println("销售发起");
+                    }
+                }
 
                 break;
 

+ 101 - 0
fs-qw-api-msg/src/main/java/com/fs/app/socket/QwImSocket.java

@@ -0,0 +1,101 @@
+package com.fs.app.socket;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.socket.configurator.QwImConfigurator;
+import com.fs.qw.vo.QwMessageListVO;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+@ServerEndpoint(value = "/qwImSocket/{companyId}", configurator = QwImConfigurator.class)
+@Component
+public class QwImSocket {
+
+    private static final ConcurrentHashMap<Long, CopyOnWriteArraySet<Session>> companySessions = new ConcurrentHashMap<>();
+
+    /**
+     * 连接建立成功调用的方法
+     * @param session   连接会话
+     * @param companyId 公司ID
+     */
+    @OnOpen
+    public void onOpen(Session session, @PathParam("companyId") Long companyId) {
+        // 将当前会话加入到会话池中
+        companySessions.computeIfAbsent(companyId, k -> new CopyOnWriteArraySet<>()).add(session);
+    }
+
+    /**
+     * 连接关闭调用的方法
+     * @param session   连接会话
+     * @param companyId 公司ID
+     */
+    @OnClose
+    public void onClose(Session session, @PathParam("companyId") Long companyId) {
+        // 从会话池中移除当前会话
+        CopyOnWriteArraySet<Session> sessions = companySessions.get(companyId);
+        if (sessions != null) {
+            sessions.remove(session);
+            // 如果直播间没人了,可以移除该直播间
+            if (sessions.isEmpty()) {
+                companySessions.remove(companyId);
+            }
+        }
+    }
+
+    /**
+     * 发生错误时调用的方法
+     * @param session   连接会话
+     * @param companyId 公司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) {
+            sessions.remove(session);
+            // 如果直播间没人了,可以移除该直播间
+            if (sessions.isEmpty()) {
+                companySessions.remove(companyId);
+            }
+        }
+    }
+
+    /**
+     * 群发消息
+     * @param message   要发送的消息
+     */
+    public static void broadcast(QwMessageListVO message) {
+        if (Objects.isNull(message)) {
+            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);
+                    }
+                } else {
+                    sessions.remove(session); // 移除已关闭的会话
+                }
+            }
+        }
+    }
+
+}

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

@@ -0,0 +1,22 @@
+package com.fs.app.socket.configurator;
+
+import com.fs.app.exception.FSException;
+
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class QwImConfigurator extends ServerEndpointConfig.Configurator {
+
+    @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)) {
+            throw new FSException("Unauthorized access to WebSocket endpoint.");
+        }
+    }
+}

+ 3 - 3
fs-qw-api-msg/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java → fs-qw-api-msg/src/main/java/com/fs/core/aspectj/DataScopeAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.DataScope;
 import com.fs.common.core.domain.BaseEntity;
@@ -7,8 +7,8 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.CompanyRole;
 import com.fs.company.domain.CompanyUser;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.service.TokenService;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.Aspect;

+ 2 - 2
fs-qw-api-msg/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java → fs-qw-api-msg/src/main/java/com/fs/core/aspectj/DataSourceAspect.java

@@ -1,8 +1,8 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.DataSource;
 import com.fs.common.utils.StringUtils;
-import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.core.datasource.DynamicDataSourceContextHolder;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;

+ 5 - 5
fs-qw-api-msg/src/main/java/com/fs/framework/aspectj/LogAspect.java → fs-qw-api-msg/src/main/java/com/fs/core/aspectj/LogAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
@@ -9,10 +9,10 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.CompanyOperLog;
-import com.fs.framework.manager.AsyncManager;
-import com.fs.framework.manager.factory.AsyncFactory;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.service.TokenService;
+import com.fs.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.AfterReturning;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java → fs-qw-api-msg/src/main/java/com/fs/core/aspectj/RateLimiterAspect.java

@@ -1,4 +1,4 @@
-package com.fs.framework.aspectj;
+package com.fs.core.aspectj;
 
 import com.fs.common.annotation.RateLimiter;
 import com.fs.common.enums.LimitType;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/ApplicationConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/ApplicationConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/ArrayStringTypeHandler.java → fs-qw-api-msg/src/main/java/com/fs/core/config/ArrayStringTypeHandler.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import org.apache.ibatis.type.BaseTypeHandler;
 import org.apache.ibatis.type.JdbcType;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/CaptchaConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/CaptchaConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.google.code.kaptcha.impl.DefaultKaptcha;
 import com.google.code.kaptcha.util.Config;

+ 2 - 2
fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/DataSourceConfig.java

@@ -1,10 +1,10 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.alibaba.druid.pool.DruidDataSource;
 import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
 import com.alibaba.druid.util.Utils;
 import com.fs.common.enums.DataSourceType;
-import com.fs.framework.datasource.DynamicDataSource;
+import com.fs.core.datasource.DynamicDataSource;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.ConfigurationProperties;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java → fs-qw-api-msg/src/main/java/com/fs/core/config/FastJson2JsonRedisSerializer.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.parser.ParserConfig;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/FilterConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/FilterConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.filter.RepeatableFilter;
 import com.fs.common.filter.XssFilter;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/KaptchaTextCreator.java → fs-qw-api-msg/src/main/java/com/fs/core/config/KaptchaTextCreator.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.google.code.kaptcha.text.impl.DefaultTextCreator;
 

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/MyBatisConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
 import org.apache.ibatis.io.VFS;

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

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;

+ 2 - 2
fs-qw-api-msg/src/main/java/com/fs/framework/config/ResourcesConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/ResourcesConfig.java

@@ -1,8 +1,8 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.config.FSConfig;
 import com.fs.common.constant.Constants;
-import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import com.fs.core.interceptor.RepeatSubmitInterceptor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;

+ 6 - 5
fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/SecurityConfig.java

@@ -1,9 +1,9 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 
-import com.fs.framework.security.filter.JwtAuthenticationTokenFilter;
-import com.fs.framework.security.handle.AuthenticationEntryPointImpl;
-import com.fs.framework.security.handle.LogoutSuccessHandlerImpl;
+import com.fs.core.security.filter.JwtAuthenticationTokenFilter;
+import com.fs.core.security.handle.AuthenticationEntryPointImpl;
+import com.fs.core.security.handle.LogoutSuccessHandlerImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.HttpMethod;
@@ -106,7 +106,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                         "/**/*.html",
                         "/**/*.css",
                         "/**/*.js",
-                        "/profile/**"
+                        "/profile/**",
+                        "/qwImSocket/**"
                 ).permitAll()
 
                 .antMatchers("/**").anonymous()

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/ServerConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/ServerConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.utils.ServletUtils;
 import org.springframework.stereotype.Component;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/SwaggerConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/SwaggerConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.config.FSConfig;
 import io.swagger.annotations.ApiOperation;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/ThreadPoolConfig.java → fs-qw-api-msg/src/main/java/com/fs/core/config/ThreadPoolConfig.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config;
+package com.fs.core.config;
 
 import com.fs.common.utils.Threads;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;

+ 17 - 0
fs-qw-api-msg/src/main/java/com/fs/core/config/WebSocketConfig.java

@@ -0,0 +1,17 @@
+package com.fs.core.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+    /**
+     * ServerEndpointExporter 作用
+     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
+     */
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+}

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/properties/DruidProperties.java → fs-qw-api-msg/src/main/java/com/fs/core/config/properties/DruidProperties.java

@@ -1,4 +1,4 @@
-package com.fs.framework.config.properties;
+package com.fs.core.config.properties;
 
 import com.alibaba.druid.pool.DruidDataSource;
 import org.springframework.beans.factory.annotation.Value;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/datasource/DynamicDataSource.java → fs-qw-api-msg/src/main/java/com/fs/core/datasource/DynamicDataSource.java

@@ -1,4 +1,4 @@
-package com.fs.framework.datasource;
+package com.fs.core.datasource;
 
 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java → fs-qw-api-msg/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java

@@ -1,4 +1,4 @@
-package com.fs.framework.datasource;
+package com.fs.core.datasource;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/exception/GlobalExceptionHandler.java → fs-qw-api-msg/src/main/java/com/fs/core/exception/GlobalExceptionHandler.java

@@ -1,4 +1,4 @@
-package com.fs.framework.exception;
+package com.fs.core.exception;
 
 import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.domain.AjaxResult;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java → fs-qw-api-msg/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java

@@ -1,4 +1,4 @@
-package com.fs.framework.interceptor;
+package com.fs.core.interceptor;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.RepeatSubmit;

+ 2 - 2
fs-qw-api-msg/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java → fs-qw-api-msg/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java

@@ -1,4 +1,4 @@
-package com.fs.framework.interceptor.impl;
+package com.fs.core.interceptor.impl;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
@@ -6,7 +6,7 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.common.filter.RepeatedlyRequestWrapper;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.http.HttpHelper;
-import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import com.fs.core.interceptor.RepeatSubmitInterceptor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/manager/AsyncManager.java → fs-qw-api-msg/src/main/java/com/fs/core/manager/AsyncManager.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager;
+package com.fs.core.manager;
 
 import com.fs.common.utils.Threads;
 import com.fs.common.utils.spring.SpringUtils;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/manager/ShutdownManager.java → fs-qw-api-msg/src/main/java/com/fs/core/manager/ShutdownManager.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager;
+package com.fs.core.manager;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java → fs-qw-api-msg/src/main/java/com/fs/core/manager/factory/AsyncFactory.java

@@ -1,4 +1,4 @@
-package com.fs.framework.manager.factory;
+package com.fs.core.manager.factory;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.utils.LogUtils;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/security/LoginBody.java → fs-qw-api-msg/src/main/java/com/fs/core/security/LoginBody.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 /**
  * 用户登录对象

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/security/LoginUser.java → fs-qw-api-msg/src/main/java/com/fs/core/security/LoginUser.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fs.company.domain.Company;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/security/SecurityUtils.java → fs-qw-api-msg/src/main/java/com/fs/core/security/SecurityUtils.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security;
+package com.fs.core.security;
 
 import com.fs.common.constant.HttpStatus;
 import com.fs.common.exception.CustomException;

+ 4 - 4
fs-qw-api-msg/src/main/java/com/fs/framework/security/filter/JwtAuthenticationTokenFilter.java → fs-qw-api-msg/src/main/java/com/fs/core/security/filter/JwtAuthenticationTokenFilter.java

@@ -1,10 +1,10 @@
-package com.fs.framework.security.filter;
+package com.fs.core.security.filter;
 
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.StringUtils;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.security.SecurityUtils;
-import com.fs.framework.service.TokenService;
+import com.fs.core.security.LoginUser;
+import com.fs.core.security.SecurityUtils;
+import com.fs.core.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/security/handle/AuthenticationEntryPointImpl.java → fs-qw-api-msg/src/main/java/com/fs/core/security/handle/AuthenticationEntryPointImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security.handle;
+package com.fs.core.security.handle;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.constant.HttpStatus;

+ 5 - 5
fs-qw-api-msg/src/main/java/com/fs/framework/security/handle/LogoutSuccessHandlerImpl.java → fs-qw-api-msg/src/main/java/com/fs/core/security/handle/LogoutSuccessHandlerImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.security.handle;
+package com.fs.core.security.handle;
 
 import com.alibaba.fastjson.JSON;
 import com.fs.common.constant.Constants;
@@ -6,10 +6,10 @@ import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
-import com.fs.framework.manager.AsyncManager;
-import com.fs.framework.manager.factory.AsyncFactory;
-import com.fs.framework.security.LoginUser;
-import com.fs.framework.service.TokenService;
+import com.fs.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
+import com.fs.core.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.core.Authentication;

+ 4 - 4
fs-qw-api-msg/src/main/java/com/fs/framework/service/CompanyLoginService.java → fs-qw-api-msg/src/main/java/com/fs/core/service/CompanyLoginService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCache;
@@ -7,9 +7,9 @@ import com.fs.common.exception.user.CaptchaException;
 import com.fs.common.exception.user.CaptchaExpireException;
 import com.fs.common.exception.user.UserPasswordNotMatchException;
 import com.fs.common.utils.MessageUtils;
-import com.fs.framework.manager.AsyncManager;
-import com.fs.framework.manager.factory.AsyncFactory;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.core.security.LoginUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/service/CompanyPermissionService.java → fs-qw-api-msg/src/main/java/com/fs/core/service/CompanyPermissionService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyMenuService;

+ 2 - 2
fs-qw-api-msg/src/main/java/com/fs/framework/service/PermissionService.java → fs-qw-api-msg/src/main/java/com/fs/core/service/PermissionService.java

@@ -1,9 +1,9 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyRole;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;

+ 2 - 2
fs-qw-api-msg/src/main/java/com/fs/framework/service/TokenService.java → fs-qw-api-msg/src/main/java/com/fs/core/service/TokenService.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCache;
@@ -7,7 +7,7 @@ import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.AddressUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.common.utils.uuid.IdUtils;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import eu.bitwalker.useragentutils.UserAgent;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.Jwts;

+ 2 - 2
fs-qw-api-msg/src/main/java/com/fs/framework/service/UserDetailsServiceImpl.java → fs-qw-api-msg/src/main/java/com/fs/core/service/UserDetailsServiceImpl.java

@@ -1,4 +1,4 @@
-package com.fs.framework.service;
+package com.fs.core.service;
 
 
 import com.fs.common.enums.UserStatus;
@@ -8,7 +8,7 @@ import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
-import com.fs.framework.security.LoginUser;
+import com.fs.core.security.LoginUser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;

+ 1 - 1
fs-qw-api-msg/src/main/resources/mybatis/mybatis-config.xml

@@ -13,7 +13,7 @@ PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 	</settings>
 
 	<typeHandlers>
-		<typeHandler handler="com.fs.framework.config.ArrayStringTypeHandler"/>
+		<typeHandler handler="com.fs.core.config.ArrayStringTypeHandler"/>
 	</typeHandlers>
 
 </configuration>

+ 2 - 1
fs-service-system/src/main/java/com/fs/fastGpt/service/AiHookService.java

@@ -1,6 +1,7 @@
 package com.fs.fastGpt.service;
 
 import com.fs.common.core.domain.R;
+import com.fs.qw.vo.QwMessageListVO;
 import com.fs.qwHookApi.vo.QwHookVO;
 
 public interface AiHookService {
@@ -27,5 +28,5 @@ public interface AiHookService {
      * @param sendType 发送者类型 1用户 2客服
      * @param json     消息json
      */
-    void saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json);
+    QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json);
 }

+ 27 - 3
fs-service-system/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -28,6 +28,7 @@ import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.service.*;
+import com.fs.qw.vo.QwMessageListVO;
 import com.fs.qwApi.param.QwSendMsgParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.qwHookApi.param.QwHookSendMsgParam;
@@ -1271,19 +1272,19 @@ public class AiHookServiceImpl implements AiHookService {
      */
     @Transactional(rollbackFor = Exception.class)
     @Override
-    public void saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json) {
+    public QwMessageListVO saveQwMsg(Long qwUserId, Long userId, String content, String uuid, int sendType, String json) {
         // 查询企微用户
         QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
         if (Objects.isNull(qwUser)){
             log.warn("企微用户不存在 qwUserId: {}", qwUserId);
-            return;
+            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());
-            return;
+            return null;
         }
 
         // 查询会话
@@ -1327,6 +1328,29 @@ public class AiHookServiceImpl implements AiHookService {
         qwMsg.setCreateTime(new Date());
         qwMsgMapper.insertQwMsg(qwMsg);
         log.debug("保存企微聊天记录 msgId: {}", qwMsg.getMsgId());
+
+        // 组装返回消息结构
+        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("https://cos.his.cdwjyyh.com/fs/20241231/22a765a96da247d1b83ea94fef438a41.png");
+        }
+
+        listVO.setCompanyId(qwUser.getCompanyId());
+        listVO.setType("text");
+        listVO.setStatus("succeed");
+        listVO.setFromUser(qwFromUser);
+        listVO.setSendTime(qwMsg.getCreateTime().getTime());
+        listVO.setId(qwMsg.getMsgId().toString());
+        listVO.setContent(qwMsg.getContent());
+        listVO.setToContactId(String.valueOf(qwSession.getSessionId()));
+        return listVO;
     }
 
     /**

+ 9 - 0
fs-service-system/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java

@@ -285,6 +285,10 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         params.setScorpid(sCorpId);
         WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> listWxWorkResponseDTO = wxWorkService.UserId2Vid(params, serverId);
 
+        if (listWxWorkResponseDTO.getErrcode() != 0) {
+            return R.error(listWxWorkResponseDTO.getErrmsg());
+        }
+
         // 发送消息
         WxWorkSendTextMsgDTO textMsgDTO = new WxWorkSendTextMsgDTO();
         textMsgDTO.setUuid(uuid);
@@ -293,6 +297,10 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         textMsgDTO.setContent(param.getContent());
         WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> msgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(textMsgDTO, serverId);
 
+        if (msgRespDTOWxWorkResponseDTO.getErrcode() != 0) {
+            return R.error(msgRespDTOWxWorkResponseDTO.getErrmsg());
+        }
+
         String msg = msgRespDTOWxWorkResponseDTO.getErrmsg();
         if ("ok".equals(msg)) {
             // 消息保存本地数据库
@@ -303,6 +311,7 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
             qwMsg.setCompanyId(qwUser.getCompanyId());
             qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
             qwMsg.setMsgType(1);
+            qwMsg.setMsgJson(JSONObject.toJSONString(textMsgDTO));
             qwMsg.setStatus(0);
             qwMsg.setQwUserId(qwSession.getQwUserId());
             qwMsg.setQwExtId(qwSession.getQwExtId());

+ 3 - 0
fs-service-system/src/main/java/com/fs/qw/vo/QwMessageListVO.java

@@ -1,5 +1,6 @@
 package com.fs.qw.vo;
 
+import com.alibaba.fastjson.annotation.JSONField;
 import com.fs.qw.domain.QWFromUser;
 import com.fs.qw.domain.QwMsg;
 import lombok.Data;
@@ -19,6 +20,8 @@ public class QwMessageListVO {
     private Integer duration; //时长
     private String toContactId;
     private QWFromUser fromUser;
+    @JSONField(serialize = false)
+    private Long companyId;
 
     //获取fromUser
     public  QWFromUser getQwFromUser(Long senderId,QwMsg qwMsg){

+ 6 - 0
fs-service-system/src/main/java/com/fs/wxwork/dto/WxWorkMessageDTO.java

@@ -20,4 +20,10 @@ public class WxWorkMessageDTO {
     private Integer msgtype;         // 对应 "msgtype": 2 (消息类型 0 和 2文本消息)
     private String recordwording;    //通话时长
     private Integer recordtype;         //通话5
+
+    // 图片
+    private String file_id;
+    private Integer file_size;
+    private String aes_key;
+    private String openim_cdn_authkey;
 }