Commit 2e73959a2c13ec22b983c0e47832fcc6da767233

Authored by YunaiV
1 parent c98495df

【代码新增】AI:开启 AI 租户的能力

yudao-module-ai/yudao-module-ai-biz/pom.xml
@@ -32,6 +32,11 @@ @@ -32,6 +32,11 @@
32 <version>${revision}</version> 32 <version>${revision}</version>
33 </dependency> 33 </dependency>
34 34
  35 + <dependency>
  36 + <groupId>cn.iocoder.boot</groupId>
  37 + <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
  38 + </dependency>
  39 +
35 <!-- Web 相关 --> 40 <!-- Web 相关 -->
36 <dependency> 41 <dependency>
37 <groupId>cn.iocoder.boot</groupId> 42 <groupId>cn.iocoder.boot</groupId>
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.http
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 POST {{baseUrl}}/ai/chat/message/send 2 POST {{baseUrl}}/ai/chat/message/send
3 Content-Type: application/json 3 Content-Type: application/json
4 Authorization: {{token}} 4 Authorization: {{token}}
  5 +tenant-id: {{adminTenentId}}
5 6
6 { 7 {
7 "conversationId": "1781604279872581724", 8 "conversationId": "1781604279872581724",
@@ -12,9 +13,10 @@ Authorization: {{token}} @@ -12,9 +13,10 @@ Authorization: {{token}}
12 POST {{baseUrl}}/ai/chat/message/send-stream 13 POST {{baseUrl}}/ai/chat/message/send-stream
13 Content-Type: application/json 14 Content-Type: application/json
14 Authorization: {{token}} 15 Authorization: {{token}}
  16 +tenant-id: {{adminTenentId}}
15 17
16 { 18 {
17 - "conversationId": "1781604279872581690", 19 + "conversationId": "1781604279872581724",
18 "content": "1+1=?" 20 "content": "1+1=?"
19 } 21 }
20 22
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java
@@ -57,6 +57,4 @@ public class AiImageRespVO { @@ -57,6 +57,4 @@ public class AiImageRespVO {
57 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 57 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
58 private LocalDateTime createTime; 58 private LocalDateTime createTime;
59 59
60 -  
61 -  
62 } 60 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
@@ -77,6 +77,11 @@ public class AiImageDO extends BaseDO { @@ -77,6 +77,11 @@ public class AiImageDO extends BaseDO {
77 private Integer status; 77 private Integer status;
78 78
79 /** 79 /**
  80 + * 完成时间
  81 + */
  82 + private LocalDateTime finishTime;
  83 +
  84 + /**
80 * 绘画错误信息 85 * 绘画错误信息
81 */ 86 */
82 private String errorMessage; 87 private String errorMessage;
@@ -112,11 +117,6 @@ public class AiImageDO extends BaseDO { @@ -112,11 +117,6 @@ public class AiImageDO extends BaseDO {
112 */ 117 */
113 private String taskId; 118 private String taskId;
114 119
115 - /**  
116 - * 完成时间  
117 - */  
118 - private LocalDateTime finishTime;  
119 -  
120 public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> { 120 public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> {
121 121
122 @Override 122 @Override
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.ai.core.util.AiUtils; @@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 8 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
9 import cn.iocoder.yudao.framework.common.pojo.PageResult; 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  11 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
11 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; 12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; 13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
@@ -125,11 +126,14 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -125,11 +126,14 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
125 return success(new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) 126 return success(new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class))
126 .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent))); 127 .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)));
127 }).doOnComplete(() -> { 128 }).doOnComplete(() -> {
128 - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())); 129 + // 忽略租户,因为 Flux 异步无法透传租户
  130 + TenantUtils.executeIgnore(() ->
  131 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())));
129 }).doOnError(throwable -> { 132 }).doOnError(throwable -> {
130 - // TODO @芋艿:失败的情况下,要不要删除消息  
131 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); 133 log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable);
132 - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())); 134 + // 忽略租户,因为 Flux 异步无法透传租户
  135 + TenantUtils.executeIgnore(() ->
  136 + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())));
