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 af276e35a..d2d230842 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 11fe384da..7f3b119d5 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-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java b/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java
index 9eeaf8b44..ff586c39e 100644
--- a/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java
+++ b/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java
@@ -63,13 +63,12 @@ public class ApiSignatureTest {
when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test")));
// mock 方法
when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret);
+ when(signatureRedisDAO.setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS))).thenReturn(true);
// 调用
boolean result = apiSignatureAspect.verifySignature(apiSignature, request);
// 断言结果
assertTrue(result);
- // 断言调用
- verify(signatureRedisDAO).setNonce(eq(appId), eq(nonce), eq(120), eq(TimeUnit.SECONDS));
}
-}
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
index 682850b98..12a63f3d4 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
@@ -112,6 +112,18 @@
dev.tinyflow
tinyflow-java-core
${tinyflow.version}
+
+
+
+ com.agentsflex
+ agents-flex-store-elasticsearch
+
+
+
+ org.codehaus.groovy
+ groovy-all
+
+
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mcp/DouBaoMcpTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mcp/DouBaoMcpTests.java
new file mode 100644
index 000000000..a97bd0a5c
--- /dev/null
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mcp/DouBaoMcpTests.java
@@ -0,0 +1,122 @@
+package cn.iocoder.yudao.framework.ai.mcp;
+
+import cn.iocoder.yudao.framework.ai.core.model.doubao.DouBaoChatModel;
+import org.junit.jupiter.api.Test;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.openai.OpenAiChatModel;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.ai.openai.api.OpenAiApi;
+import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.method.MethodToolCallbackProvider;
+
+public class DouBaoMcpTests {
+
+ private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
+ .openAiApi(OpenAiApi.builder()
+ .baseUrl(DouBaoChatModel.BASE_URL)
+ .apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
+ .build())
+ .defaultOptions(OpenAiChatOptions.builder()
+ .model("doubao-1-5-lite-32k-250115") // 模型(doubao)
+ .temperature(0.7)
+ .build())
+ .build();
+
+ private final DouBaoChatModel chatModel = new DouBaoChatModel(openAiChatModel);
+
+ private final MethodToolCallbackProvider provider = MethodToolCallbackProvider.builder()
+ .toolObjects(new UserService())
+ .build();
+
+ private final ChatClient chatClient = ChatClient.builder(chatModel)
+ .defaultTools(provider)
+ .build();
+
+ @Test
+ public void testMcpGetUserInfo() {
+
+ // 打印结果
+ System.out.println(chatClient.prompt()
+ .user("目前有哪些工具可以使用")
+ .call()
+ .content());
+ System.out.println("====================================");
+ // 打印结果
+ System.out.println(chatClient.prompt()
+ .user("小新的年龄是多少")
+ .call()
+ .content());
+ System.out.println("====================================");
+ // 打印结果
+ System.out.println(chatClient.prompt()
+ .user("获取小新的基本信息")
+ .call()
+ .content());
+ System.out.println("====================================");
+ // 打印结果
+ System.out.println(chatClient.prompt()
+ .user("小新是什么职业的")
+ .call()
+ .content());
+ System.out.println("====================================");
+ // 打印结果
+ System.out.println(chatClient.prompt()
+ .user("小新的教育背景")
+ .call()
+ .content());
+ System.out.println("====================================");
+ // 打印结果
+ System.out.println(chatClient.prompt()
+ .user("小新的兴趣爱好是什么")
+ .call()
+ .content());
+ System.out.println("====================================");
+
+ }
+
+
+ static class UserService {
+
+ @Tool(name = "getUserAge", description = "获取用户年龄")
+ public String getUserAge(String userName) {
+ return "《" + userName + "》的年龄为:18";
+ }
+
+ @Tool(name = "getUserSex", description = "获取用户性别")
+ public String getUserSex(String userName) {
+ return "《" + userName + "》的性别为:男";
+ }
+
+ @Tool(name = "getUserBasicInfo", description = "获取用户基本信息,包括姓名、年龄、性别等")
+ public String getUserBasicInfo(String userName) {
+ return "《" + userName + "》的基本信息:\n姓名:" + userName + "\n年龄:18\n性别:男\n身高:175cm\n体重:65kg";
+ }
+
+ @Tool(name = "getUserContact", description = "获取用户联系方式,包括电话、邮箱等")
+ public String getUserContact(String userName) {
+ return "《" + userName + "》的联系方式:\n电话:138****1234\n邮箱:" + userName.toLowerCase() + "@example.com\nQQ:123456789";
+ }
+
+ @Tool(name = "getUserAddress", description = "获取用户地址信息")
+ public String getUserAddress(String userName) {
+ return "《" + userName + "》的地址信息:北京市朝阳区科技园区88号";
+ }
+
+ @Tool(name = "getUserJob", description = "获取用户职业信息")
+ public String getUserJob(String userName) {
+ return "《" + userName + "》的职业信息:软件工程师,就职于ABC科技有限公司,工作年限5年";
+ }
+
+ @Tool(name = "getUserHobbies", description = "获取用户兴趣爱好")
+ public String getUserHobbies(String userName) {
+ return "《" + userName + "》的兴趣爱好:编程、阅读、旅游、摄影、打篮球";
+ }
+
+ @Tool(name = "getUserEducation", description = "获取用户教育背景")
+ public String getUserEducation(String userName) {
+ return "《" + userName + "》的教育背景:\n本科:计算机科学与技术专业,北京大学\n硕士:软件工程专业,清华大学";
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WenDuoDuoPptApiTests.java
similarity index 91%
rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java
rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WenDuoDuoPptApiTests.java
index 3bb9898ad..54c8cffc5 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WenDuoDuoPptApiTests.java
@@ -1,6 +1,6 @@
package cn.iocoder.yudao.framework.ai.ppt.wdd;
-import cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api.WddPptApi;
+import cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api.WenDuoDuoPptApi;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -10,24 +10,23 @@ import java.util.Map;
import java.util.Objects;
/**
- * {@link WddPptApi} 集成测试
+ * {@link WenDuoDuoPptApi} 集成测试
*
* @author xiaoxin
*/
-public class WddPptApiTests {
-
- private final WddPptApi wddPptApi = new WddPptApi("https://docmee.cn");
+public class WenDuoDuoPptApiTests {
private final String token = ""; // API Token
+ private final WenDuoDuoPptApi wenDuoDuoPptApi = new WenDuoDuoPptApi(token);
@Test
@Disabled
public void testCreateApiToken() {
// 准备参数
String apiKey = "";
- WddPptApi.CreateTokenRequest request = new WddPptApi.CreateTokenRequest(apiKey);
+ WenDuoDuoPptApi.CreateTokenRequest request = new WenDuoDuoPptApi.CreateTokenRequest(apiKey);
// 调用方法
- String token = wddPptApi.createApiToken(request);
+ String token = wenDuoDuoPptApi.createApiToken(request);
// 打印结果
System.out.println(token);
}
@@ -38,7 +37,7 @@ public class WddPptApiTests {
@Test
@Disabled
public void testCreateTask() {
- WddPptApi.ApiResponse apiResponse = wddPptApi.createTask(token, 1, "dify 介绍", null);
+ WenDuoDuoPptApi.ApiResponse apiResponse = wenDuoDuoPptApi.createTask(1, "dify 介绍", null);
System.out.println(apiResponse);
}
@@ -46,10 +45,10 @@ public class WddPptApiTests {
@Test // 创建大纲
@Disabled
public void testGenerateOutlineRequest() {
- WddPptApi.CreateOutlineRequest request = new WddPptApi.CreateOutlineRequest(
+ WenDuoDuoPptApi.CreateOutlineRequest request = new WenDuoDuoPptApi.CreateOutlineRequest(
"1901539019628613632", "medium", null, null, null, null);
// 调用
- Flux