From a9a2fb5d688526690903349dad983f98a474ed7a Mon Sep 17 00:00:00 2001 From: yang <2119157836@qq.com> Date: Mon, 3 Mar 2025 17:21:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(communityCenter):=20ai=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../communityCenter/ChatController.java | 25 ++ .../src/main/resources/application-dev.yml | 271 ++++++++--------- .../src/main/resources/application-druid.yml | 276 +++++++++--------- mcwl-common/pom.xml | 6 + .../com/mcwl/common/config/JacksonConfig.java | 18 ++ mcwl-communityCenter/pom.xml | 20 ++ .../config/WebSocketConfig.java | 25 ++ .../domain/DeepSeekRequest.java | 68 +++++ .../communityCenter/service/AIService.java | 10 + .../service/CustomerService.java | 11 + .../service/impl/AIServiceImpl.java | 84 ++++++ .../service/impl/CustomerServiceImpl.java | 32 ++ .../webSocket/ChatWebSocketPoint.java | 92 ++++++ .../framework/config/ResourcesConfig.java | 23 ++ 14 files changed, 692 insertions(+), 269 deletions(-) create mode 100644 mcwl-admin/src/main/java/com/mcwl/web/controller/communityCenter/ChatController.java create mode 100644 mcwl-common/src/main/java/com/mcwl/common/config/JacksonConfig.java create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/config/WebSocketConfig.java create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/domain/DeepSeekRequest.java create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/AIService.java create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/CustomerService.java create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/AIServiceImpl.java create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/CustomerServiceImpl.java create mode 100644 mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocketPoint.java diff --git a/mcwl-admin/src/main/java/com/mcwl/web/controller/communityCenter/ChatController.java b/mcwl-admin/src/main/java/com/mcwl/web/controller/communityCenter/ChatController.java new file mode 100644 index 0000000..13a63b0 --- /dev/null +++ b/mcwl-admin/src/main/java/com/mcwl/web/controller/communityCenter/ChatController.java @@ -0,0 +1,25 @@ +package com.mcwl.web.controller.communityCenter; + +import com.mcwl.communityCenter.webSocket.ChatWebSocketPoint; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/chat") +public class ChatController { + + private final ChatWebSocketPoint chatWebSocketPoint; + + @GetMapping("/switchUserMode") + public void switchUserMode(String sessionId, Boolean isCustomer) throws Exception { + if (isCustomer == null) { + chatWebSocketPoint.switchUserMode(sessionId, false); + } else { + chatWebSocketPoint.switchUserMode(sessionId, isCustomer); + } + } + +} \ No newline at end of file diff --git a/mcwl-admin/src/main/resources/application-dev.yml b/mcwl-admin/src/main/resources/application-dev.yml index 36c3a82..64759e3 100644 --- a/mcwl-admin/src/main/resources/application-dev.yml +++ b/mcwl-admin/src/main/resources/application-dev.yml @@ -1,103 +1,112 @@ # 数据源配置 spring: - #mq - rabbitmq: - host: 1.13.246.108 - port: 5672 - username: guest - password: guest - virtualHost: / - listener: - simple: - prefetch: 1 # 每次之能获取一条 - acknowledge-mode: manual # 设置消费端手动ack确认 - retry: - enabled: true # 是否支持重试 - # 生产者配置 - publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange) - publisher-returns: true #确认消息已发送到队列(Queue) + #mq + rabbitmq: + host: 1.13.246.108 + port: 5672 + username: guest + password: guest + virtualHost: / + listener: + simple: + prefetch: 1 # 每次之能获取一条 + acknowledge-mode: manual # 设置消费端手动ack确认 + retry: + enabled: true # 是否支持重试 + # 生产者配置 + publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange) + publisher-returns: true #确认消息已发送到队列(Queue) - datasource: - type: com.alibaba.druid.pool.DruidDataSource - driverClassName: com.mysql.cj.jdbc.Driver - druid: - # 主库数据源 - master: - url: jdbc:mysql://1.13.246.108:3306/mcwl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: ybl123456@ - # 从库数据源 - slave: - # 从数据源开关/默认关闭 - enabled: false - url: - username: - password: - # 初始连接数 - initialSize: 5 - # 最小连接池数量 - minIdle: 10 - # 最大连接池数量 - maxActive: 20 - # 配置获取连接等待超时的时间 - maxWait: 60000 - # 配置连接超时时间 - connectTimeout: 30000 - # 配置网络超时时间 - socketTimeout: 60000 - # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 - timeBetweenEvictionRunsMillis: 60000 - # 配置一个连接在池中最小生存的时间,单位是毫秒 - minEvictableIdleTimeMillis: 300000 - # 配置一个连接在池中最大生存的时间,单位是毫秒 - maxEvictableIdleTimeMillis: 900000 - # 配置检测连接是否有效 - validationQuery: SELECT 1 FROM DUAL - testWhileIdle: true - testOnBorrow: false - testOnReturn: false - webStatFilter: - enabled: true - statViewServlet: - enabled: true - # 设置白名单,不填则允许所有访问 - allow: - url-pattern: /druid/* - # 控制台管理用户名和密码 - login-username: mcwl - login-password: 123456 - filter: - stat: - enabled: true - # 慢SQL记录 - log-slow-sql: true - slow-sql-millis: 1000 - merge-sql: true - wall: - config: - multi-statement-allow: true - # redis 配置 - redis: - # 地址 - host: 1.13.246.108 - # 端口,默认为6379 - port: 6370 - # 数据库索引 - database: 0 - # 密码 - password: MuYu_Cloud@Redis - # 连接超时时间 - timeout: 10s - lettuce: - pool: - # 连接池中的最小空闲连接 - min-idle: 0 - # 连接池中的最大空闲连接 - max-idle: 8 - # 连接池的最大数据库连接数 - max-active: 8 - # #连接池最大阻塞等待时间(使用负值表示没有限制) - max-wait: -1ms + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://1.13.246.108:3306/mcwl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: ybl123456@ + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: mcwl + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + # redis 配置 + redis: + # 地址 + host: 1.13.246.108 + # 端口,默认为6379 + port: 6370 + # 数据库索引 + database: 0 + # 密码 + password: MuYu_Cloud@Redis + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + + #ai配置 + ai: + dashscope: + base-url: https://api.deepseek.com/chat/completions + api-key: sk-5d1f611b6ba74b90ae9e3dff5aaa508a + chat: + options: + model: deepseek-chat # token配置 @@ -111,47 +120,47 @@ token: # 公众号配置 wechat: - # 应用ID - appid: wx82d4c3c96f0ffa5b - # 应用密钥 - secret: abbabcf1da711a3bbd95387ec83edcac + # 应用ID + appid: wx82d4c3c96f0ffa5b + # 应用密钥 + secret: abbabcf1da711a3bbd95387ec83edcac # yml版(application.yml) aliyun: - oss: - bucketName: ybl2112 - endpoint: oss-cn-beijing.aliyuncs.com - accessKeyId: LTAI5tSHZZ8wHJRP8X4r9TXT - accessKeySecret: F82IVNx0IGJ3AnP6gSIfcyql1HCXIH - accessPre: https://ybl2112.oss-cn-beijing.aliyuncs.com/ + oss: + bucketName: ybl2112 + endpoint: oss-cn-beijing.aliyuncs.com + accessKeyId: LTAI5tSHZZ8wHJRP8X4r9TXT + accessKeySecret: F82IVNx0IGJ3AnP6gSIfcyql1HCXIH + accessPre: https://ybl2112.oss-cn-beijing.aliyuncs.com/ ## 沙箱环境 mall: - mgt: - aliPayConfig: - protocol: https - gatewayHost: openapi-sandbox.dl.alipaydev.com - signType: RSA2 - # 沙箱应用id - appId: 9021000135682614 - # 沙箱应用私钥 - privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCQxmQGcaiKjOhayWi+zNTvpp8B5YT8jFFkjLzrD+W+T2Dwf2GfFR4p95zsCJxYeoLWdghMPA6/GMFrLbuVFpaEjuTm4icqA9N8n5d3W0j7gh+wMjZoqyJclAIeb09ut7rY6mWzilA9kWmZnUG7MOWIU70RVRYrfJectCFw/odM9lG4XIVe13X2h+1ecTQyQzLWmnvKFCfo7dQjE7fIYiWfud1ZGUneNs3u73pNWMB6ThGTTCbs0atcgM3fYOg3q7fTxIu9VcaUCJiJ/kNbL9sVEyOrSyx2f2o6w06zdEaOiQFsuDeS8QPYGMg7pf42wAfqCO6hqxQiQT5vp1hvB0o1AgMBAAECggEAIhaEYLwMSispXo8D2cES9iaOU/z91hUX6Qv2Q4anuqqoEZh8nN91Db6etTjFz1NxURvTklelxTsH97t56n26DRY0MWTYgd0Kw9Iz8MeOpKGb4nnAM97vpUq4QQBGfLRIC2ENdzu+7vA5JBFR88hsky/cWaNmJ/EbJauIIDneE7GigMR2HF7kfzdZzOBN4ZEh/ef5NKeCnEieRJJhWRgrgNXVZ44Tqi67AM7ey9pyUtBe7fgzxXtrWXBN9yKaVxxSXm3KJXFQqA6mcilFVZaxMNlAySc4MPTW8lq0ozOCOCunoeIphNz/OVIxGu3/voXFXlBfOKqOkYMVZxMY6OrvtQKBgQD0nIlXK4VW72VaGpz9kxQkRNzJV/yqaqet1GOSlPM2l0RCRFOVVdnvbQdHGPe6+HxHL1dh5MP8T/aHoP+4UXkkQCc8moS2FZxJvFH2QTSZBcSSdGL7GMpROqs38J+XlJzrhNcB20lrW6D7yMeQa4YEcXwdbD8Er/YaIqODBWYYewKBgQCXg+16RLDArciwwhf0TBWZPor2iYFDdwU5UPu7CKOhU1MLfQhG85gGpXHjB6G8cMUi/ezxh/FEl+sWOZegpkPwL5/BQS9tNYWIaC4kipPF/a5Up4DMYUHVAuuPwNqqXpvgU+rGjCns0wtPRnjrkghLkc3oTSID7o7pzUwIk2whDwKBgAys3+EIfExY82OL5X6uVGjcuKQmTw11oWK8krxRw5iclgjpCXu/ix+BAtOIU634mlgF9/02oYE9k4TLrvSaJDDgsifNyfq1e/fGLmkYT+VuCxWbulVQn4s+AwlPCrYMGWWK6KlL9638fYcOjGjLaZJpXwkXRtyzUYlhKh/r87JpAoGBAIavRp2mi/xrPvgpQQPv0k9L8llfOCHRnjoqC+thrZsNp8eRmJcBmMVnskofEZ2iHQuS71pw/n58EQTLo0ayJbhPjVJL8K3CovXzrfjbmqqoa5xi3bJQTiXdF6rMw1QpD6Uk05E1LVuQ6v/IZFr7kBYlAQWb8z3NhQq+bPU+nyLvAoGAGpBbSM8gPzdWQqkHoos0icu3cj0GhN3MU7+1Eb/rsXyh/lk5wtZTEnHjwhdUOUtwVNjvrv7CzA7unhOoaM6YcE/Zpd4zt8pjqH1Mhds7UHf4Xg+A+J4G6meYnhSwfBpOub02ncsqfBlXE0qhFv6AvcMewWndyLb8EYaUUXTYkG0= - # 沙箱应用公钥证书 - appCertPath: cert/appPublicCert.crt - # 沙箱支付宝公钥证书路径 - alipayCertPath: cert/alipayPublicCert.crt - # 沙箱支付宝根证书路径 - alipayRootCertPath: cert/alipayRootCert.crt - notifyUrl: https://53a65908.r27.cpolar.top/ali/pay/notify - # 沙箱支付宝网关 - gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do + mgt: + aliPayConfig: + protocol: https + gatewayHost: openapi-sandbox.dl.alipaydev.com + signType: RSA2 + # 沙箱应用id + appId: 9021000135682614 + # 沙箱应用私钥 + privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCQxmQGcaiKjOhayWi+zNTvpp8B5YT8jFFkjLzrD+W+T2Dwf2GfFR4p95zsCJxYeoLWdghMPA6/GMFrLbuVFpaEjuTm4icqA9N8n5d3W0j7gh+wMjZoqyJclAIeb09ut7rY6mWzilA9kWmZnUG7MOWIU70RVRYrfJectCFw/odM9lG4XIVe13X2h+1ecTQyQzLWmnvKFCfo7dQjE7fIYiWfud1ZGUneNs3u73pNWMB6ThGTTCbs0atcgM3fYOg3q7fTxIu9VcaUCJiJ/kNbL9sVEyOrSyx2f2o6w06zdEaOiQFsuDeS8QPYGMg7pf42wAfqCO6hqxQiQT5vp1hvB0o1AgMBAAECggEAIhaEYLwMSispXo8D2cES9iaOU/z91hUX6Qv2Q4anuqqoEZh8nN91Db6etTjFz1NxURvTklelxTsH97t56n26DRY0MWTYgd0Kw9Iz8MeOpKGb4nnAM97vpUq4QQBGfLRIC2ENdzu+7vA5JBFR88hsky/cWaNmJ/EbJauIIDneE7GigMR2HF7kfzdZzOBN4ZEh/ef5NKeCnEieRJJhWRgrgNXVZ44Tqi67AM7ey9pyUtBe7fgzxXtrWXBN9yKaVxxSXm3KJXFQqA6mcilFVZaxMNlAySc4MPTW8lq0ozOCOCunoeIphNz/OVIxGu3/voXFXlBfOKqOkYMVZxMY6OrvtQKBgQD0nIlXK4VW72VaGpz9kxQkRNzJV/yqaqet1GOSlPM2l0RCRFOVVdnvbQdHGPe6+HxHL1dh5MP8T/aHoP+4UXkkQCc8moS2FZxJvFH2QTSZBcSSdGL7GMpROqs38J+XlJzrhNcB20lrW6D7yMeQa4YEcXwdbD8Er/YaIqODBWYYewKBgQCXg+16RLDArciwwhf0TBWZPor2iYFDdwU5UPu7CKOhU1MLfQhG85gGpXHjB6G8cMUi/ezxh/FEl+sWOZegpkPwL5/BQS9tNYWIaC4kipPF/a5Up4DMYUHVAuuPwNqqXpvgU+rGjCns0wtPRnjrkghLkc3oTSID7o7pzUwIk2whDwKBgAys3+EIfExY82OL5X6uVGjcuKQmTw11oWK8krxRw5iclgjpCXu/ix+BAtOIU634mlgF9/02oYE9k4TLrvSaJDDgsifNyfq1e/fGLmkYT+VuCxWbulVQn4s+AwlPCrYMGWWK6KlL9638fYcOjGjLaZJpXwkXRtyzUYlhKh/r87JpAoGBAIavRp2mi/xrPvgpQQPv0k9L8llfOCHRnjoqC+thrZsNp8eRmJcBmMVnskofEZ2iHQuS71pw/n58EQTLo0ayJbhPjVJL8K3CovXzrfjbmqqoa5xi3bJQTiXdF6rMw1QpD6Uk05E1LVuQ6v/IZFr7kBYlAQWb8z3NhQq+bPU+nyLvAoGAGpBbSM8gPzdWQqkHoos0icu3cj0GhN3MU7+1Eb/rsXyh/lk5wtZTEnHjwhdUOUtwVNjvrv7CzA7unhOoaM6YcE/Zpd4zt8pjqH1Mhds7UHf4Xg+A+J4G6meYnhSwfBpOub02ncsqfBlXE0qhFv6AvcMewWndyLb8EYaUUXTYkG0= + # 沙箱应用公钥证书 + appCertPath: cert/appPublicCert.crt + # 沙箱支付宝公钥证书路径 + alipayCertPath: cert/alipayPublicCert.crt + # 沙箱支付宝根证书路径 + alipayRootCertPath: cert/alipayRootCert.crt + notifyUrl: https://53a65908.r27.cpolar.top/ali/pay/notify + # 沙箱支付宝网关 + gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do huawei: - obs: - ak: NWXJ93POA9NCDVV1WQYJ - sk: 3aBuzYdUgSsxdNnycD2mESnVwtcyjAKPxqewE79N - bucketName: mcwl - upload: - endPoint: obs.cn-south-1.myhuaweicloud.com + obs: + ak: NWXJ93POA9NCDVV1WQYJ + sk: 3aBuzYdUgSsxdNnycD2mESnVwtcyjAKPxqewE79N + bucketName: mcwl + upload: + endPoint: obs.cn-south-1.myhuaweicloud.com diff --git a/mcwl-admin/src/main/resources/application-druid.yml b/mcwl-admin/src/main/resources/application-druid.yml index 2d1d4d6..897e6ec 100644 --- a/mcwl-admin/src/main/resources/application-druid.yml +++ b/mcwl-admin/src/main/resources/application-druid.yml @@ -1,158 +1,158 @@ # 数据源配置 spring: - #mq - rabbitmq: - host: 113.45.74.175 - port: 5672 - username: guest - password: guest - virtualHost: / - listener: - simple: - prefetch: 1 # 每次之能获取一条 - acknowledge-mode: manual # 设置消费端手动ack确认 - retry: - enabled: true # 是否支持重试 - # 生产者配置 - publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange) - publisher-returns: true #确认消息已发送到队列(Queue) + #mq + rabbitmq: + host: 113.45.74.175 + port: 5672 + username: guest + password: guest + virtualHost: / + listener: + simple: + prefetch: 1 # 每次之能获取一条 + acknowledge-mode: manual # 设置消费端手动ack确认 + retry: + enabled: true # 是否支持重试 + # 生产者配置 + publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange) + publisher-returns: true #确认消息已发送到队列(Queue) - datasource: - type: com.alibaba.druid.pool.DruidDataSource - driverClassName: com.mysql.cj.jdbc.Driver - druid: - # 主库数据源 - master: - url: jdbc:mysql://113.45.74.175:3306/mcwl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: Leng1128 - # 从库数据源 - slave: - # 从数据源开关/默认关闭 - enabled: false - url: - username: - password: - # 初始连接数 - initialSize: 5 - # 最小连接池数量 - minIdle: 10 - # 最大连接池数量 - maxActive: 20 - # 配置获取连接等待超时的时间 - maxWait: 60000 - # 配置连接超时时间 - connectTimeout: 30000 - # 配置网络超时时间 - socketTimeout: 60000 - # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 - timeBetweenEvictionRunsMillis: 60000 - # 配置一个连接在池中最小生存的时间,单位是毫秒 - minEvictableIdleTimeMillis: 300000 - # 配置一个连接在池中最大生存的时间,单位是毫秒 - maxEvictableIdleTimeMillis: 900000 - # 配置检测连接是否有效 - validationQuery: SELECT 1 FROM DUAL - testWhileIdle: true - testOnBorrow: false - testOnReturn: false - webStatFilter: - enabled: true - statViewServlet: - enabled: true - # 设置白名单,不填则允许所有访问 - allow: - url-pattern: /druid/* - # 控制台管理用户名和密码 - login-username: mcwl - login-password: 123456 - filter: - stat: - enabled: true - # 慢SQL记录 - log-slow-sql: true - slow-sql-millis: 1000 - merge-sql: true - wall: - config: - multi-statement-allow: true - # redis 配置 - redis: - # 地址 - host: 113.45.74.175 - # 端口,默认为6379 - port: 6370 - # 数据库索引 - database: 0 - # 密码 - password: MuYu_Cloud@Redis - # 连接超时时间 - timeout: 10s - lettuce: - pool: - # 连接池中的最小空闲连接 - min-idle: 0 - # 连接池中的最大空闲连接 - max-idle: 8 - # 连接池的最大数据库连接数 - max-active: 8 - # #连接池最大阻塞等待时间(使用负值表示没有限制) - max-wait: -1ms + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + druid: + # 主库数据源 + master: + url: jdbc:mysql://113.45.74.175:3306/mcwl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: Leng1128 + # 从库数据源 + slave: + # 从数据源开关/默认关闭 + enabled: false + url: + username: + password: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: mcwl + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + # redis 配置 + redis: + # 地址 + host: 113.45.74.175 + # 端口,默认为6379 + port: 6370 + # 数据库索引 + database: 0 + # 密码 + password: MuYu_Cloud@Redis + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms # token配置 token: - # 令牌自定义标识 - header: Authorization - # 令牌密钥 - secret: abcdefghijklmnopqrstuvwxyz - # 令牌有效期(默认30分钟) - expireTime: 30 + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 30 # 公众号配置 wechat: - # 应用ID - appid: wx82d4c3c96f0ffa5b - # 应用密钥 - secret: abbabcf1da711a3bbd95387ec83edcac + # 应用ID + appid: wx82d4c3c96f0ffa5b + # 应用密钥 + secret: abbabcf1da711a3bbd95387ec83edcac # yml版(application.yml) aliyun: - oss: - bucketName: ybl2112 - endpoint: oss-cn-beijing.aliyuncs.com - accessKeyId: LTAI5tSHZZ8wHJRP8X4r9TXT - accessKeySecret: F82IVNx0IGJ3AnP6gSIfcyql1HCXIH - accessPre: https://ybl2112.oss-cn-beijing.aliyuncs.com/ + oss: + bucketName: ybl2112 + endpoint: oss-cn-beijing.aliyuncs.com + accessKeyId: LTAI5tSHZZ8wHJRP8X4r9TXT + accessKeySecret: F82IVNx0IGJ3AnP6gSIfcyql1HCXIH + accessPre: https://ybl2112.oss-cn-beijing.aliyuncs.com/ # 线上环境 mall: - mgt: - aliPayConfig: - protocol: https - gatewayHost: openapi-sandbox.dl.alipaydev.com - signType: RSA2 - # 线上应用id - appId: 2021005119630093 - # 线上应用私钥 - privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCQuhRU5iHoabfzJS40A+moMKzb5rmi5XbL59K98CW65Oq/rnzQnj6sCYggIXOLCLKUykZ1XIVa+4V7bGFkMDF0236ncPumnVY/tfCSebLyKl+up1mGeH4mAR6ily7nXAhRnXL+A0015bpCa5FCg2ReN68MEmquB/mHfeLqJfRupwzDWnIWxYSKkFCJ0i47KTFYhM93LHHQ8GrZNNaeiJ6q59MruQI83z0HCxN/DzK4Z2rBWGilwPuYcSEa0STPXYhz8XMXUX/Mlp7dd5zyWeNaxkNRWlhKHvhLVPGvteaEcchs476DIWLCRVXEC1372wMKNrKblzRp7Wi5+l+W14k5AgMBAAECggEAHahkPjmivTPc5FC8NSCQI01GPxH6/Ky1OXfaMd4ifTgn+vvQzBeBlFOnt53jRZyUq/T/l1FMaqacZfyBwLw3hlDslXeLuksHv6qTEBEsYH/ad7oHmIzcnQEhvAPrMrIjakYvqGoYynC3SKEudUjjqoMSthCYF+2+bsuXUpBQlNQLqcJ03U+YhfLswwVVujKvAujoLRqDAWeaa0k0y/1Ua4fr5GJq5M+P8TTBRgZlSK5A7Ee6yxMUncZ9W3lTqZtyG0OQLbz9gkr0c63KPisigaOx9eKlCPWFWUWo+zGvQMVijHDTeoJ3dqNmsPtiIE3rDt8anx4a1R7YK6MQ520hAQKBgQDPFMNl4fRmLSOLNShJm/0mBMoIjPmS3+tPBtCiG4oH9F8vYMJWDA1Im0FdgRa2HlCv6hpiVvvQt824RaUYa7XR6aefXKVEWddHvl3utnghMohjK8o8fTmIxADtU5Q2OlWmPdp1v7yDg8yE5P1GlY+AZ3oggxXOIRVtQrNeL7E3iQKBgQCy6nEaUQUCDDts4o7Pn+N402g3/T9Vz/5JtKmDI01EndK5l/yE8CdgGwdiwN1k3la/Nv8P5kaaX2qVcCGoOIQmMNGOxuNfkKCi+bJKdxmFOI4E3iVYT2H6x27QJwNB6bik1i6b2OWT7ckxeB6xMjefNp1u8Qif+vab60OQZSqoMQKBgESRF3Hwsz/xykcZvtFAuT2RcGQMacbcJcnw87v5ambf33SMkUx0iSF1ZttTFvYOa6ET3tCZBKBDe/Z7+QJxB68NstbtkjtjlAjJW8ji2jwDw20y3q/QtvA2Fih++CBMiHeXG3LJnd3eEiYevE5Wz0ExAhspzFqIUdPvtfyFxiQhAoGBAKgc+iGMN3RxIfVx/FbDGe6SVr8lSrnLMlj3VMBQD26GDVcupKwVLCp1uz7jkiQRdtk9R/UcnwK23WOFdVqnoCRygrXx0/wb3ZTFou2tc2Fmfqu8QML19E67zjfwMHNitYjNaAYwi6ewKvg8sjo1wWXs34k7GquYGNjw+w9Wv/pBAoGAUlVHfZ4RncAiiH/x3EDgqmmVikFpLoM2xXxM16nplrjdBE6IzkpgWb/x2ZI6JCMDyoV2kvF6owW2+QEQ81MrHQMtBRpAvd8nI03gXA5VF3uwp4UjfGpoIAGDBrqQv9DM8vx6C6VJNTT5esZWZSw/+PZ4D19l0/n7gICGx3ugw0Q= - # 线上应用公钥证书 - appCertPath: appCertPublicKey_2021005119630093.crt - # 线上支付宝公钥证书路径 - alipayCertPath: cert/alipayCertPublicKey_RSA2.crt - # 线上支付宝根证书路径 - alipayRootCertPath: cert/alipayRootCert.crt - # 线上支付宝公钥 - notifyUrl: https://253d7236.r27.cpolar.top/ali/pay/notify - # 线上支付宝网关 - gatewayUrl: https://openapi.alipay.com/gateway.do + mgt: + aliPayConfig: + protocol: https + gatewayHost: openapi-sandbox.dl.alipaydev.com + signType: RSA2 + # 线上应用id + appId: 2021005119630093 + # 线上应用私钥 + privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCQuhRU5iHoabfzJS40A+moMKzb5rmi5XbL59K98CW65Oq/rnzQnj6sCYggIXOLCLKUykZ1XIVa+4V7bGFkMDF0236ncPumnVY/tfCSebLyKl+up1mGeH4mAR6ily7nXAhRnXL+A0015bpCa5FCg2ReN68MEmquB/mHfeLqJfRupwzDWnIWxYSKkFCJ0i47KTFYhM93LHHQ8GrZNNaeiJ6q59MruQI83z0HCxN/DzK4Z2rBWGilwPuYcSEa0STPXYhz8XMXUX/Mlp7dd5zyWeNaxkNRWlhKHvhLVPGvteaEcchs476DIWLCRVXEC1372wMKNrKblzRp7Wi5+l+W14k5AgMBAAECggEAHahkPjmivTPc5FC8NSCQI01GPxH6/Ky1OXfaMd4ifTgn+vvQzBeBlFOnt53jRZyUq/T/l1FMaqacZfyBwLw3hlDslXeLuksHv6qTEBEsYH/ad7oHmIzcnQEhvAPrMrIjakYvqGoYynC3SKEudUjjqoMSthCYF+2+bsuXUpBQlNQLqcJ03U+YhfLswwVVujKvAujoLRqDAWeaa0k0y/1Ua4fr5GJq5M+P8TTBRgZlSK5A7Ee6yxMUncZ9W3lTqZtyG0OQLbz9gkr0c63KPisigaOx9eKlCPWFWUWo+zGvQMVijHDTeoJ3dqNmsPtiIE3rDt8anx4a1R7YK6MQ520hAQKBgQDPFMNl4fRmLSOLNShJm/0mBMoIjPmS3+tPBtCiG4oH9F8vYMJWDA1Im0FdgRa2HlCv6hpiVvvQt824RaUYa7XR6aefXKVEWddHvl3utnghMohjK8o8fTmIxADtU5Q2OlWmPdp1v7yDg8yE5P1GlY+AZ3oggxXOIRVtQrNeL7E3iQKBgQCy6nEaUQUCDDts4o7Pn+N402g3/T9Vz/5JtKmDI01EndK5l/yE8CdgGwdiwN1k3la/Nv8P5kaaX2qVcCGoOIQmMNGOxuNfkKCi+bJKdxmFOI4E3iVYT2H6x27QJwNB6bik1i6b2OWT7ckxeB6xMjefNp1u8Qif+vab60OQZSqoMQKBgESRF3Hwsz/xykcZvtFAuT2RcGQMacbcJcnw87v5ambf33SMkUx0iSF1ZttTFvYOa6ET3tCZBKBDe/Z7+QJxB68NstbtkjtjlAjJW8ji2jwDw20y3q/QtvA2Fih++CBMiHeXG3LJnd3eEiYevE5Wz0ExAhspzFqIUdPvtfyFxiQhAoGBAKgc+iGMN3RxIfVx/FbDGe6SVr8lSrnLMlj3VMBQD26GDVcupKwVLCp1uz7jkiQRdtk9R/UcnwK23WOFdVqnoCRygrXx0/wb3ZTFou2tc2Fmfqu8QML19E67zjfwMHNitYjNaAYwi6ewKvg8sjo1wWXs34k7GquYGNjw+w9Wv/pBAoGAUlVHfZ4RncAiiH/x3EDgqmmVikFpLoM2xXxM16nplrjdBE6IzkpgWb/x2ZI6JCMDyoV2kvF6owW2+QEQ81MrHQMtBRpAvd8nI03gXA5VF3uwp4UjfGpoIAGDBrqQv9DM8vx6C6VJNTT5esZWZSw/+PZ4D19l0/n7gICGx3ugw0Q= + # 线上应用公钥证书 + appCertPath: appCertPublicKey_2021005119630093.crt + # 线上支付宝公钥证书路径 + alipayCertPath: cert/alipayCertPublicKey_RSA2.crt + # 线上支付宝根证书路径 + alipayRootCertPath: cert/alipayRootCert.crt + # 线上支付宝公钥 + notifyUrl: https://253d7236.r27.cpolar.top/ali/pay/notify + # 线上支付宝网关 + gatewayUrl: https://openapi.alipay.com/gateway.do huawei: - obs: - ak: NWXJ93POA9NCDVV1WQYJ - sk: 3aBuzYdUgSsxdNnycD2mESnVwtcyjAKPxqewE79N - bucketName: mcwl - upload: - endPoint: obs.cn-south-1.myhuaweicloud.com + obs: + ak: NWXJ93POA9NCDVV1WQYJ + sk: 3aBuzYdUgSsxdNnycD2mESnVwtcyjAKPxqewE79N + bucketName: mcwl + upload: + endPoint: obs.cn-south-1.myhuaweicloud.com diff --git a/mcwl-common/pom.xml b/mcwl-common/pom.xml index 0d16b32..a953e81 100644 --- a/mcwl-common/pom.xml +++ b/mcwl-common/pom.xml @@ -402,6 +402,12 @@ 3.19.7 + + + org.springframework.boot + spring-boot-starter-websocket + + diff --git a/mcwl-common/src/main/java/com/mcwl/common/config/JacksonConfig.java b/mcwl-common/src/main/java/com/mcwl/common/config/JacksonConfig.java new file mode 100644 index 0000000..3bedc10 --- /dev/null +++ b/mcwl-common/src/main/java/com/mcwl/common/config/JacksonConfig.java @@ -0,0 +1,18 @@ +package com.mcwl.common.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + } +} \ No newline at end of file diff --git a/mcwl-communityCenter/pom.xml b/mcwl-communityCenter/pom.xml index a630525..9a80243 100644 --- a/mcwl-communityCenter/pom.xml +++ b/mcwl-communityCenter/pom.xml @@ -31,6 +31,26 @@ com.mcwl mcwl-system + + + + + + + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter + 1.0.0-M5.1 + + + + io.projectreactor.netty + reactor-netty-http + 1.1.6 + + + diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/config/WebSocketConfig.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/config/WebSocketConfig.java new file mode 100644 index 0000000..192b4a1 --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/config/WebSocketConfig.java @@ -0,0 +1,25 @@ +package com.mcwl.communityCenter.config; + +import com.mcwl.communityCenter.webSocket.ChatWebSocketPoint; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(chatWebSocketPoint(), "/chat") + .setAllowedOrigins("*"); + } + + @Bean + public ChatWebSocketPoint chatWebSocketPoint() { + return new ChatWebSocketPoint(); + } + +} \ No newline at end of file diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/domain/DeepSeekRequest.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/domain/DeepSeekRequest.java new file mode 100644 index 0000000..15b8152 --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/domain/DeepSeekRequest.java @@ -0,0 +1,68 @@ +package com.mcwl.communityCenter.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.List; + +/** + * DeepSeek API请求参数封装类 + */ +public class DeepSeekRequest { + private String model; + private List messages; + @JsonProperty("max_tokens") + private Integer maxTokens; + private Double temperature; + private Boolean stream; + + // 构造方法 + public DeepSeekRequest() { + this.messages = new ArrayList<>(); + } + + // 添加消息的便捷方法 + public void addMessage(String role, String content) { + if (this.messages == null) { + this.messages = new ArrayList<>(); + } + this.messages.add(new Message(role, content)); + } + + + + // Getters and Setters + // 注意:必须包含所有需要序列化的字段的getter方法 + + public static class Message { + private String role; + private String content; + + public Message(String role, String content) { + this.role = role; + this.content = content; + } + + // Getters + public String getRole() { return role; } + public String getContent() { return content; } + } + + // 以下是各字段的getter/setter + public String getModel() { return model; } + public void setModel(String model) { this.model = model; } + + public List getMessages() { return messages; } + public void setMessages(List messages) { this.messages = messages; } + + public Integer getMaxTokens() { return maxTokens; } + public void setMaxTokens(Integer maxTokens) { this.maxTokens = maxTokens; } + + public Double getTemperature() { return temperature; } + public void setTemperature(Double temperature) { this.temperature = temperature; } + public Boolean getStream() { + return this.stream; + } + public void setStream(boolean stream) { + this.stream = stream; + } +} \ No newline at end of file diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/AIService.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/AIService.java new file mode 100644 index 0000000..4069449 --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/AIService.java @@ -0,0 +1,10 @@ +package com.mcwl.communityCenter.service; + +import reactor.core.publisher.Flux; + +public interface AIService { + + Flux getDeepSeekResponseStream(String message); + + +} diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/CustomerService.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/CustomerService.java new file mode 100644 index 0000000..5115fab --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/CustomerService.java @@ -0,0 +1,11 @@ +package com.mcwl.communityCenter.service; + +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; + +public interface CustomerService { + void transferToHuman(String userId, WebSocketSession session); + + void handleCustomerMessage(String userId, String message) throws IOException; +} diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/AIServiceImpl.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/AIServiceImpl.java new file mode 100644 index 0000000..594e430 --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/AIServiceImpl.java @@ -0,0 +1,84 @@ +package com.mcwl.communityCenter.service.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.mcwl.common.utils.StringUtils; +import com.mcwl.communityCenter.domain.DeepSeekRequest; +import com.mcwl.communityCenter.service.AIService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.SignalType; + +import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; + +@Service +@RequiredArgsConstructor +public class AIServiceImpl implements AIService { + + @Value("${spring.ai.dashscope.base-url}") + private String DEEPSEEK_API_URL; + @Value("${spring.ai.dashscope.api-key}") + private String API_KEY; + @Value("${spring.ai.dashscope.chat.options.model}") + private String apiModel; + + + private final ObjectMapper objectMapper; + + @Override + public Flux getDeepSeekResponseStream(String message) { + WebClient client = WebClient.builder() + .baseUrl(DEEPSEEK_API_URL) + .defaultHeader("Authorization", "Bearer " + API_KEY) + .build(); + + // 构建请求体(推荐使用对象映射) + DeepSeekRequest request = new DeepSeekRequest(); + request.setModel(apiModel); + // 添加对话历史 + request.addMessage("user", message); + request.setMaxTokens(500); + request.setTemperature(0.7); + request.setStream(true); + + return client.post() + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .retrieve() + .bodyToFlux(String.class) // 原始数据流 + .takeUntil(data -> data.contains("[DONE]")) // 遇到结束标记停止 + .flatMap(json -> parseContentFromJson(json)) // 解析内容 + .onErrorResume(e -> Flux.just("服务暂时不可用"));// 错误处理 + + } + + + // 辅助方法:从 JSON 中提取 content + private Mono parseContentFromJson(String json) { + try { + JsonNode root = objectMapper.readTree(json); + String reasoning_content = root.path("choices") + .get(0) + .path("delta") + .path("reasoning_content") + .asText(""); + String content = root.path("choices") + .get(0) + .path("delta") + .path("content") + .asText(""); + System.out.print(StringUtils.isNotEmpty(reasoning_content) ? reasoning_content : content); + return Mono.just(StringUtils.isNotEmpty(reasoning_content) ? reasoning_content : content); + } catch (JsonProcessingException e) { + return Mono.error(e); + } + } + +} diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/CustomerServiceImpl.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/CustomerServiceImpl.java new file mode 100644 index 0000000..49fa65c --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/service/impl/CustomerServiceImpl.java @@ -0,0 +1,32 @@ +package com.mcwl.communityCenter.service.impl; + +import com.mcwl.communityCenter.service.CustomerService; +import org.springframework.stereotype.Service; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class CustomerServiceImpl implements CustomerService { + + private static final Map activeSessions = new ConcurrentHashMap<>(); + + @Override + public void transferToHuman(String userId, WebSocketSession session) { + activeSessions.put(userId, session); + // 这里可以添加通知客服人员的逻辑 + } + + @Override + public void handleCustomerMessage(String userId, String message) throws IOException { + // 应实现消息队列或持久化存储 + // 先返回固定响应 + WebSocketSession session = activeSessions.get(userId); + if (session != null && session.isOpen()) { + session.sendMessage(new TextMessage("[客服] 您好,当前为人工服务模式")); + } + } +} diff --git a/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocketPoint.java b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocketPoint.java new file mode 100644 index 0000000..adfd9bb --- /dev/null +++ b/mcwl-communityCenter/src/main/java/com/mcwl/communityCenter/webSocket/ChatWebSocketPoint.java @@ -0,0 +1,92 @@ +package com.mcwl.communityCenter.webSocket; + +import com.mcwl.communityCenter.service.AIService; +import com.mcwl.communityCenter.service.CustomerService; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; +import reactor.core.Disposable; +import reactor.core.publisher.Flux; + +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@ServerEndpoint("/chat") +@NoArgsConstructor +public class ChatWebSocketPoint extends AbstractWebSocketHandler { + private final Map userModes = new ConcurrentHashMap<>(); + + // 存储会话与订阅的映射关系 + private final Map sessionSubscriptions = new ConcurrentHashMap<>(); + + @Autowired + private AIService aiService; + + @Autowired + private CustomerService customerService; + + // 构造函数注入服务... + + @Override + public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String userId = session.getId(); + String userMessage = message.getPayload(); + // 判断当前模式 + if (userModes.getOrDefault(userId, false)) { + // 人工模式 + customerService.handleCustomerMessage(userId, userMessage); + } else { + // AI 流式响应模式 + Flux responseStream = aiService.getDeepSeekResponseStream(userMessage); + + // 订阅响应流并存储 Disposable + Disposable disposable = responseStream + .doOnNext(chunk -> sendText(session, chunk)) + .doOnComplete(() -> sendText(session, "[END]")) + .doOnError(e -> sendText(session, "[ERROR] " + e.getMessage())) + .subscribe(); + + sessionSubscriptions.put(userId, disposable); + } + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + super.afterConnectionEstablished(session); + userModes.put(session.getId(), false); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + // 清理订阅资源 + String sessionId = session.getId(); + Disposable disposable = sessionSubscriptions.remove(sessionId); + if (disposable != null && !disposable.isDisposed()) { + disposable.dispose(); + System.out.println("已清理会话资源: " + sessionId); + } + } + + // 添加模式切换方法(根据业务需求) + public void switchUserMode(String sessionId, boolean isHumanMode) { + userModes.put(sessionId, isHumanMode); + } + + // 线程安全的发送方法 + private void sendText(WebSocketSession session, String text) { + try { + if (session.isOpen()) { + synchronized (session) { // WebSocketSession 非线程安全 + session.sendMessage(new TextMessage(text)); + } + } + } catch (IOException e) { + System.out.println("WebSocket 发送失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/mcwl-framework/src/main/java/com/mcwl/framework/config/ResourcesConfig.java b/mcwl-framework/src/main/java/com/mcwl/framework/config/ResourcesConfig.java index cc23821..d5af248 100644 --- a/mcwl-framework/src/main/java/com/mcwl/framework/config/ResourcesConfig.java +++ b/mcwl-framework/src/main/java/com/mcwl/framework/config/ResourcesConfig.java @@ -1,13 +1,16 @@ package com.mcwl.framework.config; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.CacheControl; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -48,6 +51,26 @@ public class ResourcesConfig implements WebMvcConfigurer registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); } + @Override + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + // 核心线程数(根据服务器CPU核心调整) + executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2); + // 最大线程数 + executor.setMaxPoolSize(50); + // 队列容量 + executor.setQueueCapacity(1000); + // 线程名前缀 + executor.setThreadNamePrefix("mvc-async-"); + // 拒绝策略(CallerRunsPolicy:由调用线程处理该任务) + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + + configurer.setTaskExecutor(executor); + // 设置异步超时时间(毫秒) + configurer.setDefaultTimeout(30_000); + } + /** * 跨域配置 */