133 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); 137 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR)));
134 } 138 }
135 139
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
5 import cn.iocoder.yudao.framework.ai.core.util.AiUtils; 5 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
6 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
8 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; 9 import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO;
9 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 10 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
10 import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; 11 import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO;
@@ -75,10 +76,14 @@ public class AiWriteServiceImpl implements AiWriteService { @@ -75,10 +76,14 @@ public class AiWriteServiceImpl implements AiWriteService {
75 // 响应结果 76 // 响应结果
76 return success(newContent); 77 return success(newContent);
77 }).doOnComplete(() -> { 78 }).doOnComplete(() -> {
78 - writeMapper.updateById(new AiWriteDO().setId(writeDO.getId()).setGeneratedContent(contentBuffer.toString())); 79 + // 忽略租户,因为 Flux 异步无法透传租户
  80 + TenantUtils.executeIgnore(() ->
  81 + writeMapper.updateById(new AiWriteDO().setId(writeDO.getId()).setGeneratedContent(contentBuffer.toString())));
79 }).doOnError(throwable -> { 82 }).doOnError(throwable -> {
80 - log.error("[AI Write][generateReqVO({}) 发生异常]", generateReqVO, throwable);  
81 - writeMapper.updateById(new AiWriteDO().setId(writeDO.getId()).setErrorMessage(throwable.getMessage())); 83 + log.error("[generateWriteContent][generateReqVO({}) 发生异常]", generateReqVO, throwable);
  84 + // 忽略租户,因为 Flux 异步无法透传租户
  85 + TenantUtils.executeIgnore(() ->
  86 + writeMapper.updateById(new AiWriteDO().setId(writeDO.getId()).setErrorMessage(throwable.getMessage())));
82 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); 87 }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR)));
83 } 88 }
84 89
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java
@@ -163,7 +163,7 @@ public class AiClientFactoryImpl implements AiClientFactory { @@ -163,7 +163,7 @@ public class AiClientFactoryImpl implements AiClientFactory {
163 * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} 163 * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)}
164 */ 164 */
165 private static QianFanChatModel buildYiYanChatClient(String key) { 165 private static QianFanChatModel buildYiYanChatClient(String key) {
166 - // TODO 芋艿:貌似目前设置,request 势必会报错 166 + // TODO @xin:貌似目前设置,request 势必会报错;看看能不能有办法,参考 buildQianWenChatClient,调用 QianFanAutoConfiguration#qianFanChatModel初始化,当然 key 要用自己的哈
167 List<String> keys = StrUtil.split(key, '|'); 167 List<String> keys = StrUtil.split(key, '|');
168 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); 168 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
169 String appKey = keys.get(0); 169 String appKey = keys.get(0);
@@ -191,8 +191,8 @@ public class AiClientFactoryImpl implements AiClientFactory { @@ -191,8 +191,8 @@ public class AiClientFactoryImpl implements AiClientFactory {
191 private static TongYiChatModel buildQianWenChatClient(String key) { 191 private static TongYiChatModel buildQianWenChatClient(String key) {
192 com.alibaba.dashscope.aigc.generation.Generation generation = SpringUtil.getBean(Generation.class); 192 com.alibaba.dashscope.aigc.generation.Generation generation = SpringUtil.getBean(Generation.class);
193 TongYiChatProperties chatOptions = SpringUtil.getBean(TongYiChatProperties.class); 193 TongYiChatProperties chatOptions = SpringUtil.getBean(TongYiChatProperties.class);
194 - // TODO @芋艿:貌似 apiKey 是全局唯一的???得测试下  
195 - // TODO @芋艿:貌似阿里云不是增量返回的 194 + // TODO @xin:貌似 apiKey 是全局唯一的???得测试下
  195 + // TODO @xin:貌似阿里云不是增量返回的
196 TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties(); 196 TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties();
197 connectionProperties.setApiKey(key); 197 connectionProperties.setApiKey(key);
198 return new TongYiAutoConfiguration().tongYiChatClient(generation, chatOptions, connectionProperties); 198 return new TongYiAutoConfiguration().tongYiChatClient(generation, chatOptions, connectionProperties);
yudao-server/src/main/resources/application-local.yaml
@@ -70,12 +70,6 @@ spring: @@ -70,12 +70,6 @@ spring:
70 port: 6379 # 端口 70 port: 6379 # 端口
71 database: 0 # 数据库索引 71 database: 0 # 数据库索引
72 # password: dev # 密码,建议生产环境开启 72 # password: dev # 密码,建议生产环境开启
73 -server:  
74 - servlet:  
75 - encoding:  
76 - enabled: true  
77 - charset: UTF-8  
78 - force: true  
79 73
80 --- #################### 定时任务相关配置 #################### 74 --- #################### 定时任务相关配置 ####################
81 75
@@ -226,8 +220,6 @@ yudao: @@ -226,8 +220,6 @@ yudao:
226 enable: false 220 enable: false
227 demo: false # 关闭演示模式 221 demo: false # 关闭演示模式
228 tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc 222 tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
229 - tenant:  
230 - enable: false  
231 223
232 justauth: 224 justauth:
233 enabled: true 225 enabled: true
yudao-server/src/main/resources/application.yaml
@@ -34,6 +34,13 @@ spring: @@ -34,6 +34,13 @@ spring:
34 redis: 34 redis:
35 time-to-live: 1h # 设置过期时间为 1 小时 35 time-to-live: 1h # 设置过期时间为 1 小时
36 36
  37 +server:
  38 + servlet:
  39 + encoding:
  40 + enabled: true
  41 + charset: UTF-8 # 必须设置 UTF-8,避免 WebFlux 流式返回(AI 场景)会乱码问题
  42 + force: true
  43 +
37 --- #################### 接口文档配置 #################### 44 --- #################### 接口文档配置 ####################
38 45
39 springdoc: 46 springdoc: