feat(communityCenter): ai窗口
parent
572ea22d4e
commit
a9a2fb5d68
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,103 +1,112 @@
|
||||||
# 数据源配置
|
# 数据源配置
|
||||||
spring:
|
spring:
|
||||||
#mq
|
#mq
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
host: 1.13.246.108
|
host: 1.13.246.108
|
||||||
port: 5672
|
port: 5672
|
||||||
username: guest
|
username: guest
|
||||||
password: guest
|
password: guest
|
||||||
virtualHost: /
|
virtualHost: /
|
||||||
listener:
|
listener:
|
||||||
simple:
|
simple:
|
||||||
prefetch: 1 # 每次之能获取一条
|
prefetch: 1 # 每次之能获取一条
|
||||||
acknowledge-mode: manual # 设置消费端手动ack确认
|
acknowledge-mode: manual # 设置消费端手动ack确认
|
||||||
retry:
|
retry:
|
||||||
enabled: true # 是否支持重试
|
enabled: true # 是否支持重试
|
||||||
# 生产者配置
|
# 生产者配置
|
||||||
publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
|
publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
|
||||||
publisher-returns: true #确认消息已发送到队列(Queue)
|
publisher-returns: true #确认消息已发送到队列(Queue)
|
||||||
|
|
||||||
datasource:
|
datasource:
|
||||||
type: com.alibaba.druid.pool.DruidDataSource
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
driverClassName: com.mysql.cj.jdbc.Driver
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
druid:
|
druid:
|
||||||
# 主库数据源
|
# 主库数据源
|
||||||
master:
|
master:
|
||||||
url: jdbc:mysql://1.13.246.108:3306/mcwl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
url: jdbc:mysql://1.13.246.108:3306/mcwl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||||
username: root
|
username: root
|
||||||
password: ybl123456@
|
password: ybl123456@
|
||||||
# 从库数据源
|
# 从库数据源
|
||||||
slave:
|
slave:
|
||||||
# 从数据源开关/默认关闭
|
# 从数据源开关/默认关闭
|
||||||
enabled: false
|
enabled: false
|
||||||
url:
|
url:
|
||||||
username:
|
username:
|
||||||
password:
|
password:
|
||||||
# 初始连接数
|
# 初始连接数
|
||||||
initialSize: 5
|
initialSize: 5
|
||||||
# 最小连接池数量
|
# 最小连接池数量
|
||||||
minIdle: 10
|
minIdle: 10
|
||||||
# 最大连接池数量
|
# 最大连接池数量
|
||||||
maxActive: 20
|
maxActive: 20
|
||||||
# 配置获取连接等待超时的时间
|
# 配置获取连接等待超时的时间
|
||||||
maxWait: 60000
|
maxWait: 60000
|
||||||
# 配置连接超时时间
|
# 配置连接超时时间
|
||||||
connectTimeout: 30000
|
connectTimeout: 30000
|
||||||
# 配置网络超时时间
|
# 配置网络超时时间
|
||||||
socketTimeout: 60000
|
socketTimeout: 60000
|
||||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||||
timeBetweenEvictionRunsMillis: 60000
|
timeBetweenEvictionRunsMillis: 60000
|
||||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||||
minEvictableIdleTimeMillis: 300000
|
minEvictableIdleTimeMillis: 300000
|
||||||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||||
maxEvictableIdleTimeMillis: 900000
|
maxEvictableIdleTimeMillis: 900000
|
||||||
# 配置检测连接是否有效
|
# 配置检测连接是否有效
|
||||||
validationQuery: SELECT 1 FROM DUAL
|
validationQuery: SELECT 1 FROM DUAL
|
||||||
testWhileIdle: true
|
testWhileIdle: true
|
||||||
testOnBorrow: false
|
testOnBorrow: false
|
||||||
testOnReturn: false
|
testOnReturn: false
|
||||||
webStatFilter:
|
webStatFilter:
|
||||||
enabled: true
|
enabled: true
|
||||||
statViewServlet:
|
statViewServlet:
|
||||||
enabled: true
|
enabled: true
|
||||||
# 设置白名单,不填则允许所有访问
|
# 设置白名单,不填则允许所有访问
|
||||||
allow:
|
allow:
|
||||||
url-pattern: /druid/*
|
url-pattern: /druid/*
|
||||||
# 控制台管理用户名和密码
|
# 控制台管理用户名和密码
|
||||||
login-username: mcwl
|
login-username: mcwl
|
||||||
login-password: 123456
|
login-password: 123456
|
||||||
filter:
|
filter:
|
||||||
stat:
|
stat:
|
||||||
enabled: true
|
enabled: true
|
||||||
# 慢SQL记录
|
# 慢SQL记录
|
||||||
log-slow-sql: true
|
log-slow-sql: true
|
||||||
slow-sql-millis: 1000
|
slow-sql-millis: 1000
|
||||||
merge-sql: true
|
merge-sql: true
|
||||||
wall:
|
wall:
|
||||||
config:
|
config:
|
||||||
multi-statement-allow: true
|
multi-statement-allow: true
|
||||||
# redis 配置
|
# redis 配置
|
||||||
redis:
|
redis:
|
||||||
# 地址
|
# 地址
|
||||||
host: 1.13.246.108
|
host: 1.13.246.108
|
||||||
# 端口,默认为6379
|
# 端口,默认为6379
|
||||||
port: 6370
|
port: 6370
|
||||||
# 数据库索引
|
# 数据库索引
|
||||||
database: 0
|
database: 0
|
||||||
# 密码
|
# 密码
|
||||||
password: MuYu_Cloud@Redis
|
password: MuYu_Cloud@Redis
|
||||||
# 连接超时时间
|
# 连接超时时间
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
# 连接池中的最小空闲连接
|
# 连接池中的最小空闲连接
|
||||||
min-idle: 0
|
min-idle: 0
|
||||||
# 连接池中的最大空闲连接
|
# 连接池中的最大空闲连接
|
||||||
max-idle: 8
|
max-idle: 8
|
||||||
# 连接池的最大数据库连接数
|
# 连接池的最大数据库连接数
|
||||||
max-active: 8
|
max-active: 8
|
||||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
max-wait: -1ms
|
max-wait: -1ms
|
||||||
|
|
||||||
|
#ai配置
|
||||||
|
ai:
|
||||||
|
dashscope:
|
||||||
|
base-url: https://api.deepseek.com/chat/completions
|
||||||
|
api-key: sk-5d1f611b6ba74b90ae9e3dff5aaa508a
|
||||||
|
chat:
|
||||||
|
options:
|
||||||
|
model: deepseek-chat
|
||||||
|
|
||||||
|
|
||||||
# token配置
|
# token配置
|
||||||
|
@ -111,47 +120,47 @@ token:
|
||||||
|
|
||||||
# 公众号配置
|
# 公众号配置
|
||||||
wechat:
|
wechat:
|
||||||
# 应用ID
|
# 应用ID
|
||||||
appid: wx82d4c3c96f0ffa5b
|
appid: wx82d4c3c96f0ffa5b
|
||||||
# 应用密钥
|
# 应用密钥
|
||||||
secret: abbabcf1da711a3bbd95387ec83edcac
|
secret: abbabcf1da711a3bbd95387ec83edcac
|
||||||
|
|
||||||
# yml版(application.yml)
|
# yml版(application.yml)
|
||||||
aliyun:
|
aliyun:
|
||||||
oss:
|
oss:
|
||||||
bucketName: ybl2112
|
bucketName: ybl2112
|
||||||
endpoint: oss-cn-beijing.aliyuncs.com
|
endpoint: oss-cn-beijing.aliyuncs.com
|
||||||
accessKeyId: LTAI5tSHZZ8wHJRP8X4r9TXT
|
accessKeyId: LTAI5tSHZZ8wHJRP8X4r9TXT
|
||||||
accessKeySecret: F82IVNx0IGJ3AnP6gSIfcyql1HCXIH
|
accessKeySecret: F82IVNx0IGJ3AnP6gSIfcyql1HCXIH
|
||||||
accessPre: https://ybl2112.oss-cn-beijing.aliyuncs.com/
|
accessPre: https://ybl2112.oss-cn-beijing.aliyuncs.com/
|
||||||
|
|
||||||
## 沙箱环境
|
## 沙箱环境
|
||||||
mall:
|
mall:
|
||||||
mgt:
|
mgt:
|
||||||
aliPayConfig:
|
aliPayConfig:
|
||||||
protocol: https
|
protocol: https
|
||||||
gatewayHost: openapi-sandbox.dl.alipaydev.com
|
gatewayHost: openapi-sandbox.dl.alipaydev.com
|
||||||
signType: RSA2
|
signType: RSA2
|
||||||
# 沙箱应用id
|
# 沙箱应用id
|
||||||
appId: 9021000135682614
|
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=
|
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
|
appCertPath: cert/appPublicCert.crt
|
||||||
# 沙箱支付宝公钥证书路径
|
# 沙箱支付宝公钥证书路径
|
||||||
alipayCertPath: cert/alipayPublicCert.crt
|
alipayCertPath: cert/alipayPublicCert.crt
|
||||||
# 沙箱支付宝根证书路径
|
# 沙箱支付宝根证书路径
|
||||||
alipayRootCertPath: cert/alipayRootCert.crt
|
alipayRootCertPath: cert/alipayRootCert.crt
|
||||||
notifyUrl: https://53a65908.r27.cpolar.top/ali/pay/notify
|
notifyUrl: https://53a65908.r27.cpolar.top/ali/pay/notify
|
||||||
# 沙箱支付宝网关
|
# 沙箱支付宝网关
|
||||||
gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
|
||||||
|
|
||||||
huawei:
|
huawei:
|
||||||
obs:
|
obs:
|
||||||
ak: NWXJ93POA9NCDVV1WQYJ
|
ak: NWXJ93POA9NCDVV1WQYJ
|
||||||
sk: 3aBuzYdUgSsxdNnycD2mESnVwtcyjAKPxqewE79N
|
sk: 3aBuzYdUgSsxdNnycD2mESnVwtcyjAKPxqewE79N
|
||||||
bucketName: mcwl
|
bucketName: mcwl
|
||||||
upload:
|
upload:
|
||||||
endPoint: obs.cn-south-1.myhuaweicloud.com
|
endPoint: obs.cn-south-1.myhuaweicloud.com
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,158 +1,158 @@
|
||||||
# 数据源配置
|
# 数据源配置
|
||||||
spring:
|
spring:
|
||||||
#mq
|
#mq
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
host: 113.45.74.175
|
host: 113.45.74.175
|
||||||
port: 5672
|
port: 5672
|
||||||
username: guest
|
username: guest
|
||||||
password: guest
|
password: guest
|
||||||
virtualHost: /
|
virtualHost: /
|
||||||
listener:
|
listener:
|
||||||
simple:
|
simple:
|
||||||
prefetch: 1 # 每次之能获取一条
|
prefetch: 1 # 每次之能获取一条
|
||||||
acknowledge-mode: manual # 设置消费端手动ack确认
|
acknowledge-mode: manual # 设置消费端手动ack确认
|
||||||
retry:
|
retry:
|
||||||
enabled: true # 是否支持重试
|
enabled: true # 是否支持重试
|
||||||
# 生产者配置
|
# 生产者配置
|
||||||
publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
|
publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
|
||||||
publisher-returns: true #确认消息已发送到队列(Queue)
|
publisher-returns: true #确认消息已发送到队列(Queue)
|
||||||
|
|
||||||
datasource:
|
datasource:
|
||||||
type: com.alibaba.druid.pool.DruidDataSource
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
driverClassName: com.mysql.cj.jdbc.Driver
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
druid:
|
druid:
|
||||||
# 主库数据源
|
# 主库数据源
|
||||||
master:
|
master:
|
||||||
url: jdbc:mysql://113.45.74.175:3306/mcwl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
url: jdbc:mysql://113.45.74.175:3306/mcwl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||||
username: root
|
username: root
|
||||||
password: Leng1128
|
password: Leng1128
|
||||||
# 从库数据源
|
# 从库数据源
|
||||||
slave:
|
slave:
|
||||||
# 从数据源开关/默认关闭
|
# 从数据源开关/默认关闭
|
||||||
enabled: false
|
enabled: false
|
||||||
url:
|
url:
|
||||||
username:
|
username:
|
||||||
password:
|
password:
|
||||||
# 初始连接数
|
# 初始连接数
|
||||||
initialSize: 5
|
initialSize: 5
|
||||||
# 最小连接池数量
|
# 最小连接池数量
|
||||||
minIdle: 10
|
minIdle: 10
|
||||||
# 最大连接池数量
|
# 最大连接池数量
|
||||||
maxActive: 20
|
maxActive: 20
|
||||||
# 配置获取连接等待超时的时间
|
# 配置获取连接等待超时的时间
|
||||||
maxWait: 60000
|
maxWait: 60000
|
||||||
# 配置连接超时时间
|
# 配置连接超时时间
|
||||||
connectTimeout: 30000
|
connectTimeout: 30000
|
||||||
# 配置网络超时时间
|
# 配置网络超时时间
|
||||||
socketTimeout: 60000
|
socketTimeout: 60000
|
||||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||||
timeBetweenEvictionRunsMillis: 60000
|
timeBetweenEvictionRunsMillis: 60000
|
||||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||||
minEvictableIdleTimeMillis: 300000
|
minEvictableIdleTimeMillis: 300000
|
||||||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||||
maxEvictableIdleTimeMillis: 900000
|
maxEvictableIdleTimeMillis: 900000
|
||||||
# 配置检测连接是否有效
|
# 配置检测连接是否有效
|
||||||
validationQuery: SELECT 1 FROM DUAL
|
validationQuery: SELECT 1 FROM DUAL
|
||||||
testWhileIdle: true
|
testWhileIdle: true
|
||||||
testOnBorrow: false
|
testOnBorrow: false
|
||||||
testOnReturn: false
|
testOnReturn: false
|
||||||
webStatFilter:
|
webStatFilter:
|
||||||
enabled: true
|
enabled: true
|
||||||
statViewServlet:
|
statViewServlet:
|
||||||
enabled: true
|
enabled: true
|
||||||
# 设置白名单,不填则允许所有访问
|
# 设置白名单,不填则允许所有访问
|
||||||
allow:
|
allow:
|
||||||
url-pattern: /druid/*
|
url-pattern: /druid/*
|
||||||
# 控制台管理用户名和密码
|
# 控制台管理用户名和密码
|
||||||
login-username: mcwl
|
login-username: mcwl
|
||||||
login-password: 123456
|
login-password: 123456
|
||||||
filter:
|
filter:
|
||||||
stat:
|
stat:
|
||||||
enabled: true
|
enabled: true
|
||||||
# 慢SQL记录
|
# 慢SQL记录
|
||||||
log-slow-sql: true
|
log-slow-sql: true
|
||||||
slow-sql-millis: 1000
|
slow-sql-millis: 1000
|
||||||
merge-sql: true
|
merge-sql: true
|
||||||
wall:
|
wall:
|
||||||
config:
|
config:
|
||||||
multi-statement-allow: true
|
multi-statement-allow: true
|
||||||
# redis 配置
|
# redis 配置
|
||||||
redis:
|
redis:
|
||||||
# 地址
|
# 地址
|
||||||
host: 113.45.74.175
|
host: 113.45.74.175
|
||||||
# 端口,默认为6379
|
# 端口,默认为6379
|
||||||
port: 6370
|
port: 6370
|
||||||
# 数据库索引
|
# 数据库索引
|
||||||
database: 0
|
database: 0
|
||||||
# 密码
|
# 密码
|
||||||
password: MuYu_Cloud@Redis
|
password: MuYu_Cloud@Redis
|
||||||
# 连接超时时间
|
# 连接超时时间
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
# 连接池中的最小空闲连接
|
# 连接池中的最小空闲连接
|
||||||
min-idle: 0
|
min-idle: 0
|
||||||
# 连接池中的最大空闲连接
|
# 连接池中的最大空闲连接
|
||||||
max-idle: 8
|
max-idle: 8
|
||||||
# 连接池的最大数据库连接数
|
# 连接池的最大数据库连接数
|
||||||
max-active: 8
|
max-active: 8
|
||||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
max-wait: -1ms
|
max-wait: -1ms
|
||||||
|
|
||||||
|
|
||||||
# token配置
|
# token配置
|
||||||
token:
|
token:
|
||||||
# 令牌自定义标识
|
# 令牌自定义标识
|
||||||
header: Authorization
|
header: Authorization
|
||||||
# 令牌密钥
|
# 令牌密钥
|
||||||
secret: abcdefghijklmnopqrstuvwxyz
|
secret: abcdefghijklmnopqrstuvwxyz
|
||||||
# 令牌有效期(默认30分钟)
|
# 令牌有效期(默认30分钟)
|
||||||
expireTime: 30
|
expireTime: 30
|
||||||
|
|
||||||
# 公众号配置
|
# 公众号配置
|
||||||
wechat:
|
wechat:
|
||||||
# 应用ID
|
# 应用ID
|
||||||
appid: wx82d4c3c96f0ffa5b
|
appid: wx82d4c3c96f0ffa5b
|
||||||
# 应用密钥
|
# 应用密钥
|
||||||
secret: abbabcf1da711a3bbd95387ec83edcac
|
secret: abbabcf1da711a3bbd95387ec83edcac
|
||||||
|
|
||||||
# yml版(application.yml)
|
# yml版(application.yml)
|
||||||
aliyun:
|
aliyun:
|
||||||
oss:
|
oss:
|
||||||
bucketName: ybl2112
|
bucketName: ybl2112
|
||||||
endpoint: oss-cn-beijing.aliyuncs.com
|
endpoint: oss-cn-beijing.aliyuncs.com
|
||||||
accessKeyId: LTAI5tSHZZ8wHJRP8X4r9TXT
|
accessKeyId: LTAI5tSHZZ8wHJRP8X4r9TXT
|
||||||
accessKeySecret: F82IVNx0IGJ3AnP6gSIfcyql1HCXIH
|
accessKeySecret: F82IVNx0IGJ3AnP6gSIfcyql1HCXIH
|
||||||
accessPre: https://ybl2112.oss-cn-beijing.aliyuncs.com/
|
accessPre: https://ybl2112.oss-cn-beijing.aliyuncs.com/
|
||||||
|
|
||||||
# 线上环境
|
# 线上环境
|
||||||
mall:
|
mall:
|
||||||
mgt:
|
mgt:
|
||||||
aliPayConfig:
|
aliPayConfig:
|
||||||
protocol: https
|
protocol: https
|
||||||
gatewayHost: openapi-sandbox.dl.alipaydev.com
|
gatewayHost: openapi-sandbox.dl.alipaydev.com
|
||||||
signType: RSA2
|
signType: RSA2
|
||||||
# 线上应用id
|
# 线上应用id
|
||||||
appId: 2021005119630093
|
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=
|
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
|
appCertPath: appCertPublicKey_2021005119630093.crt
|
||||||
# 线上支付宝公钥证书路径
|
# 线上支付宝公钥证书路径
|
||||||
alipayCertPath: cert/alipayCertPublicKey_RSA2.crt
|
alipayCertPath: cert/alipayCertPublicKey_RSA2.crt
|
||||||
# 线上支付宝根证书路径
|
# 线上支付宝根证书路径
|
||||||
alipayRootCertPath: cert/alipayRootCert.crt
|
alipayRootCertPath: cert/alipayRootCert.crt
|
||||||
# 线上支付宝公钥
|
# 线上支付宝公钥
|
||||||
notifyUrl: https://253d7236.r27.cpolar.top/ali/pay/notify
|
notifyUrl: https://253d7236.r27.cpolar.top/ali/pay/notify
|
||||||
# 线上支付宝网关
|
# 线上支付宝网关
|
||||||
gatewayUrl: https://openapi.alipay.com/gateway.do
|
gatewayUrl: https://openapi.alipay.com/gateway.do
|
||||||
|
|
||||||
huawei:
|
huawei:
|
||||||
obs:
|
obs:
|
||||||
ak: NWXJ93POA9NCDVV1WQYJ
|
ak: NWXJ93POA9NCDVV1WQYJ
|
||||||
sk: 3aBuzYdUgSsxdNnycD2mESnVwtcyjAKPxqewE79N
|
sk: 3aBuzYdUgSsxdNnycD2mESnVwtcyjAKPxqewE79N
|
||||||
bucketName: mcwl
|
bucketName: mcwl
|
||||||
upload:
|
upload:
|
||||||
endPoint: obs.cn-south-1.myhuaweicloud.com
|
endPoint: obs.cn-south-1.myhuaweicloud.com
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -402,6 +402,12 @@
|
||||||
<version>3.19.7</version>
|
<version>3.19.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- WebSocket支持 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,26 @@
|
||||||
<groupId>com.mcwl</groupId>
|
<groupId>com.mcwl</groupId>
|
||||||
<artifactId>mcwl-system</artifactId>
|
<artifactId>mcwl-system</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>com.alibaba</groupId>-->
|
||||||
|
<!-- <artifactId>dashscope-sdk-java</artifactId>-->
|
||||||
|
<!-- <version>2.18.3</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud.ai</groupId>
|
||||||
|
<artifactId>spring-ai-alibaba-starter</artifactId>
|
||||||
|
<version>1.0.0-M5.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor.netty</groupId>
|
||||||
|
<artifactId>reactor-netty-http</artifactId>
|
||||||
|
<version>1.1.6</version> <!-- 使用与你的 Spring Boot 版本兼容的版本 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Message> 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<Message> getMessages() { return messages; }
|
||||||
|
public void setMessages(List<Message> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.mcwl.communityCenter.service;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
public interface AIService {
|
||||||
|
|
||||||
|
Flux<String> getDeepSeekResponseStream(String message);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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<String> 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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<String, WebSocketSession> 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("[客服] 您好,当前为人工服务模式"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, Boolean> userModes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// 存储会话与订阅的映射关系
|
||||||
|
private final Map<String, Disposable> 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<String> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,16 @@
|
||||||
package com.mcwl.framework.config;
|
package com.mcwl.framework.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.CacheControl;
|
import org.springframework.http.CacheControl;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
import org.springframework.web.filter.CorsFilter;
|
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.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
@ -48,6 +51,26 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||||
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跨域配置
|
* 跨域配置
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue