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,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: |