重构项目结构:迁移到单体架构并优化代码组织
- 删除分布式架构相关文件和配置 - 将backend-distributed重命名为backend保留分布式代码作为参考 - 优化backend-single单体架构实现 - 添加Coze API集成相关文档和测试 - 清理项目根目录的部署脚本和配置文件 - 更新WebSocket和消息服务实现 - 完善认证服务和密码加密功能
This commit is contained in:
@@ -1,190 +0,0 @@
|
||||
package com.emotion.controller;
|
||||
|
||||
import com.emotion.dto.request.LoginRequest;
|
||||
import com.emotion.dto.request.RegisterRequest;
|
||||
import com.emotion.dto.response.AuthResponse;
|
||||
import com.emotion.dto.response.CaptchaResponse;
|
||||
import com.emotion.exception.AuthException;
|
||||
import com.emotion.exception.CaptchaException;
|
||||
import com.emotion.service.AuthService;
|
||||
import com.emotion.service.TokenService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
/**
|
||||
* AuthController测试类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
public class AuthControllerTest {
|
||||
|
||||
@Mock
|
||||
private AuthService authService;
|
||||
|
||||
@InjectMocks
|
||||
private AuthController authController;
|
||||
|
||||
private MockMvc mockMvc;
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
mockMvc = MockMvcBuilders.standaloneSetup(authController).build();
|
||||
objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogin() throws Exception {
|
||||
// 准备测试数据
|
||||
LoginRequest request = new LoginRequest();
|
||||
request.setAccount("testuser");
|
||||
request.setPassword("password123");
|
||||
request.setCaptcha("1234");
|
||||
request.setCaptchaKey("test-key");
|
||||
|
||||
AuthResponse response = new AuthResponse();
|
||||
response.setAccessToken("test-access-token");
|
||||
response.setRefreshToken("test-refresh-token");
|
||||
response.setExpiresIn(86400L);
|
||||
|
||||
// Mock服务方法
|
||||
when(authService.login(any(LoginRequest.class))).thenReturn(response);
|
||||
|
||||
// 执行测试
|
||||
mockMvc.perform(post("/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.message").value("登录成功"))
|
||||
.andExpect(jsonPath("$.data.accessToken").value("test-access-token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegister() throws Exception {
|
||||
// 准备测试数据
|
||||
RegisterRequest request = new RegisterRequest();
|
||||
request.setAccount("newuser");
|
||||
request.setPassword("password123");
|
||||
request.setUsername("New User");
|
||||
request.setEmail("newuser@example.com");
|
||||
request.setCaptcha("1234");
|
||||
request.setCaptchaKey("test-key");
|
||||
|
||||
AuthResponse response = new AuthResponse();
|
||||
response.setAccessToken("test-access-token");
|
||||
response.setRefreshToken("test-refresh-token");
|
||||
response.setExpiresIn(86400L);
|
||||
|
||||
// Mock服务方法
|
||||
when(authService.register(any(RegisterRequest.class))).thenReturn(response);
|
||||
|
||||
// 执行测试
|
||||
mockMvc.perform(post("/auth/register")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.message").value("注册成功"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateCaptcha() throws Exception {
|
||||
// 准备测试数据
|
||||
CaptchaResponse response = new CaptchaResponse();
|
||||
response.setCaptchaKey("test-captcha-key");
|
||||
response.setCaptchaImage("data:image/png;base64,test-image");
|
||||
response.setExpiresIn(300L);
|
||||
|
||||
// Mock服务方法
|
||||
when(authService.generateCaptcha()).thenReturn(response);
|
||||
|
||||
// 执行测试
|
||||
mockMvc.perform(get("/auth/captcha"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data.captchaKey").value("test-captcha-key"))
|
||||
.andExpect(jsonPath("$.data.expiresIn").value(300));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidateToken() throws Exception {
|
||||
// Mock服务方法
|
||||
when(authService.validateToken("valid-token")).thenReturn(true);
|
||||
|
||||
// 执行测试
|
||||
mockMvc.perform(get("/auth/validate")
|
||||
.header("Authorization", "Bearer valid-token"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200))
|
||||
.andExpect(jsonPath("$.data").value(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogout() throws Exception {
|
||||
// Mock服务方法
|
||||
when(authService.logoutByToken("valid-token")).thenReturn(true);
|
||||
|
||||
// 执行测试
|
||||
mockMvc.perform(post("/auth/logout")
|
||||
.header("Authorization", "Bearer valid-token"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(200));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoginWithInvalidCaptcha() throws Exception {
|
||||
// 准备测试数据
|
||||
LoginRequest request = new LoginRequest();
|
||||
request.setAccount("testuser");
|
||||
request.setPassword("password123");
|
||||
request.setCaptcha("wrong");
|
||||
request.setCaptchaKey("test-key");
|
||||
|
||||
// Mock服务方法抛出异常
|
||||
when(authService.login(any(LoginRequest.class))).thenThrow(new CaptchaException("验证码错误"));
|
||||
|
||||
// 执行测试
|
||||
mockMvc.perform(post("/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value(400))
|
||||
.andExpect(jsonPath("$.message").value("验证码错误"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoginWithInvalidAccount() throws Exception {
|
||||
// 准备测试数据
|
||||
LoginRequest request = new LoginRequest();
|
||||
request.setAccount("nonexistent");
|
||||
request.setPassword("password123");
|
||||
request.setCaptcha("1234");
|
||||
request.setCaptchaKey("test-key");
|
||||
|
||||
// Mock服务方法抛出异常
|
||||
when(authService.login(any(LoginRequest.class))).thenThrow(new AuthException("账号不存在"));
|
||||
|
||||
// 执行测试
|
||||
mockMvc.perform(post("/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isUnauthorized())
|
||||
.andExpect(jsonPath("$.code").value(401))
|
||||
.andExpect(jsonPath("$.message").value("账号不存在"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.emotion.service;
|
||||
|
||||
import com.emotion.service.impl.AiChatServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Coze API测试类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-24
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
public class CozeApiTest {
|
||||
|
||||
@Autowired
|
||||
private AIChatService aiChatService;
|
||||
|
||||
@Test
|
||||
public void testServiceAvailability() {
|
||||
// 测试服务可用性检查
|
||||
boolean isAvailable = aiChatService.isServiceAvailable();
|
||||
String status = aiChatService.getServiceStatus();
|
||||
|
||||
// 验证结果
|
||||
assertNotNull(status);
|
||||
assertTrue(status.equals("available") || status.equals("unavailable"));
|
||||
|
||||
// 如果配置正确,服务应该可用
|
||||
if (isAvailable) {
|
||||
assertEquals("available", status);
|
||||
} else {
|
||||
assertEquals("unavailable", status);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHealthCheck() {
|
||||
// 测试健康检查
|
||||
boolean healthStatus = aiChatService.healthCheck();
|
||||
|
||||
// 验证结果 - 健康检查应该返回布尔值
|
||||
assertNotNull(healthStatus);
|
||||
}
|
||||
|
||||
// 注意:以下测试需要真实的Coze API配置才能通过
|
||||
// 在测试环境中可能会失败,因为没有真实的API token
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void testSendMessage() {
|
||||
// 测试发送消息
|
||||
String conversationId = "test-conversation-001";
|
||||
String message = "你好,这是一条测试消息";
|
||||
String userId = "test-user-001";
|
||||
|
||||
String response = aiChatService.sendMessage(conversationId, message, userId);
|
||||
|
||||
// 验证响应
|
||||
assertNotNull(response);
|
||||
assertFalse(response.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendChatMessage() {
|
||||
// 测试聊天消息
|
||||
String conversationId = "test-conversation-002";
|
||||
String message = "请介绍一下你自己";
|
||||
String userId = "test-user-002";
|
||||
|
||||
String response = aiChatService.sendChatMessage(conversationId, message, userId);
|
||||
|
||||
// 验证响应
|
||||
assertNotNull(response);
|
||||
assertFalse(response.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuestChat() {
|
||||
// 测试访客聊天
|
||||
String message = "你好,我是访客用户";
|
||||
String clientIp = "192.168.1.100";
|
||||
|
||||
Map<String, Object> response = aiChatService.guestChat(message, clientIp);
|
||||
|
||||
// 验证响应
|
||||
assertNotNull(response);
|
||||
assertTrue(response.containsKey("message"));
|
||||
assertTrue(response.containsKey("error"));
|
||||
assertTrue(response.containsKey("timestamp"));
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.emotion.service;
|
||||
|
||||
import com.emotion.entity.Message;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 消息服务测试类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-24
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@Transactional
|
||||
public class MessageServiceTest {
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
@Test
|
||||
public void testCreateMessage() {
|
||||
// 创建消息对象
|
||||
Message message = new Message();
|
||||
message.setConversationId("test-conversation-001");
|
||||
message.setContent("这是一条测试消息");
|
||||
message.setType("text");
|
||||
message.setSender("user");
|
||||
message.setCreateBy("test-user-001");
|
||||
|
||||
// 调用优化后的createMessage方法
|
||||
Message savedMessage = messageService.createMessage(message);
|
||||
|
||||
// 验证结果
|
||||
assertNotNull(savedMessage);
|
||||
assertNotNull(savedMessage.getId());
|
||||
assertEquals("test-conversation-001", savedMessage.getConversationId());
|
||||
assertEquals("这是一条测试消息", savedMessage.getContent());
|
||||
assertEquals("text", savedMessage.getType());
|
||||
assertEquals("user", savedMessage.getSender());
|
||||
assertEquals("test-user-001", savedMessage.getCreateBy());
|
||||
|
||||
// 验证默认值设置
|
||||
assertNotNull(savedMessage.getTimestamp());
|
||||
assertEquals("sent", savedMessage.getStatus());
|
||||
assertEquals(0, savedMessage.getIsRead());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMessageWithCustomValues() {
|
||||
// 创建消息对象,设置自定义时间戳和状态
|
||||
Message message = new Message();
|
||||
message.setConversationId("test-conversation-002");
|
||||
message.setContent("自定义状态消息");
|
||||
message.setType("text");
|
||||
message.setSender("ai");
|
||||
message.setCreateBy("ai");
|
||||
message.setTimestamp(LocalDateTime.of(2025, 7, 24, 10, 30, 0));
|
||||
message.setStatus("processing");
|
||||
message.setIsRead(1);
|
||||
|
||||
// 调用优化后的createMessage方法
|
||||
Message savedMessage = messageService.createMessage(message);
|
||||
|
||||
// 验证结果 - 自定义值应该被保留
|
||||
assertNotNull(savedMessage);
|
||||
assertEquals("test-conversation-002", savedMessage.getConversationId());
|
||||
assertEquals("自定义状态消息", savedMessage.getContent());
|
||||
assertEquals("ai", savedMessage.getSender());
|
||||
assertEquals(LocalDateTime.of(2025, 7, 24, 10, 30, 0), savedMessage.getTimestamp());
|
||||
assertEquals("processing", savedMessage.getStatus());
|
||||
assertEquals(1, savedMessage.getIsRead());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMessageWithPartialDefaults() {
|
||||
// 创建消息对象,只设置部分字段
|
||||
Message message = new Message();
|
||||
message.setConversationId("test-conversation-003");
|
||||
message.setContent("部分默认值消息");
|
||||
message.setType("text");
|
||||
message.setSender("user");
|
||||
message.setCreateBy("test-user-003");
|
||||
message.setStatus("delivered"); // 设置自定义状态
|
||||
// 不设置timestamp和isRead,应该使用默认值
|
||||
|
||||
// 调用优化后的createMessage方法
|
||||
Message savedMessage = messageService.createMessage(message);
|
||||
|
||||
// 验证结果
|
||||
assertNotNull(savedMessage);
|
||||
assertEquals("test-conversation-003", savedMessage.getConversationId());
|
||||
assertEquals("部分默认值消息", savedMessage.getContent());
|
||||
assertEquals("delivered", savedMessage.getStatus()); // 自定义状态
|
||||
assertNotNull(savedMessage.getTimestamp()); // 默认时间戳
|
||||
assertEquals(0, savedMessage.getIsRead()); // 默认未读状态
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.emotion.service;
|
||||
|
||||
import com.emotion.entity.User;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* 密码加密测试类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-24
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@Transactional
|
||||
public class PasswordEncryptionTest {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Test
|
||||
public void testPasswordEncryption() {
|
||||
String rawPassword = "testPassword123";
|
||||
|
||||
// 测试密码编码器
|
||||
String encodedPassword = passwordEncoder.encode(rawPassword);
|
||||
|
||||
// 验证密码不是明文
|
||||
assertNotEquals(rawPassword, encodedPassword);
|
||||
|
||||
// 验证密码匹配
|
||||
assertTrue(passwordEncoder.matches(rawPassword, encodedPassword));
|
||||
|
||||
// 验证错误密码不匹配
|
||||
assertFalse(passwordEncoder.matches("wrongPassword", encodedPassword));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserCreationWithPasswordEncryption() {
|
||||
String account = "testuser001";
|
||||
String username = "Test User";
|
||||
String rawPassword = "testPassword123";
|
||||
String email = "test@example.com";
|
||||
String phone = "13800138000";
|
||||
|
||||
// 创建用户
|
||||
User user = userService.createUser(account, username, rawPassword, email, phone);
|
||||
|
||||
// 验证用户创建成功
|
||||
assertNotNull(user);
|
||||
assertNotNull(user.getId());
|
||||
assertEquals(account, user.getAccount());
|
||||
assertEquals(username, user.getUsername());
|
||||
assertEquals(email, user.getEmail());
|
||||
assertEquals(phone, user.getPhone());
|
||||
|
||||
// 验证密码已加密
|
||||
assertNotEquals(rawPassword, user.getPassword());
|
||||
|
||||
// 验证密码验证功能
|
||||
assertTrue(userService.validatePassword(user.getId(), rawPassword));
|
||||
assertFalse(userService.validatePassword(user.getId(), "wrongPassword"));
|
||||
|
||||
// 验证可以通过账号查询到用户
|
||||
User foundUser = userService.getByAccount(account);
|
||||
assertNotNull(foundUser);
|
||||
assertEquals(user.getId(), foundUser.getId());
|
||||
|
||||
// 验证密码匹配
|
||||
assertTrue(passwordEncoder.matches(rawPassword, foundUser.getPassword()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordConsistencyBetweenServices() {
|
||||
String rawPassword = "consistencyTest123";
|
||||
|
||||
// 使用UserService加密密码
|
||||
String userServiceEncoded = passwordEncoder.encode(rawPassword);
|
||||
|
||||
// 验证AuthService能正确验证UserService加密的密码
|
||||
assertTrue(passwordEncoder.matches(rawPassword, userServiceEncoded));
|
||||
|
||||
// 测试多次加密产生不同的哈希值(BCrypt的特性)
|
||||
String encoded1 = passwordEncoder.encode(rawPassword);
|
||||
String encoded2 = passwordEncoder.encode(rawPassword);
|
||||
|
||||
// 哈希值应该不同(因为BCrypt使用随机盐)
|
||||
assertNotEquals(encoded1, encoded2);
|
||||
|
||||
// 但都应该能验证原始密码
|
||||
assertTrue(passwordEncoder.matches(rawPassword, encoded1));
|
||||
assertTrue(passwordEncoder.matches(rawPassword, encoded2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBCryptPasswordFormat() {
|
||||
String rawPassword = "formatTest123";
|
||||
String encodedPassword = passwordEncoder.encode(rawPassword);
|
||||
|
||||
// BCrypt密码应该以$2a$、$2b$或$2y$开头
|
||||
assertTrue(encodedPassword.startsWith("$2a$") ||
|
||||
encodedPassword.startsWith("$2b$") ||
|
||||
encodedPassword.startsWith("$2y$"),
|
||||
"密码应该使用BCrypt格式加密");
|
||||
|
||||
// BCrypt密码长度通常是60个字符
|
||||
assertEquals(60, encodedPassword.length(), "BCrypt密码长度应该是60个字符");
|
||||
}
|
||||
|
||||
/*
|
||||
// 注意:以下测试需要完整的认证流程,可能需要验证码等
|
||||
@Test
|
||||
public void testFullAuthenticationFlow() {
|
||||
// 这个测试需要模拟完整的注册和登录流程
|
||||
// 由于涉及验证码等复杂逻辑,在实际测试中可能需要mock相关服务
|
||||
|
||||
String account = "authtest001";
|
||||
String password = "authTestPassword123";
|
||||
String email = "authtest@example.com";
|
||||
|
||||
// 1. 注册用户
|
||||
RegisterRequest registerRequest = new RegisterRequest();
|
||||
registerRequest.setAccount(account);
|
||||
registerRequest.setPassword(password);
|
||||
registerRequest.setEmail(email);
|
||||
// 需要设置验证码等其他必要字段
|
||||
|
||||
// 2. 登录验证
|
||||
LoginRequest loginRequest = new LoginRequest();
|
||||
loginRequest.setAccount(account);
|
||||
loginRequest.setPassword(password);
|
||||
// 需要设置验证码等其他必要字段
|
||||
|
||||
// 验证登录成功
|
||||
// AuthResponse authResponse = authService.login(loginRequest);
|
||||
// assertNotNull(authResponse);
|
||||
// assertNotNull(authResponse.getToken());
|
||||
}
|
||||
*/
|
||||
}
|
||||
Reference in New Issue
Block a user