Commit 2e73959a2c13ec22b983c0e47832fcc6da767233
1 parent
c98495df
【代码新增】AI:开启 AI 租户的能力
Showing
9 changed files
with
38 additions
and
25 deletions
yudao-module-ai/yudao-module-ai-biz/pom.xml
| ... | ... | @@ -32,6 +32,11 @@ |
| 32 | 32 | <version>${revision}</version> |
| 33 | 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 | 40 | <!-- Web 相关 --> |
| 36 | 41 | <dependency> |
| 37 | 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 | 2 | POST {{baseUrl}}/ai/chat/message/send |
| 3 | 3 | Content-Type: application/json |
| 4 | 4 | Authorization: {{token}} |
| 5 | +tenant-id: {{adminTenentId}} | |
| 5 | 6 | |
| 6 | 7 | { |
| 7 | 8 | "conversationId": "1781604279872581724", |
| ... | ... | @@ -12,9 +13,10 @@ Authorization: {{token}} |
| 12 | 13 | POST {{baseUrl}}/ai/chat/message/send-stream |
| 13 | 14 | Content-Type: application/json |
| 14 | 15 | Authorization: {{token}} |
| 16 | +tenant-id: {{adminTenentId}} | |
| 15 | 17 | |
| 16 | 18 | { |
| 17 | - "conversationId": "1781604279872581690", | |
| 19 | + "conversationId": "1781604279872581724", | |
| 18 | 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
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 | 77 | private Integer status; |
| 78 | 78 | |
| 79 | 79 | /** |
| 80 | + * 完成时间 | |
| 81 | + */ | |
| 82 | + private LocalDateTime finishTime; | |
| 83 | + | |
| 84 | + /** | |
| 80 | 85 | * 绘画错误信息 |
| 81 | 86 | */ |
| 82 | 87 | private String errorMessage; |
| ... | ... | @@ -112,11 +117,6 @@ public class AiImageDO extends BaseDO { |
| 112 | 117 | */ |
| 113 | 118 | private String taskId; |
| 114 | 119 | |
| 115 | - /** | |
| 116 | - * 完成时间 | |
| 117 | - */ | |
| 118 | - private LocalDateTime finishTime; | |
| 119 | - | |
| 120 | 120 | public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> { |
| 121 | 121 | |
| 122 | 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 | 8 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |
| 11 | 12 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; |
| 12 | 13 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; |
| 13 | 14 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; |
| ... | ... | @@ -125,11 +126,14 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 125 | 126 | return success(new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |
| 126 | 127 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent))); |
| 127 | 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 | 132 | }).doOnError(throwable -> { |
| 130 | - // TODO @芋艿:失败的情况下,要不要删除消息 | |
| 131 | 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 | 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 | 5 | import cn.iocoder.yudao.framework.ai.core.util.AiUtils; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 8 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |
| 8 | 9 | import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; |
| 9 | 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 10 | 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; |
| ... | ... | @@ -75,10 +76,14 @@ public class AiWriteServiceImpl implements AiWriteService { |
| 75 | 76 | // 响应结果 |
| 76 | 77 | return success(newContent); |
| 77 | 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 | 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 | 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 | 163 | * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} |
| 164 | 164 | */ |
| 165 | 165 | private static QianFanChatModel buildYiYanChatClient(String key) { |
| 166 | - // TODO 芋艿:貌似目前设置,request 势必会报错 | |
| 166 | + // TODO @xin:貌似目前设置,request 势必会报错;看看能不能有办法,参考 buildQianWenChatClient,调用 QianFanAutoConfiguration#qianFanChatModel初始化,当然 key 要用自己的哈 | |
| 167 | 167 | List<String> keys = StrUtil.split(key, '|'); |
| 168 | 168 | Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); |
| 169 | 169 | String appKey = keys.get(0); |
| ... | ... | @@ -191,8 +191,8 @@ public class AiClientFactoryImpl implements AiClientFactory { |
| 191 | 191 | private static TongYiChatModel buildQianWenChatClient(String key) { |
| 192 | 192 | com.alibaba.dashscope.aigc.generation.Generation generation = SpringUtil.getBean(Generation.class); |
| 193 | 193 | TongYiChatProperties chatOptions = SpringUtil.getBean(TongYiChatProperties.class); |
| 194 | - // TODO @芋艿:貌似 apiKey 是全局唯一的???得测试下 | |
| 195 | - // TODO @芋艿:貌似阿里云不是增量返回的 | |
| 194 | + // TODO @xin:貌似 apiKey 是全局唯一的???得测试下 | |
| 195 | + // TODO @xin:貌似阿里云不是增量返回的 | |
| 196 | 196 | TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties(); |
| 197 | 197 | connectionProperties.setApiKey(key); |
| 198 | 198 | return new TongYiAutoConfiguration().tongYiChatClient(generation, chatOptions, connectionProperties); | ... | ... |
yudao-server/src/main/resources/application-local.yaml
| ... | ... | @@ -70,12 +70,6 @@ spring: |
| 70 | 70 | port: 6379 # 端口 |
| 71 | 71 | database: 0 # 数据库索引 |
| 72 | 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 | 220 | enable: false |
| 227 | 221 | demo: false # 关闭演示模式 |
| 228 | 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 | 224 | justauth: |
| 233 | 225 | enabled: true | ... | ... |
yudao-server/src/main/resources/application.yaml
| ... | ... | @@ -34,6 +34,13 @@ spring: |
| 34 | 34 | redis: |
| 35 | 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 | 46 | springdoc: | ... | ... |