feat: AI 场景路由、ASR 服务及前后端全链路同步

- 新增 AI 场景路由控制器和管理接口
- 新增 ASR 语音识别服务及前后端集成
- 同步 AI Runtime 客户端到 Web/小程序/Life-Script
- 完善 AI 配置测试修复和管理后台路由配置
- 新增数据库迁移脚本

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 13:25:21 +08:00
parent d77090aa5e
commit 89fc42819d
72 changed files with 4584 additions and 383 deletions
@@ -1,9 +1,13 @@
package com.emotion.controller;
import com.emotion.service.AuthService;
import com.emotion.service.TokenService;
import com.emotion.util.JwtUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
@@ -17,7 +21,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author huazhongmin
* @date 2025-07-26
*/
@WebMvcTest(AuthController.class)
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false)
public class AuthControllerTest {
@Autowired
@@ -26,13 +31,28 @@ public class AuthControllerTest {
@MockBean
private AuthService authService;
@MockBean
private JwtUtil jwtUtil;
@MockBean
private TokenService tokenService;
@BeforeEach
public void setUp() {
when(jwtUtil.validateToken("test-token")).thenReturn(true);
when(jwtUtil.getUserIdFromToken("test-token")).thenReturn("test-user");
when(jwtUtil.getUsernameFromToken("test-token")).thenReturn("tester");
when(jwtUtil.getUserTypeFromToken("test-token")).thenReturn("user");
}
@Test
public void testCheckAccountExists() throws Exception {
// 模拟账户存在的情况
when(authService.existsByAccount("existingUser")).thenReturn(true);
mockMvc.perform(get("/auth/check-account")
.param("account", "existingUser"))
mockMvc.perform(get("/api/auth/checkAccount").contextPath("/api")
.param("account", "existingUser")
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value(true));
@@ -43,8 +63,9 @@ public class AuthControllerTest {
// 模拟账户不存在的情况
when(authService.existsByAccount("nonExistingUser")).thenReturn(false);
mockMvc.perform(get("/auth/check-account")
.param("account", "nonExistingUser"))
mockMvc.perform(get("/api/auth/checkAccount").contextPath("/api")
.param("account", "nonExistingUser")
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value(false));
@@ -55,8 +76,9 @@ public class AuthControllerTest {
// 模拟邮箱存在的情况
when(authService.existsByEmail("existing@example.com")).thenReturn(true);
mockMvc.perform(get("/auth/check-email")
.param("email", "existing@example.com"))
mockMvc.perform(get("/api/auth/checkEmail").contextPath("/api")
.param("email", "existing@example.com")
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value(true));
@@ -67,8 +89,9 @@ public class AuthControllerTest {
// 模拟邮箱不存在的情况
when(authService.existsByEmail("nonexisting@example.com")).thenReturn(false);
mockMvc.perform(get("/auth/check-email")
.param("email", "nonexisting@example.com"))
mockMvc.perform(get("/api/auth/checkEmail").contextPath("/api")
.param("email", "nonexisting@example.com")
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value(false));
@@ -79,8 +102,9 @@ public class AuthControllerTest {
// 模拟手机号存在的情况
when(authService.existsByPhone("13800138000")).thenReturn(true);
mockMvc.perform(get("/auth/check-phone")
.param("phone", "13800138000"))
mockMvc.perform(get("/api/auth/checkPhone").contextPath("/api")
.param("phone", "13800138000")
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value(true));
@@ -91,8 +115,9 @@ public class AuthControllerTest {
// 模拟手机号不存在的情况
when(authService.existsByPhone("13900139000")).thenReturn(false);
mockMvc.perform(get("/auth/check-phone")
.param("phone", "13900139000"))
mockMvc.perform(get("/api/auth/checkPhone").contextPath("/api")
.param("phone", "13900139000")
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").value(false));
@@ -10,6 +10,7 @@ import org.junit.jupiter.api.RepeatedTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.util.AopTestUtils;
import java.lang.reflect.Method;
import java.util.HashMap;
@@ -35,6 +36,8 @@ public class CozeWorkflowIntegrationTest {
@Autowired
private AiChatService aiChatService;
private AiChatServiceImpl aiChatServiceImpl;
@Autowired
private AiConfigService aiConfigService;
@@ -49,6 +52,7 @@ public class CozeWorkflowIntegrationTest {
@BeforeEach
public void setUp() {
random = new Random();
aiChatServiceImpl = AopTestUtils.getTargetObject(aiChatService);
}
// ==================== Property 1: Request Format Correctness ====================
@@ -82,7 +86,7 @@ public class CozeWorkflowIntegrationTest {
@SuppressWarnings("unchecked")
Map<String, Object> requestBody = (Map<String, Object>) buildWorkflowRequestMethod.invoke(
aiChatService, config, parameters, userId);
aiChatServiceImpl, config, parameters, userId);
// 验证必需字段
// 2.1: workflow_id - 应该与数据库配置一致
@@ -155,7 +159,7 @@ public class CozeWorkflowIntegrationTest {
parseMethod.setAccessible(true);
java.util.stream.Stream<String> lines = sseResponse.lines();
String result = (String) parseMethod.invoke(aiChatService, lines);
String result = (String) parseMethod.invoke(aiChatServiceImpl, lines);
// 验证正确提取output内容
assertEquals("这是AI生成的内容", result,
@@ -184,7 +188,7 @@ public class CozeWorkflowIntegrationTest {
parseMethod.setAccessible(true);
java.util.stream.Stream<String> lines = sseResponse.lines();
String result = (String) parseMethod.invoke(aiChatService, lines);
String result = (String) parseMethod.invoke(aiChatServiceImpl, lines);
// 验证正确提取随机output内容
assertEquals(randomOutput, result,
@@ -218,7 +222,7 @@ public class CozeWorkflowIntegrationTest {
parseMethod.setAccessible(true);
java.util.stream.Stream<String> lines = sseResponse.lines();
String result = (String) parseMethod.invoke(aiChatService, lines);
String result = (String) parseMethod.invoke(aiChatServiceImpl, lines);
// 验证只提取End节点的内容
assertEquals("最终输出内容", result,
@@ -244,7 +248,7 @@ public class CozeWorkflowIntegrationTest {
parseMethod.setAccessible(true);
java.util.stream.Stream<String> lines = sseResponse.lines();
String result = (String) parseMethod.invoke(aiChatService, lines);
String result = (String) parseMethod.invoke(aiChatServiceImpl, lines);
// 当content不是JSON或没有output字段时,应返回原始content
assertEquals("直接内容,没有output字段", result,
@@ -276,7 +280,7 @@ public class CozeWorkflowIntegrationTest {
@SuppressWarnings("unchecked")
Map<String, Object> mergedParams = (Map<String, Object>) mergeMethod.invoke(
aiChatService, config, runtimeParams);
aiChatServiceImpl, config, runtimeParams);
// 验证运行时参数被正确设置
assertEquals(runtimeInput, mergedParams.get("input"),
@@ -295,7 +299,7 @@ public class CozeWorkflowIntegrationTest {
extractMethod.setAccessible(true);
String content = "{\"output\":\"提取的内容\"}";
String result = (String) extractMethod.invoke(aiChatService, content);
String result = (String) extractMethod.invoke(aiChatServiceImpl, content);
assertEquals("提取的内容", result, "应正确提取output字段");
}
@@ -308,7 +312,7 @@ public class CozeWorkflowIntegrationTest {
extractMethod.setAccessible(true);
String content = "这不是JSON内容";
String result = (String) extractMethod.invoke(aiChatService, content);
String result = (String) extractMethod.invoke(aiChatServiceImpl, content);
assertEquals("这不是JSON内容", result, "非JSON内容应原样返回");
}
@@ -324,7 +328,7 @@ public class CozeWorkflowIntegrationTest {
String randomOutput = "随机输出_" + UUID.randomUUID().toString();
String content = "{\"output\":\"" + randomOutput + "\"}";
String result = (String) extractMethod.invoke(aiChatService, content);
String result = (String) extractMethod.invoke(aiChatServiceImpl, content);
assertEquals(randomOutput, result, "应正确提取随机生成的output内容");
}