mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-18 17:38:48 +08:00
1、修改使用Spring sse方式
2、增加security排除路径
This commit is contained in:
parent
5b26ae9b1c
commit
7b81f13866
@ -109,6 +109,7 @@ security:
|
|||||||
- /*/api-docs
|
- /*/api-docs
|
||||||
- /*/api-docs/**
|
- /*/api-docs/**
|
||||||
- /warm-flow-ui/config
|
- /warm-flow-ui/config
|
||||||
|
- /snail-ai/agent/*/chat/stream
|
||||||
|
|
||||||
# MyBatisPlus配置
|
# MyBatisPlus配置
|
||||||
# https://baomidou.com/config/
|
# https://baomidou.com/config/
|
||||||
|
|||||||
@ -9,27 +9,19 @@ import com.aizuda.snail.ai.openapi.client.core.api.OpenApiChatClient;
|
|||||||
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiConversationClient;
|
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiConversationClient;
|
||||||
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiUserClient;
|
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiUserClient;
|
||||||
import com.aizuda.snail.ai.openapi.client.core.listener.SseEventListener;
|
import com.aizuda.snail.ai.openapi.client.core.listener.SseEventListener;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenAPI 使用示例 Controller
|
* OpenAPI 使用示例 Controller
|
||||||
* <p>
|
|
||||||
* 演示如何使用 OpenAPI Client 调用 Snail AI 服务端接口
|
* 演示如何使用 OpenAPI Client 调用 Snail AI 服务端接口
|
||||||
*
|
*
|
||||||
* @author opensnail
|
* @author opensnail
|
||||||
@ -39,7 +31,6 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/snail-ai")
|
@RequestMapping("/snail-ai")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Tag(name = "OpenAPI Demo", description = "OpenAPI 客户端使用示例")
|
|
||||||
public class OpenApiDemoController {
|
public class OpenApiDemoController {
|
||||||
|
|
||||||
private final OpenApiAgentClient agentClient;
|
private final OpenApiAgentClient agentClient;
|
||||||
@ -51,15 +42,19 @@ public class OpenApiDemoController {
|
|||||||
|
|
||||||
// ==================== User 相关接口 ====================
|
// ==================== User 相关接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册当前登录用户并返回 OpenAPI 用户信息。
|
||||||
|
*/
|
||||||
@PostMapping("/user/register")
|
@PostMapping("/user/register")
|
||||||
@Operation(summary = "注册当前登录用户", description = "使用当前系统登录用户自动注册,返回 openId")
|
|
||||||
public Result<OpenApiUserVO> registerCurrentUser() {
|
public Result<OpenApiUserVO> registerCurrentUser() {
|
||||||
OpenApiUserVO user = ensureOpenApiUser();
|
OpenApiUserVO user = ensureOpenApiUser();
|
||||||
return Result.ok(user);
|
return Result.ok(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前登录用户对应的 OpenAPI 用户信息。
|
||||||
|
*/
|
||||||
@GetMapping("/user")
|
@GetMapping("/user")
|
||||||
@Operation(summary = "获取当前登录用户的 OpenAPI 信息", description = "自动解析当前用户 openId 并查询详情")
|
|
||||||
public Result<OpenApiUserVO> getUser() {
|
public Result<OpenApiUserVO> getUser() {
|
||||||
String openId = ensureOpenId();
|
String openId = ensureOpenId();
|
||||||
OpenApiUserQueryRequest request = new OpenApiUserQueryRequest();
|
OpenApiUserQueryRequest request = new OpenApiUserQueryRequest();
|
||||||
@ -69,16 +64,19 @@ public class OpenApiDemoController {
|
|||||||
|
|
||||||
// ==================== Agent 相关接口 ====================
|
// ==================== Agent 相关接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前用户可访问的智能体列表。
|
||||||
|
*/
|
||||||
@GetMapping("/agents")
|
@GetMapping("/agents")
|
||||||
@Operation(summary = "获取所有 Agent 列表", description = "查询当前用户可访问的所有智能体")
|
|
||||||
public Result<List<OpenApiAgentVO>> listAgents() {
|
public Result<List<OpenApiAgentVO>> listAgents() {
|
||||||
return agentClient.listAgents();
|
return agentClient.listAgents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据智能体 ID 查询智能体详情。
|
||||||
|
*/
|
||||||
@GetMapping("/agent/{agentId}")
|
@GetMapping("/agent/{agentId}")
|
||||||
@Operation(summary = "获取 Agent 详情", description = "根据 ID 查询智能体详细信息")
|
|
||||||
public Result<OpenApiAgentVO> getAgent(
|
public Result<OpenApiAgentVO> getAgent(
|
||||||
@Parameter(description = "Agent ID", required = true, example = "1")
|
|
||||||
@PathVariable Long agentId) {
|
@PathVariable Long agentId) {
|
||||||
OpenApiAgentIdentityRequest request = new OpenApiAgentIdentityRequest();
|
OpenApiAgentIdentityRequest request = new OpenApiAgentIdentityRequest();
|
||||||
request.setAgentId(agentId);
|
request.setAgentId(agentId);
|
||||||
@ -87,26 +85,25 @@ public class OpenApiDemoController {
|
|||||||
|
|
||||||
// ==================== Conversation 相关接口 ====================
|
// ==================== Conversation 相关接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为指定智能体创建新会话。
|
||||||
|
*/
|
||||||
@PostMapping("/agent/{agentId}/conversation")
|
@PostMapping("/agent/{agentId}/conversation")
|
||||||
@Operation(summary = "创建会话", description = "为指定 Agent 创建一个新的对话会话")
|
|
||||||
public Result<OpenApiConversationVO> createConversation(
|
public Result<OpenApiConversationVO> createConversation(
|
||||||
@Parameter(description = "Agent ID", required = true, example = "1")
|
|
||||||
@PathVariable Long agentId,
|
@PathVariable Long agentId,
|
||||||
@Parameter(description = "创建会话请求")
|
|
||||||
@RequestBody OpenApiCreateConversationRequest request) {
|
@RequestBody OpenApiCreateConversationRequest request) {
|
||||||
request.setAgentId(agentId);
|
request.setAgentId(agentId);
|
||||||
request.setOpenId(ensureOpenId());
|
request.setOpenId(ensureOpenId());
|
||||||
return conversationClient.createConversation(request);
|
return conversationClient.createConversation(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询指定智能体下的会话列表。
|
||||||
|
*/
|
||||||
@GetMapping("/agent/{agentId}/conversations")
|
@GetMapping("/agent/{agentId}/conversations")
|
||||||
@Operation(summary = "获取会话列表", description = "查询指定 Agent 的所有会话(分页)")
|
|
||||||
public PageResult<List<OpenApiConversationVO>> listConversations(
|
public PageResult<List<OpenApiConversationVO>> listConversations(
|
||||||
@Parameter(description = "Agent ID", required = true, example = "1")
|
|
||||||
@PathVariable Long agentId,
|
@PathVariable Long agentId,
|
||||||
@Parameter(description = "页码", example = "1")
|
|
||||||
@RequestParam(defaultValue = "1") int page,
|
@RequestParam(defaultValue = "1") int page,
|
||||||
@Parameter(description = "每页数量", example = "10")
|
|
||||||
@RequestParam(defaultValue = "10") int size) {
|
@RequestParam(defaultValue = "10") int size) {
|
||||||
OpenApiConversationQueryRequest request = new OpenApiConversationQueryRequest();
|
OpenApiConversationQueryRequest request = new OpenApiConversationQueryRequest();
|
||||||
request.setAgentId(agentId);
|
request.setAgentId(agentId);
|
||||||
@ -116,12 +113,12 @@ public class OpenApiDemoController {
|
|||||||
return conversationClient.listConversations(request);
|
return conversationClient.listConversations(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定会话的消息历史。
|
||||||
|
*/
|
||||||
@GetMapping("/agent/{agentId}/conversation/{conversationId}/messages")
|
@GetMapping("/agent/{agentId}/conversation/{conversationId}/messages")
|
||||||
@Operation(summary = "获取会话消息", description = "查询指定会话的所有消息记录")
|
|
||||||
public Result<List<OpenApiMessageVO>> getMessages(
|
public Result<List<OpenApiMessageVO>> getMessages(
|
||||||
@Parameter(description = "Agent ID", required = true, example = "1")
|
|
||||||
@PathVariable Long agentId,
|
@PathVariable Long agentId,
|
||||||
@Parameter(description = "会话 ID", required = true, example = "conv-123")
|
|
||||||
@PathVariable String conversationId) {
|
@PathVariable String conversationId) {
|
||||||
OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
|
OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
|
||||||
request.setAgentId(agentId);
|
request.setAgentId(agentId);
|
||||||
@ -130,12 +127,12 @@ public class OpenApiDemoController {
|
|||||||
return conversationClient.getMessages(request);
|
return conversationClient.getMessages(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定会话。
|
||||||
|
*/
|
||||||
@DeleteMapping("/agent/{agentId}/conversation/{conversationId}")
|
@DeleteMapping("/agent/{agentId}/conversation/{conversationId}")
|
||||||
@Operation(summary = "删除会话", description = "删除指定的对话会话")
|
|
||||||
public Result<Void> deleteConversation(
|
public Result<Void> deleteConversation(
|
||||||
@Parameter(description = "Agent ID", required = true, example = "1")
|
|
||||||
@PathVariable Long agentId,
|
@PathVariable Long agentId,
|
||||||
@Parameter(description = "会话 ID", required = true, example = "conv-123")
|
|
||||||
@PathVariable String conversationId) {
|
@PathVariable String conversationId) {
|
||||||
OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
|
OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
|
||||||
request.setAgentId(agentId);
|
request.setAgentId(agentId);
|
||||||
@ -146,19 +143,21 @@ public class OpenApiDemoController {
|
|||||||
|
|
||||||
// ==================== Chat 相关接口 ====================
|
// ==================== Chat 相关接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前聊天发送模式。
|
||||||
|
*/
|
||||||
@GetMapping("/chat/mode")
|
@GetMapping("/chat/mode")
|
||||||
@Operation(summary = "获取聊天发送模式", description = "返回 stream(流式) 或 sync(同步)")
|
|
||||||
public Result<Map<String, String>> getChatMode() {
|
public Result<Map<String, String>> getChatMode() {
|
||||||
String mode = "sync".equalsIgnoreCase(chatMode) ? "sync" : "stream";
|
String mode = "sync".equalsIgnoreCase(chatMode) ? "sync" : "stream";
|
||||||
return Result.ok(Map.of("mode", mode));
|
return Result.ok(Map.of("mode", mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步对话接口。
|
||||||
|
*/
|
||||||
@PostMapping("/agent/{agentId}/chat/sync")
|
@PostMapping("/agent/{agentId}/chat/sync")
|
||||||
@Operation(summary = "同步对话", description = "发送消息并等待 AI 回复(非流式)")
|
|
||||||
public Result<OpenApiChatSyncResponse> chatSync(
|
public Result<OpenApiChatSyncResponse> chatSync(
|
||||||
@Parameter(description = "Agent ID", required = true, example = "1")
|
|
||||||
@PathVariable Long agentId,
|
@PathVariable Long agentId,
|
||||||
@Parameter(description = "对话请求")
|
|
||||||
@RequestBody OpenApiChatRequest request) {
|
@RequestBody OpenApiChatRequest request) {
|
||||||
request.setAgentId(agentId);
|
request.setAgentId(agentId);
|
||||||
request.setOpenId(ensureOpenId());
|
request.setOpenId(ensureOpenId());
|
||||||
@ -166,21 +165,21 @@ public class OpenApiDemoController {
|
|||||||
return chatClient.chatSync(request);
|
return chatClient.chatSync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式对话接口,按 SSE 事件返回消息分片。
|
||||||
|
*/
|
||||||
@GetMapping("/agent/{agentId}/chat/stream")
|
@GetMapping("/agent/{agentId}/chat/stream")
|
||||||
@Operation(summary = "流式对话", description = "发送消息并以 SSE 流式接收 AI 回复")
|
public SseEmitter chatStream(
|
||||||
public void chatStream(
|
|
||||||
@Parameter(description = "Agent ID", required = true, example = "1")
|
|
||||||
@PathVariable Long agentId,
|
@PathVariable Long agentId,
|
||||||
@Parameter(description = "用户消息", required = true, example = "你好")
|
|
||||||
@RequestParam String content,
|
@RequestParam String content,
|
||||||
@Parameter(description = "会话 ID(可选)", example = "conv-123")
|
@RequestParam(required = false) String conversationId) {
|
||||||
@RequestParam(required = false) String conversationId,
|
SseEmitter emitter = new SseEmitter(300000L);
|
||||||
HttpServletResponse response) {
|
emitter.onTimeout(() -> {
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
safeSend(emitter, "error", "SSE stream timeout");
|
||||||
response.setCharacterEncoding("UTF-8");
|
safeSend(emitter, "done", "");
|
||||||
response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
|
emitter.complete();
|
||||||
response.setHeader("Cache-Control", "no-cache");
|
});
|
||||||
response.setHeader("Connection", "keep-alive");
|
emitter.onError(error -> log.warn("SSE emitter error: {}", error.getMessage()));
|
||||||
|
|
||||||
OpenApiChatRequest request = new OpenApiChatRequest();
|
OpenApiChatRequest request = new OpenApiChatRequest();
|
||||||
request.setAgentId(agentId);
|
request.setAgentId(agentId);
|
||||||
@ -189,93 +188,64 @@ public class OpenApiDemoController {
|
|||||||
request.setConversationId(conversationId);
|
request.setConversationId(conversationId);
|
||||||
|
|
||||||
log.info("Stream chat request: agentId={}, content={}", agentId, content);
|
log.info("Stream chat request: agentId={}, content={}", agentId, content);
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
final boolean[] completed = {false};
|
|
||||||
try {
|
try {
|
||||||
PrintWriter writer = response.getWriter();
|
|
||||||
chatClient.chatStream(request, new SseEventListener() {
|
chatClient.chatStream(request, new SseEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onText(String text) {
|
public void onText(String text) {
|
||||||
try {
|
safeSend(emitter, "text", text);
|
||||||
writeSseEvent(writer, "text", text);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Failed to send SSE text", e);
|
|
||||||
completed[0] = true;
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onThinking(String thinking) {
|
public void onThinking(String thinking) {
|
||||||
try {
|
safeSend(emitter, "thinking", thinking);
|
||||||
writeSseEvent(writer, "thinking", thinking);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Failed to send SSE thinking", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(String data) {
|
public void onComplete(String data) {
|
||||||
try {
|
safeSend(emitter, "done", data);
|
||||||
writeSseEvent(writer, "done", data);
|
log.info("Stream chat completed");
|
||||||
log.info("Stream chat completed");
|
emitter.complete();
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Failed to send SSE completion", e);
|
|
||||||
} finally {
|
|
||||||
completed[0] = true;
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(String errorMessage) {
|
public void onError(String errorMessage) {
|
||||||
log.error("Stream chat error: {}", errorMessage);
|
log.error("Stream chat error: {}", errorMessage);
|
||||||
try {
|
safeSend(emitter, "error", errorMessage);
|
||||||
writeSseEvent(writer, "error", errorMessage);
|
safeSend(emitter, "done", "");
|
||||||
} catch (IOException e) {
|
emitter.complete();
|
||||||
log.error("Failed to send SSE error", e);
|
|
||||||
} finally {
|
|
||||||
completed[0] = true;
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
latch.await(5, TimeUnit.MINUTES);
|
|
||||||
if (!completed[0]) {
|
|
||||||
writeSseEvent(writer, "error", "SSE stream timeout");
|
|
||||||
writeSseEvent(writer, "done", "");
|
|
||||||
}
|
|
||||||
writer.flush();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Stream chat exception", e);
|
log.error("Stream chat exception", e);
|
||||||
try {
|
safeSend(emitter, "error", "stream exception: " + e.getMessage());
|
||||||
PrintWriter writer = response.getWriter();
|
safeSend(emitter, "done", "");
|
||||||
writeSseEvent(writer, "error", "stream exception: " + e.getMessage());
|
emitter.complete();
|
||||||
writeSseEvent(writer, "done", "");
|
}
|
||||||
writer.flush();
|
|
||||||
} catch (IOException ex) {
|
return emitter;
|
||||||
log.error("Failed to write stream exception", ex);
|
}
|
||||||
}
|
|
||||||
}
|
/**
|
||||||
}
|
* 输出一条 SSE 事件。
|
||||||
|
*/
|
||||||
private void writeSseEvent(PrintWriter writer, String event, String data) throws IOException {
|
private void safeSend(SseEmitter emitter, String event, String data) {
|
||||||
synchronized (writer) {
|
try {
|
||||||
writer.write("event: " + event + "\n");
|
emitter.send(SseEmitter.event().name(event).data(data == null ? "" : data));
|
||||||
String payload = data == null ? "" : data;
|
} catch (IOException e) {
|
||||||
String[] lines = payload.split("\\R", -1);
|
log.warn("SSE send failed, event={}", event, e);
|
||||||
for (String line : lines) {
|
|
||||||
writer.write("data: " + line + "\n");
|
|
||||||
}
|
|
||||||
writer.write("\n");
|
|
||||||
writer.flush();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户对应的 openId,不存在时会自动注册。
|
||||||
|
*/
|
||||||
private String ensureOpenId() {
|
private String ensureOpenId() {
|
||||||
return ensureOpenApiUser().getOpenId();
|
return ensureOpenApiUser().getOpenId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确保当前登录用户已注册为 OpenAPI 用户。
|
||||||
|
*/
|
||||||
private OpenApiUserVO ensureOpenApiUser() {
|
private OpenApiUserVO ensureOpenApiUser() {
|
||||||
Long userId = LoginHelper.getUserId();
|
Long userId = LoginHelper.getUserId();
|
||||||
String username = LoginHelper.getLoginUser().getNickname();
|
String username = LoginHelper.getLoginUser().getNickname();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user