getResult(String value) {
+ if (value == null) {
+ return null;
+ }
+ return StrUtils.splitToLongSet(value, COMMA);
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/redis/RateLimiterRedisDAO.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/redis/RateLimiterRedisDAO.java
index fc1378f3bd..18c30682e5 100644
--- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/redis/RateLimiterRedisDAO.java
+++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/redis/RateLimiterRedisDAO.java
@@ -44,6 +44,7 @@ public class RateLimiterRedisDAO {
RateLimiterConfig config = rateLimiter.getConfig();
if (config == null) {
rateLimiter.trySetRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS);
+ rateLimiter.expire(rateInterval, TimeUnit.SECONDS); // 原因参见 https://t.zsxq.com/lcR0W
return rateLimiter;
}
// 2. 如果存在,并且配置相同,则直接返回
@@ -54,6 +55,7 @@ public class RateLimiterRedisDAO {
}
// 3. 如果存在,并且配置不同,则进行新建
rateLimiter.setRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS);
+ rateLimiter.expire(rateInterval, TimeUnit.SECONDS); // 原因参见 https://t.zsxq.com/lcR0W
return rateLimiter;
}
diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java
index af276e35a9..d2d2308426 100644
--- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java
+++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java
@@ -2,10 +2,12 @@ package cn.iocoder.yudao.framework.signature.core.aop;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature;
import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO;
@@ -69,13 +71,17 @@ public class ApiSignatureAspect {
// 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
String nonce = request.getHeader(signature.nonce());
- signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit());
+ if (BooleanUtil.isFalse(signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit()))) {
+ String timestamp = request.getHeader(signature.timestamp());
+ log.info("[verifySignature][appId({}) timestamp({}) nonce({}) sign({}) 存在重复请求]", appId, timestamp, nonce, clientSignature);
+ throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), "存在重复请求");
+ }
return true;
}
/**
* 校验请求头加签参数
- *
+ *
* 1. appId 是否为空
* 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟
* 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
@@ -118,7 +124,7 @@ public class ApiSignatureAspect {
/**
* 构建签名字符串
- *
+ *
* 格式为 = 请求参数 + 请求体 + 请求头 + 密钥
*
* @param signature signature
@@ -139,7 +145,7 @@ public class ApiSignatureAspect {
/**
* 获取请求头加签参数 Map
*
- * @param request 请求
+ * @param request 请求
* @param signature 签名注解
* @return signature params
*/
diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java
index 11fe384dac..7f3b119d53 100644
--- a/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java
+++ b/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/redis/ApiSignatureRedisDAO.java
@@ -17,7 +17,7 @@ public class ApiSignatureRedisDAO {
/**
* 验签随机数
- *
+ *
* KEY 格式:signature_nonce:%s // 参数为 随机数
* VALUE 格式:String
* 过期时间:不固定
@@ -26,7 +26,7 @@ public class ApiSignatureRedisDAO {
/**
* 签名密钥
- *
+ *
* HASH 结构
* KEY 格式:%s // 参数为 appid
* VALUE 格式:String
@@ -40,8 +40,8 @@ public class ApiSignatureRedisDAO {
return stringRedisTemplate.opsForValue().get(formatNonceKey(appId, nonce));
}
- public void setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
- stringRedisTemplate.opsForValue().set(formatNonceKey(appId, nonce), "", time, timeUnit);
+ public Boolean setNonce(String appId, String nonce, int time, TimeUnit timeUnit) {
+ return stringRedisTemplate.opsForValue().setIfAbsent(formatNonceKey(appId, nonce), "", time, timeUnit);
}
private static String formatNonceKey(String appId, String nonce) {
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
index 52f707075b..bcf78f163b 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/config/YudaoWebSecurityConfigurerAdapter.java
@@ -30,6 +30,7 @@ import org.springframework.web.util.pattern.PathPattern;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
+import javax.servlet.DispatcherType;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -142,7 +143,9 @@ public class YudaoWebSecurityConfigurerAdapter {
// ②:每个项目的自定义规则
.authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))
// ③:兜底规则,必须认证
- .authorizeHttpRequests(c -> c.anyRequest().authenticated());
+ .authorizeHttpRequests(c -> c
+ .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // WebFlux 异步请求,无需认证,目的:SSE 场景
+ .anyRequest().authenticated());
// 添加 Token Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
index c8b0dbd66e..d07c4aaedf 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java
@@ -62,9 +62,9 @@ public class BannerApplicationRunner implements ApplicationRunner {
if (isNotPresent("cn.iocoder.yudao.module.ai.framework.web.config.AiWebConfiguration")) {
System.out.println("[AI 大模型 yudao-module-ai - 已禁用][参考 https://doc.iocoder.cn/ai/build/ 开启]");
}
- // IOT 物联网
+ // IoT 物联网
if (isNotPresent("cn.iocoder.yudao.module.iot.framework.web.config.IotWebConfiguration")) {
- System.out.println("[IOT 物联网 yudao-module-iot - 已禁用][参考 https://doc.iocoder.cn/iot/build/ 开启]");
+ System.out.println("[IoT 物联网 yudao-module-iot - 已禁用][参考 https://doc.iocoder.cn/iot/build/ 开启]");
}
});
}
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
index 2956cd602e..6090d1deff 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.web.core.handler;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
@@ -22,6 +23,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
+import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
@@ -35,6 +37,7 @@ import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.time.LocalDateTime;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -133,9 +136,23 @@ public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public CommonResult> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
+ // 获取 errorMessage
+ String errorMessage = null;
FieldError fieldError = ex.getBindingResult().getFieldError();
- assert fieldError != null; // 断言,避免告警
- return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
+ if (fieldError == null) {
+ // 组合校验,参考自 https://t.zsxq.com/3HVTx
+ List allErrors = ex.getBindingResult().getAllErrors();
+ if (CollUtil.isNotEmpty(allErrors)) {
+ errorMessage = allErrors.get(0).getDefaultMessage();
+ }
+ } else {
+ errorMessage = fieldError.getDefaultMessage();
+ }
+ // 转换 CommonResult
+ if (StrUtil.isEmpty(errorMessage)) {
+ return CommonResult.error(BAD_REQUEST);
+ }
+ return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", errorMessage));
}
/**
@@ -376,11 +393,11 @@ public class GlobalExceptionHandler {
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
}
- // 9. IOT 物联网
+ // 9. IoT 物联网
if (message.contains("iot_")) {
- log.error("[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
+ log.error("[IoT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
- "[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
+ "[IoT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
}
return null;
}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.http b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.http
new file mode 100644
index 0000000000..a0f127865a
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeController.http
@@ -0,0 +1,35 @@
+### 创建知识库
+POST {{baseUrl}}/ai/knowledge/create
+Content-Type: application/json
+Authorization: {{token}}
+tenant-id: {{adminTenantId}}
+
+{
+ "name": "测试标题",
+ "description": "测试描述",
+ "embeddingModelId": 30,
+ "topK": 3,
+ "similarityThreshold": 0.5,
+ "status": 0
+}
+
+### 更新知识库
+PUT {{baseUrl}}/ai/knowledge/update
+Content-Type: application/json
+Authorization: {{token}}
+tenant-id: {{adminTenantId}}
+
+{
+ "id": 1,
+ "name": "测试标题(更新)",
+ "description": "测试描述",
+ "embeddingModelId": 30,
+ "topK": 5,
+ "similarityThreshold": 0.6,
+ "status": 0
+}
+
+### 获取知识库分页
+GET {{baseUrl}}/ai/knowledge/page?pageNo=1&pageSize=10
+Authorization: {{token}}
+tenant-id: {{adminTenantId}}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.http b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.http
new file mode 100644
index 0000000000..1c858ed3eb
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeDocumentController.http
@@ -0,0 +1,35 @@
+### 创建知识文档
+POST {{baseUrl}}/ai/knowledge/document/create
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}
+
+{
+ "knowledgeId": 2,
+ "name": "测试文档",
+ "url": "https://static.iocoder.cn/README.md",
+ "segmentMaxTokens": 800
+}
+
+### 批量创建知识文档
+POST {{baseUrl}}/ai/knowledge/document/create-list
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}
+
+{
+ "knowledgeId": 1,
+ "list": [
+ {
+ "name": "测试文档1",
+ "url": "https://static.iocoder.cn/README.md",
+ "segmentMaxTokens": 800
+ },
+ {
+ "name": "测试文档2",
+ "url": "https://static.iocoder.cn/README_yudao.md",
+ "segmentMaxTokens": 400
+ }
+ ]
+}
+
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.http b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.http
new file mode 100644
index 0000000000..09018da3dc
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/AiKnowledgeSegmentController.http
@@ -0,0 +1,17 @@
+### 切片内容
+GET {{baseUrl}}/ai/knowledge/segment/split?url=https://static.iocoder.cn/README_yudao.md&segmentMaxTokens=800
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}
+
+### 搜索段落内容
+GET {{baseUrl}}/ai/knowledge/segment/search?knowledgeId=2&content=如何使用这个产品&topK=5&similarityThreshold=0.1
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}
+
+### 获取文档处理列表
+GET {{baseUrl}}/ai/knowledge/segment/get-process-list?documentIds=1,2,3
+Content-Type: application/json
+Authorization: Bearer {{token}}
+tenant-id: {{adminTenantId}}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java
new file mode 100644
index 0000000000..6545c0bc1c
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentCreateListReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+
+import java.util.List;
+
+@Schema(description = "管理后台 - AI 知识库文档批量创建 Request VO")
+@Data
+public class AiKnowledgeDocumentCreateListReqVO {
+
+ @Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1204")
+ @NotNull(message = "知识库编号不能为空")
+ private Long knowledgeId;
+
+ @Schema(description = "分段的最大 Token 数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
+ @NotNull(message = "分段的最大 Token 数不能为空")
+ private Integer segmentMaxTokens;
+
+ @Schema(description = "文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "文档列表不能为空")
+ private List list;
+
+ @Schema(description = "文档")
+ @Data
+ public static class Document {
+
+ @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "三方登陆")
+ @NotBlank(message = "文档名称不能为空")
+ private String name;
+
+ @Schema(description = "文档 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn")
+ @URL(message = "文档 URL 格式不正确")
+ private String url;
+
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateStatusReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateStatusReqVO.java
new file mode 100644
index 0000000000..93d393ab4a
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/document/AiKnowledgeDocumentUpdateStatusReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 知识库文档更新状态 Request VO")
+@Data
+public class AiKnowledgeDocumentUpdateStatusReqVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ @NotNull(message = "状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentProcessRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentProcessRespVO.java
new file mode 100644
index 0000000000..a6b95265b7
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentProcessRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 知识库段落向量进度 Response VO")
+@Data
+public class AiKnowledgeSegmentProcessRespVO {
+
+ @Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long documentId;
+
+ @Schema(description = "总段落数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Long count;
+
+ @Schema(description = "已向量化段落数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+ private Long embeddingCount;
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSaveReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSaveReqVO.java
new file mode 100644
index 0000000000..0c5dad11db
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSaveReqVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 新增/修改知识库段落 request VO")
+@Data
+public class AiKnowledgeSegmentSaveReqVO {
+
+ @Schema(description = "编号", example = "24790")
+ private Long id;
+
+ @Schema(description = "知识库文档编号", example = "1024")
+ private Long documentId;
+
+ @Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java 开发手册")
+ @NotEmpty(message = "切片内容不能为空")
+ private String content;
+
+}
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchRespVO.java
new file mode 100644
index 0000000000..50bbc5c867
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/knowledge/vo/segment/AiKnowledgeSegmentSearchRespVO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 知识库段落搜索 Response VO")
+@Data
+public class AiKnowledgeSegmentSearchRespVO extends AiKnowledgeSegmentRespVO {
+
+ @Schema(description = "文档名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品使用手册")
+ private String documentName;
+
+ @Schema(description = "相似度分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.95")
+ private Double score;
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java
new file mode 100644
index 0000000000..86dd4d0a61
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiModelController.java
@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.ai.controller.admin.model;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
+import cn.iocoder.yudao.module.ai.service.model.AiModelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - AI 模型")
+@RestController
+@RequestMapping("/ai/model")
+@Validated
+public class AiModelController {
+
+ @Resource
+ private AiModelService modelService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建模型")
+ @PreAuthorize("@ss.hasPermission('ai:model:create')")
+ public CommonResult createModel(@Valid @RequestBody AiModelSaveReqVO createReqVO) {
+ return success(modelService.createModel(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新模型")
+ @PreAuthorize("@ss.hasPermission('ai:model:update')")
+ public CommonResult updateModel(@Valid @RequestBody AiModelSaveReqVO updateReqVO) {
+ modelService.updateModel(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除模型")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('ai:model:delete')")
+ public CommonResult deleteModel(@RequestParam("id") Long id) {
+ modelService.deleteModel(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得模型")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('ai:model:query')")
+ public CommonResult getModel(@RequestParam("id") Long id) {
+ AiModelDO model = modelService.getModel(id);
+ return success(BeanUtils.toBean(model, AiModelRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得模型分页")
+ @PreAuthorize("@ss.hasPermission('ai:model:query')")
+ public CommonResult> getModelPage(@Valid AiModelPageReqVO pageReqVO) {
+ PageResult pageResult = modelService.getModelPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, AiModelRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得模型列表")
+ @Parameter(name = "type", description = "类型", required = true, example = "1")
+ @Parameter(name = "platform", description = "平台", example = "midjourney")
+ public CommonResult> getModelSimpleList(
+ @RequestParam("type") Integer type,
+ @RequestParam(value = "platform", required = false) String platform) {
+ List list = modelService.getModelListByStatusAndType(
+ CommonStatusEnum.ENABLE.getStatus(), type, platform);
+ return success(convertList(list, model -> new AiModelRespVO().setId(model.getId())
+ .setName(model.getName()).setModel(model.getModel()).setPlatform(model.getPlatform())));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiToolController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiToolController.java
new file mode 100644
index 0000000000..e98f87e0b5
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiToolController.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.ai.controller.admin.model;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolRespVO;
+import cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool.AiToolSaveReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO;
+import cn.iocoder.yudao.module.ai.service.model.AiToolService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+@Tag(name = "管理后台 - AI 工具")
+@RestController
+@RequestMapping("/ai/tool")
+@Validated
+public class AiToolController {
+
+ @Resource
+ private AiToolService toolService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建工具")
+ @PreAuthorize("@ss.hasPermission('ai:tool:create')")
+ public CommonResult createTool(@Valid @RequestBody AiToolSaveReqVO createReqVO) {
+ return success(toolService.createTool(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新工具")
+ @PreAuthorize("@ss.hasPermission('ai:tool:update')")
+ public CommonResult updateTool(@Valid @RequestBody AiToolSaveReqVO updateReqVO) {
+ toolService.updateTool(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除工具")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('ai:tool:delete')")
+ public CommonResult deleteTool(@RequestParam("id") Long id) {
+ toolService.deleteTool(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得工具")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('ai:tool:query')")
+ public CommonResult getTool(@RequestParam("id") Long id) {
+ AiToolDO tool = toolService.getTool(id);
+ return success(BeanUtils.toBean(tool, AiToolRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得工具分页")
+ @PreAuthorize("@ss.hasPermission('ai:tool:query')")
+ public CommonResult> getToolPage(@Valid AiToolPageReqVO pageReqVO) {
+ PageResult pageResult = toolService.getToolPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, AiToolRespVO.class));
+ }
+
+ @GetMapping("/simple-list")
+ @Operation(summary = "获得工具列表")
+ public CommonResult> getToolSimpleList() {
+ List list = toolService.getToolListByStatus(CommonStatusEnum.ENABLE.getStatus());
+ return success(convertList(list, tool -> new AiToolRespVO()
+ .setId(tool.getId()).setName(tool.getName())));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolPageReqVO.java
new file mode 100644
index 0000000000..dc8b04c507
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolPageReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - AI 工具分页 Request VO")
+@Data
+public class AiToolPageReqVO extends PageParam {
+
+ @Schema(description = "工具名称", example = "王五")
+ private String name;
+
+ @Schema(description = "工具描述", example = "你猜")
+ private String description;
+
+ @Schema(description = "状态", example = "1")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolRespVO.java
new file mode 100644
index 0000000000..6d5a02e687
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - AI 工具 Response VO")
+@Data
+public class AiToolRespVO {
+
+ @Schema(description = "工具编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19661")
+ private Long id;
+
+ @Schema(description = "工具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+ private String name;
+
+ @Schema(description = "工具描述", example = "你猜")
+ private String description;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer status;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolSaveReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolSaveReqVO.java
new file mode 100644
index 0000000000..c85cfc33e7
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/tool/AiToolSaveReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.ai.controller.admin.model.vo.tool;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+@Schema(description = "管理后台 - AI 工具新增/修改 Request VO")
+@Data
+public class AiToolSaveReqVO {
+
+ @Schema(description = "工具编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19661")
+ private Long id;
+
+ @Schema(description = "工具名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
+ @NotEmpty(message = "工具名称不能为空")
+ private String name;
+
+ @Schema(description = "工具描述", example = "你猜")
+ private String description;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java
new file mode 100644
index 0000000000..d558d90454
--- /dev/null
+++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.java
@@ -0,0 +1,77 @@
+package cn.iocoder.yudao.module.ai.controller.admin.workflow;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ai.controller.admin.workflow.vo.*;
+import cn.iocoder.yudao.module.ai.dal.dataobject.workflow.AiWorkflowDO;
+import cn.iocoder.yudao.module.ai.service.workflow.AiWorkflowService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - AI 工作流")
+@RestController
+@RequestMapping("/ai/workflow")
+@Slf4j
+public class AiWorkflowController {
+
+ @Resource
+ private AiWorkflowService workflowService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建 AI 工作流")
+ @PreAuthorize("@ss.hasPermission('ai:workflow:create')")
+ public CommonResult createWorkflow(@Valid @RequestBody AiWorkflowSaveReqVO createReqVO) {
+ return success(workflowService.createWorkflow(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新 AI 工作流")
+ @PreAuthorize("@ss.hasPermission('ai:workflow:update')")
+ public CommonResult updateWorkflow(@Valid @RequestBody AiWorkflowSaveReqVO updateReqVO) {
+ workflowService.updateWorkflow(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除 AI 工作流")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('ai:workflow:delete')")
+ public CommonResult deleteWorkflow(@RequestParam("id") Long id) {
+ workflowService.deleteWorkflow(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得 AI 工作流")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('ai:workflow:query')")
+ public CommonResult getWorkflow(@RequestParam("id") Long id) {
+ AiWorkflowDO workflow = workflowService.getWorkflow(id);
+ return success(BeanUtils.toBean(workflow, AiWorkflowRespVO.class));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得 AI 工作流分页")
+ @PreAuthorize("@ss.hasPermission('ai:workflow:query')")
+ public CommonResult> getWorkflowPage(@Valid AiWorkflowPageReqVO pageReqVO) {
+ PageResult pageResult = workflowService.getWorkflowPage(pageReqVO);
+ return success(BeanUtils.toBean(pageResult, AiWorkflowRespVO.class));
+ }
+
+ @PostMapping("/test")
+ @Operation(summary = "测试 AI 工作流")
+ @PreAuthorize("@ss.hasPermission('ai:workflow:test')")
+ public CommonResult