服务层重构与优化:补全所有ServiceImpl实现类,修复RestTemplate注入,完善DTO与配置,保证编译与启动通过
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# Controller层重构总结
|
||||
|
||||
## 重构概述
|
||||
|
||||
本次重构主要完成了以下工作:
|
||||
|
||||
1. **创建统一的request和response包结构**
|
||||
2. **建立全局异常处理机制**
|
||||
3. **重构所有Controller层代码**
|
||||
4. **优化接口入参和出参规范**
|
||||
|
||||
## 完成的工作
|
||||
|
||||
### 1. 创建统一的基础类
|
||||
|
||||
#### 在emotion-common模块中创建:
|
||||
|
||||
- `BaseRequest` - 基础请求类,包含通用字段如requestId、clientIp、userAgent等
|
||||
- `BaseResponse` - 基础响应类,包含通用字段如timestamp、requestId、processingTime等
|
||||
- `BasePageRequest` - 基础分页请求类,继承BaseRequest,包含分页参数
|
||||
- `BasePageResponse` - 基础分页响应类,继承BaseResponse,包含分页信息
|
||||
|
||||
#### 异常处理类:
|
||||
|
||||
- `BusinessException` - 业务异常
|
||||
- `AuthException` - 认证异常
|
||||
- `CaptchaException` - 验证码异常
|
||||
- `TokenException` - Token异常
|
||||
- `GlobalExceptionHandler` - 全局异常处理器
|
||||
|
||||
### 2. 各模块request和response类
|
||||
|
||||
#### emotion-ai模块:
|
||||
- **Request类**:
|
||||
- `AiChatRequest` - AI聊天请求
|
||||
- `CreateConversationRequest` - 创建会话请求
|
||||
- `EmotionAnalysisRequest` - 情绪分析请求
|
||||
- `GuestChatRequest` - 访客聊天请求
|
||||
- `ConversationListRequest` - 会话列表请求
|
||||
|
||||
- **Response类**:
|
||||
- `AiChatResponse` - AI聊天响应
|
||||
- `CreateConversationResponse` - 创建会话响应
|
||||
- `EmotionAnalysisResponse` - 情绪分析响应
|
||||
- `GuestChatResponse` - 访客聊天响应
|
||||
- `ConversationListResponse` - 会话列表响应
|
||||
|
||||
#### emotion-auth模块:
|
||||
- **Request类**:
|
||||
- `LoginRequest` - 登录请求
|
||||
- `RegisterRequest` - 注册请求
|
||||
- `OAuthLoginRequest` - 第三方登录请求
|
||||
- `SliderCaptchaVerifyRequest` - 滑块验证码验证请求
|
||||
|
||||
- **Response类**:
|
||||
- `LoginResponse` - 登录响应
|
||||
- `UserInfoResponse` - 用户信息响应
|
||||
- `CaptchaResponse` - 验证码响应
|
||||
- `SliderCaptchaResponse` - 滑块验证码响应
|
||||
|
||||
#### emotion-user模块:
|
||||
- **Request类**:
|
||||
- `UserUpdateRequest` - 用户更新请求
|
||||
|
||||
- **Response类**:
|
||||
- `UserInfoResponse` - 用户信息响应
|
||||
|
||||
#### emotion-record模块:
|
||||
- **Request类**:
|
||||
- `CreateEmotionRecordRequest` - 创建情绪记录请求
|
||||
|
||||
- **Response类**:
|
||||
- `EmotionRecordResponse` - 情绪记录响应
|
||||
|
||||
### 3. Controller层重构
|
||||
|
||||
#### 重构原则:
|
||||
1. **移除业务逻辑** - 所有业务逻辑移至Service层
|
||||
2. **统一入参出参** - 使用新的request/response格式
|
||||
3. **移除try-catch** - 使用全局异常处理机制
|
||||
4. **统一返回格式** - 使用Result包装返回结果
|
||||
|
||||
#### 已重构的Controller:
|
||||
- `AiChatController` - AI聊天控制器
|
||||
- `GuestChatController` - 访客聊天控制器
|
||||
- `AuthController` - 认证控制器
|
||||
- `CaptchaController` - 验证码控制器
|
||||
- `UserController` - 用户控制器
|
||||
|
||||
### 4. 全局异常处理
|
||||
|
||||
#### 异常处理机制:
|
||||
- 统一异常处理器 `GlobalExceptionHandler`
|
||||
- 支持多种异常类型处理
|
||||
- 自动参数校验异常处理
|
||||
- 统一错误响应格式
|
||||
|
||||
#### 支持的异常类型:
|
||||
- 业务异常 `BusinessException`
|
||||
- 认证异常 `AuthException`
|
||||
- 验证码异常 `CaptchaException`
|
||||
- Token异常 `TokenException`
|
||||
- 参数校验异常 `MethodArgumentNotValidException`
|
||||
- 系统异常 `RuntimeException`、`Exception`
|
||||
|
||||
## 代码规范
|
||||
|
||||
### 1. 命名规范
|
||||
- Request类以`Request`结尾
|
||||
- Response类以`Response`结尾
|
||||
- 包名使用`request`和`response`
|
||||
|
||||
### 2. 继承关系
|
||||
- 所有Request类继承`BaseRequest`或`BasePageRequest`
|
||||
- 所有Response类继承`BaseResponse`或`BasePageResponse`
|
||||
|
||||
### 3. 注解规范
|
||||
- 使用`@Schema`注解描述字段
|
||||
- 使用`@Valid`注解进行参数校验
|
||||
- 使用`@NotBlank`、`@NotNull`等校验注解
|
||||
|
||||
### 4. Controller规范
|
||||
- 不包含业务逻辑
|
||||
- 统一使用Result包装返回结果
|
||||
- 不使用try-catch,依赖全局异常处理
|
||||
- 接口文档完整
|
||||
|
||||
## 优势
|
||||
|
||||
1. **代码结构清晰** - 职责分离明确
|
||||
2. **异常处理统一** - 全局异常处理机制
|
||||
3. **接口规范统一** - 统一的入参出参格式
|
||||
4. **维护性提升** - 代码更易维护和扩展
|
||||
5. **开发效率提升** - 减少重复代码
|
||||
|
||||
## 后续工作
|
||||
|
||||
1. **Service层接口更新** - 确保Service层使用新的request/response格式
|
||||
2. **单元测试编写** - 为重构后的代码编写测试用例
|
||||
3. **接口文档更新** - 更新API文档
|
||||
4. **性能测试** - 验证重构后的性能表现
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有Controller层不再包含业务逻辑
|
||||
2. 异常处理统一由GlobalExceptionHandler处理
|
||||
3. 新的request/response类需要在Service层中使用
|
||||
4. 需要更新相关的单元测试和集成测试
|
||||
+7
-15
@@ -1,11 +1,10 @@
|
||||
package com.emotionmuseum.ai.controller;
|
||||
|
||||
import com.emotionmuseum.ai.dto.*;
|
||||
import com.emotionmuseum.ai.entity.Conversation;
|
||||
import com.emotionmuseum.ai.entity.Message;
|
||||
import com.emotionmuseum.ai.request.*;
|
||||
import com.emotionmuseum.ai.response.*;
|
||||
import com.emotionmuseum.ai.service.AiChatService;
|
||||
import com.emotionmuseum.ai.service.ConversationDbService;
|
||||
import com.emotionmuseum.common.dto.PageQuery;
|
||||
import com.emotionmuseum.common.response.BasePageResponse;
|
||||
import com.emotionmuseum.common.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -16,7 +15,6 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI聊天控制器
|
||||
@@ -40,17 +38,15 @@ public class AiChatController {
|
||||
public Result<CreateConversationResponse> createConversation(
|
||||
@Valid @RequestBody CreateConversationRequest request) {
|
||||
log.info("收到创建会话请求: userId={}, title={}", request.getUserId(), request.getTitle());
|
||||
|
||||
CreateConversationResponse response = aiChatService.createConversation(request);
|
||||
return Result.success(response);
|
||||
}
|
||||
|
||||
@Operation(summary = "发送聊天消息")
|
||||
@PostMapping("/send")
|
||||
public Result<ChatResponse> sendMessage(@Valid @RequestBody ChatRequest request) {
|
||||
public Result<AiChatResponse> sendMessage(@Valid @RequestBody AiChatRequest request) {
|
||||
log.info("收到聊天请求: userId={}, message={}", request.getUserId(), request.getMessage());
|
||||
|
||||
ChatResponse response = aiChatService.chat(request);
|
||||
AiChatResponse response = aiChatService.chat(request);
|
||||
return Result.success(response);
|
||||
}
|
||||
|
||||
@@ -58,16 +54,14 @@ public class AiChatController {
|
||||
@PostMapping("/emotion/analyze")
|
||||
public Result<EmotionAnalysisResponse> analyzeEmotion(@Valid @RequestBody EmotionAnalysisRequest request) {
|
||||
log.info("收到情绪分析请求: userId={}, text={}", request.getUserId(), request.getText());
|
||||
|
||||
EmotionAnalysisResponse response = aiChatService.analyzeEmotion(request);
|
||||
return Result.success(response);
|
||||
}
|
||||
|
||||
@Operation(summary = "流式聊天")
|
||||
@PostMapping("/stream")
|
||||
public Result<String> streamChat(@Valid @RequestBody ChatRequest request) {
|
||||
public Result<String> streamChat(@Valid @RequestBody AiChatRequest request) {
|
||||
log.info("收到流式聊天请求: userId={}", request.getUserId());
|
||||
|
||||
String response = aiChatService.streamChat(request);
|
||||
return Result.success(response);
|
||||
}
|
||||
@@ -76,16 +70,14 @@ public class AiChatController {
|
||||
@GetMapping("/health")
|
||||
public Result<Boolean> healthCheck() {
|
||||
log.info("AI服务健康检查");
|
||||
|
||||
boolean isHealthy = aiChatService.healthCheck();
|
||||
return Result.success(isHealthy);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取AI服务信息")
|
||||
@GetMapping("/info")
|
||||
public Result<Object> getServiceInfo() {
|
||||
public Result<String> getServiceInfo() {
|
||||
log.info("获取AI服务信息");
|
||||
|
||||
return Result.success("Emotion Museum AI Service - Powered by Spring AI & Coze");
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package com.emotionmuseum.ai.controller;
|
||||
|
||||
import com.emotionmuseum.ai.dto.*;
|
||||
import com.emotionmuseum.ai.request.*;
|
||||
import com.emotionmuseum.ai.response.*;
|
||||
import com.emotionmuseum.ai.service.GuestChatService;
|
||||
import com.emotionmuseum.common.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.emotionmuseum.ai.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI聊天请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "AI聊天请求")
|
||||
public class AiChatRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID", example = "user_123")
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "消息内容", example = "我今天感觉有点焦虑,不知道该怎么办")
|
||||
@NotBlank(message = "消息内容不能为空")
|
||||
@Size(max = 2000, message = "消息内容不能超过2000字符")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "对话ID(可选)", example = "conv_123456")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "消息类型", example = "text")
|
||||
private String type = "text";
|
||||
|
||||
@Schema(description = "聊天历史(可选)")
|
||||
private List<ChatMessage> history;
|
||||
|
||||
@Schema(description = "是否需要情绪分析", example = "true")
|
||||
private Boolean needEmotionAnalysis = true;
|
||||
|
||||
@Schema(description = "上下文信息")
|
||||
private String context;
|
||||
|
||||
/**
|
||||
* 聊天消息
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "聊天消息")
|
||||
public static class ChatMessage {
|
||||
@Schema(description = "角色", example = "user")
|
||||
private String role; // user, assistant
|
||||
|
||||
@Schema(description = "消息内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "时间戳")
|
||||
private Long timestamp;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.emotionmuseum.ai.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BasePageRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 会话列表请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "会话列表请求")
|
||||
public class ConversationListRequest extends BasePageRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID", example = "user_123")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "会话类型", example = "emotion_chat")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "会话状态", example = "active")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "用户类型", example = "guest")
|
||||
private String userType;
|
||||
|
||||
@Schema(description = "开始时间", example = "2025-01-01 00:00:00")
|
||||
private String startTime;
|
||||
|
||||
@Schema(description = "结束时间", example = "2025-12-31 23:59:59")
|
||||
private String endTime;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.emotionmuseum.ai.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 创建会话请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "创建会话请求")
|
||||
public class CreateConversationRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID", example = "user_123")
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "会话标题", example = "今日心情分享")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "会话类型", example = "emotion_chat")
|
||||
private String type = "emotion_chat";
|
||||
|
||||
@Schema(description = "初始消息", example = "你好,我想聊聊今天的心情")
|
||||
private String initialMessage;
|
||||
|
||||
@Schema(description = "上下文信息")
|
||||
private String context;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.emotionmuseum.ai.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 情绪分析请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "情绪分析请求")
|
||||
public class EmotionAnalysisRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID", example = "user_123")
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "待分析文本", example = "我今天感觉很沮丧,工作压力很大")
|
||||
@NotBlank(message = "待分析文本不能为空")
|
||||
@Size(max = 1000, message = "待分析文本不能超过1000字符")
|
||||
private String text;
|
||||
|
||||
@Schema(description = "分析类型", example = "detailed")
|
||||
private String analysisType = "detailed"; // simple, detailed
|
||||
|
||||
@Schema(description = "语言", example = "zh")
|
||||
private String language = "zh";
|
||||
|
||||
@Schema(description = "上下文信息")
|
||||
private String context;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.emotionmuseum.ai.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 访客聊天请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "访客聊天请求")
|
||||
public class GuestChatRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "消息内容", example = "你好,我想聊聊今天的心情")
|
||||
@NotBlank(message = "消息内容不能为空")
|
||||
@Size(max = 2000, message = "消息内容不能超过2000字符")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "会话ID(可选,如果不提供则创建新会话)", example = "conv_123456")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "会话标题(创建新会话时使用)", example = "今日心情分享")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "消息类型", example = "text")
|
||||
private String messageType = "text";
|
||||
|
||||
@Schema(description = "是否流式响应", example = "false")
|
||||
private Boolean stream = false;
|
||||
|
||||
@Schema(description = "附加上下文信息")
|
||||
private String context;
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.emotionmuseum.ai.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI聊天响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "AI聊天响应")
|
||||
public class AiChatResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "消息ID")
|
||||
private String messageId;
|
||||
|
||||
@Schema(description = "对话ID")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "AI回复内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "消息类型", example = "text")
|
||||
private String type = "text";
|
||||
|
||||
@Schema(description = "发送者", example = "assistant")
|
||||
private String sender = "assistant";
|
||||
|
||||
@Schema(description = "响应时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime responseTime;
|
||||
|
||||
@Schema(description = "情绪分析结果")
|
||||
private EmotionAnalysisResponse emotionAnalysis;
|
||||
|
||||
@Schema(description = "使用情况")
|
||||
private Usage usage;
|
||||
|
||||
@Schema(description = "元数据")
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
@Schema(description = "是否为多条消息")
|
||||
private Boolean multipleMessages = false;
|
||||
|
||||
@Schema(description = "消息数量")
|
||||
private Integer messageCount = 1;
|
||||
|
||||
@Schema(description = "所有消息ID列表(当拆分为多条消息时)")
|
||||
private List<String> messageIds;
|
||||
|
||||
/**
|
||||
* 使用情况
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "使用情况")
|
||||
public static class Usage {
|
||||
@Schema(description = "输入Token数")
|
||||
private Integer promptTokens;
|
||||
|
||||
@Schema(description = "输出Token数")
|
||||
private Integer completionTokens;
|
||||
|
||||
@Schema(description = "总Token数")
|
||||
private Integer totalTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* 情绪分析响应
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "情绪分析响应")
|
||||
public static class EmotionAnalysisResponse {
|
||||
@Schema(description = "情绪类型")
|
||||
private String emotionType;
|
||||
|
||||
@Schema(description = "情绪强度")
|
||||
private Double intensity;
|
||||
|
||||
@Schema(description = "情绪描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "建议")
|
||||
private String suggestion;
|
||||
|
||||
@Schema(description = "置信度")
|
||||
private Double confidence;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.emotionmuseum.ai.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 会话列表响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "会话列表响应")
|
||||
public class ConversationListResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "会话标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "会话类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "会话状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "用户类型")
|
||||
private String userType;
|
||||
|
||||
@Schema(description = "消息数量")
|
||||
private Integer messageCount;
|
||||
|
||||
@Schema(description = "最后活跃时间")
|
||||
private LocalDateTime lastActiveTime;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "主要情绪")
|
||||
private String primaryEmotion;
|
||||
|
||||
@Schema(description = "情绪强度")
|
||||
private Double emotionIntensity;
|
||||
|
||||
@Schema(description = "Coze会话ID")
|
||||
private String cozeConversationId;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.emotionmuseum.ai.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 创建会话响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "创建会话响应")
|
||||
public class CreateConversationResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "会话标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "会话类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "会话状态", example = "active")
|
||||
private String status = "active";
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "Coze会话ID")
|
||||
private String cozeConversationId;
|
||||
|
||||
@Schema(description = "元数据")
|
||||
private Map<String, Object> metadata;
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.emotionmuseum.ai.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 情绪分析响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "情绪分析响应")
|
||||
public class EmotionAnalysisResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "主要情绪", example = "焦虑")
|
||||
private String primaryEmotion;
|
||||
|
||||
@Schema(description = "情绪强度", example = "0.75")
|
||||
private Double intensity;
|
||||
|
||||
@Schema(description = "情绪极性", example = "negative")
|
||||
private String polarity; // positive, negative, neutral
|
||||
|
||||
@Schema(description = "置信度", example = "0.85")
|
||||
private Double confidence;
|
||||
|
||||
@Schema(description = "情绪分布")
|
||||
private List<EmotionScore> emotions;
|
||||
|
||||
@Schema(description = "关键词")
|
||||
private List<String> keywords;
|
||||
|
||||
@Schema(description = "建议")
|
||||
private String suggestion;
|
||||
|
||||
@Schema(description = "分析时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime analysisTime;
|
||||
|
||||
@Schema(description = "额外信息")
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
/**
|
||||
* 情绪得分
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "情绪得分")
|
||||
public static class EmotionScore {
|
||||
@Schema(description = "情绪名称")
|
||||
private String emotion;
|
||||
|
||||
@Schema(description = "得分")
|
||||
private Double score;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package com.emotionmuseum.ai.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 访客聊天响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "访客聊天响应")
|
||||
public class GuestChatResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "访客用户ID")
|
||||
private String guestUserId;
|
||||
|
||||
@Schema(description = "访客昵称")
|
||||
private String guestNickname;
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "会话标题")
|
||||
private String conversationTitle;
|
||||
|
||||
@Schema(description = "用户消息ID")
|
||||
private String userMessageId;
|
||||
|
||||
@Schema(description = "AI回复消息ID")
|
||||
private String aiMessageId;
|
||||
|
||||
@Schema(description = "用户消息内容")
|
||||
private String userMessage;
|
||||
|
||||
@Schema(description = "AI回复内容")
|
||||
private String aiReply;
|
||||
|
||||
@Schema(description = "消息时间戳")
|
||||
private LocalDateTime messageTimestamp;
|
||||
|
||||
@Schema(description = "会话状态")
|
||||
private String conversationStatus;
|
||||
|
||||
@Schema(description = "是否为新会话")
|
||||
private Boolean isNewConversation;
|
||||
|
||||
@Schema(description = "Coze聊天ID")
|
||||
private String cozeChatId;
|
||||
|
||||
@Schema(description = "情绪分析结果")
|
||||
private EmotionAnalysisResult emotionAnalysis;
|
||||
|
||||
@Schema(description = "Token使用情况")
|
||||
private TokenUsage tokenUsage;
|
||||
|
||||
@Schema(description = "错误信息(如果有)")
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 情绪分析结果内部类
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "情绪分析结果")
|
||||
public static class EmotionAnalysisResult {
|
||||
@Schema(description = "主要情绪")
|
||||
private String primaryEmotion;
|
||||
|
||||
@Schema(description = "情绪得分")
|
||||
private Double emotionScore;
|
||||
|
||||
@Schema(description = "置信度")
|
||||
private Double confidence;
|
||||
|
||||
@Schema(description = "情绪趋势")
|
||||
private String emotionTrend;
|
||||
}
|
||||
|
||||
/**
|
||||
* Token使用情况内部类
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Token使用情况")
|
||||
public static class TokenUsage {
|
||||
@Schema(description = "输入Token数")
|
||||
private Integer promptTokens;
|
||||
|
||||
@Schema(description = "输出Token数")
|
||||
private Integer completionTokens;
|
||||
|
||||
@Schema(description = "总Token数")
|
||||
private Integer totalTokens;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
package com.emotionmuseum.auth.controller;
|
||||
|
||||
import com.emotionmuseum.common.result.Result;
|
||||
import com.emotionmuseum.auth.dto.LoginRequest;
|
||||
import com.emotionmuseum.auth.dto.RegisterRequest;
|
||||
import com.emotionmuseum.auth.request.LoginRequest;
|
||||
import com.emotionmuseum.auth.request.RegisterRequest;
|
||||
import com.emotionmuseum.auth.service.AuthService;
|
||||
import com.emotionmuseum.auth.vo.LoginResponse;
|
||||
import com.emotionmuseum.auth.vo.UserInfoResponse;
|
||||
import com.emotionmuseum.auth.response.LoginResponse;
|
||||
import com.emotionmuseum.auth.response.UserInfoResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
package com.emotionmuseum.auth.controller;
|
||||
|
||||
import com.emotionmuseum.common.result.Result;
|
||||
import com.emotionmuseum.auth.dto.CaptchaResponse;
|
||||
import com.emotionmuseum.auth.dto.SliderCaptchaResponse;
|
||||
import com.emotionmuseum.auth.dto.SliderCaptchaVerifyRequest;
|
||||
import com.emotionmuseum.auth.response.CaptchaResponse;
|
||||
import com.emotionmuseum.auth.response.SliderCaptchaResponse;
|
||||
import com.emotionmuseum.auth.request.SliderCaptchaVerifyRequest;
|
||||
import com.emotionmuseum.auth.service.CaptchaService;
|
||||
import com.emotionmuseum.auth.service.SliderCaptchaService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.emotionmuseum.auth.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 用户登录请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "用户登录请求")
|
||||
public class LoginRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "账号(支持账号/邮箱/手机号)", example = "test_user")
|
||||
@NotBlank(message = "账号不能为空")
|
||||
private String account;
|
||||
|
||||
@Schema(description = "密码", example = "123456")
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "验证码ID", example = "captcha_123")
|
||||
@NotBlank(message = "验证码ID不能为空")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "验证码", example = "1234")
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String captcha;
|
||||
|
||||
@Schema(description = "记住我", example = "false")
|
||||
private Boolean rememberMe = false;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.emotionmuseum.auth.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 第三方登录请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "第三方登录请求")
|
||||
public class OAuthLoginRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "第三方平台类型", example = "wechat")
|
||||
@NotBlank(message = "平台类型不能为空")
|
||||
private String platform;
|
||||
|
||||
@Schema(description = "授权码", example = "auth_code_123")
|
||||
@NotBlank(message = "授权码不能为空")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "状态码", example = "state_123")
|
||||
private String state;
|
||||
|
||||
@Schema(description = "验证码ID", example = "captcha_123")
|
||||
@NotBlank(message = "验证码ID不能为空")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "验证码", example = "1234")
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String captcha;
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.emotionmuseum.auth.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 用户注册请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "用户注册请求")
|
||||
public class RegisterRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "账号", example = "test_user")
|
||||
@NotBlank(message = "账号不能为空")
|
||||
@Size(min = 4, max = 20, message = "账号长度必须在4-20位之间")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "账号只能包含字母、数字和下划线")
|
||||
private String account;
|
||||
|
||||
@Schema(description = "密码", example = "123456")
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 6, max = 20, message = "密码长度必须在6-20位之间")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "确认密码", example = "123456")
|
||||
@NotBlank(message = "确认密码不能为空")
|
||||
private String confirmPassword;
|
||||
|
||||
@Schema(description = "验证码ID", example = "captcha_123")
|
||||
@NotBlank(message = "验证码ID不能为空")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "验证码", example = "1234")
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String captcha;
|
||||
|
||||
@Schema(description = "用户名", example = "测试用户")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Size(min = 2, max = 20, message = "用户名长度必须在2-20位之间")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "邮箱", example = "test@example.com")
|
||||
@NotBlank(message = "邮箱不能为空")
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号", example = "13800138000")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "昵称", example = "小测试")
|
||||
@NotBlank(message = "昵称不能为空")
|
||||
@Size(min = 1, max = 20, message = "昵称长度必须在1-20位之间")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "生日", example = "1990-01-01")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "所在地", example = "北京市")
|
||||
@Size(max = 50, message = "所在地长度不能超过50位")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "个人简介", example = "这是一个测试用户")
|
||||
@Size(max = 200, message = "个人简介长度不能超过200位")
|
||||
private String bio;
|
||||
|
||||
/**
|
||||
* 验证密码一致性
|
||||
*/
|
||||
public boolean isPasswordMatch() {
|
||||
return password != null && password.equals(confirmPassword);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.emotionmuseum.auth.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 滑块验证码验证请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "滑块验证码验证请求")
|
||||
public class SliderCaptchaVerifyRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "验证码ID")
|
||||
@NotBlank(message = "验证码ID不能为空")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "滑块X坐标")
|
||||
@NotNull(message = "滑块X坐标不能为空")
|
||||
private Integer x;
|
||||
|
||||
@Schema(description = "滑块Y坐标")
|
||||
@NotNull(message = "滑块Y坐标不能为空")
|
||||
private Integer y;
|
||||
|
||||
@Schema(description = "滑动轨迹")
|
||||
private String track;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.emotionmuseum.auth.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 验证码响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "验证码响应")
|
||||
public class CaptchaResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "验证码ID")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "验证码图片Base64")
|
||||
private String captchaImage;
|
||||
|
||||
@Schema(description = "验证码类型", example = "arithmetic")
|
||||
private String captchaType;
|
||||
|
||||
@Schema(description = "过期时间(秒)", example = "300")
|
||||
private Long expireTime;
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.emotionmuseum.auth.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 登录响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "登录响应")
|
||||
public class LoginResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "访问Token")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(description = "刷新Token")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(description = "Token类型", example = "Bearer")
|
||||
private String tokenType = "Bearer";
|
||||
|
||||
@Schema(description = "Token过期时间(秒)", example = "86400")
|
||||
private Long expiresIn;
|
||||
|
||||
@Schema(description = "用户信息")
|
||||
private UserInfoResponse userInfo;
|
||||
|
||||
@Schema(description = "登录时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime loginTime;
|
||||
|
||||
/**
|
||||
* 用户信息响应
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "用户信息响应")
|
||||
public static class UserInfoResponse {
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "账号")
|
||||
private String account;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "头像URL")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "用户状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "最后登录时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime lastLoginTime;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.emotionmuseum.auth.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 滑块验证码响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "滑块验证码响应")
|
||||
public class SliderCaptchaResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "验证码ID")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "背景图片Base64")
|
||||
private String backgroundImage;
|
||||
|
||||
@Schema(description = "滑块图片Base64")
|
||||
private String sliderImage;
|
||||
|
||||
@Schema(description = "滑块X坐标")
|
||||
private Integer sliderX;
|
||||
|
||||
@Schema(description = "滑块Y坐标")
|
||||
private Integer sliderY;
|
||||
|
||||
@Schema(description = "过期时间(秒)")
|
||||
private Long expireTime;
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.emotionmuseum.auth.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户信息响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "用户信息响应")
|
||||
public class UserInfoResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "账号")
|
||||
private String account;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "头像URL")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "生日")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "所在地")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "个人简介")
|
||||
private String bio;
|
||||
|
||||
@Schema(description = "会员等级")
|
||||
private String memberLevel;
|
||||
|
||||
@Schema(description = "使用天数")
|
||||
private Integer totalDays;
|
||||
|
||||
@Schema(description = "成长数据")
|
||||
private GrowthStatsVO growthStats;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "是否已验证")
|
||||
private Integer isVerified;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "最后活跃时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime lastActiveTime;
|
||||
|
||||
/**
|
||||
* 成长数据VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "成长数据")
|
||||
public static class GrowthStatsVO {
|
||||
|
||||
@Schema(description = "自我感知")
|
||||
private BigDecimal selfAwareness;
|
||||
|
||||
@Schema(description = "情绪韧性")
|
||||
private BigDecimal emotionalResilience;
|
||||
|
||||
@Schema(description = "行动力")
|
||||
private BigDecimal actionPower;
|
||||
|
||||
@Schema(description = "共情力")
|
||||
private BigDecimal empathy;
|
||||
|
||||
@Schema(description = "生活热度")
|
||||
private BigDecimal lifeEnthusiasm;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.emotionmuseum.common.exception;
|
||||
|
||||
import com.emotionmuseum.common.result.ResultCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 认证异常
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Getter
|
||||
public class AuthException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private final Integer code;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
public AuthException(String message) {
|
||||
super(message);
|
||||
this.code = ResultCode.UNAUTHORIZED.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public AuthException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public AuthException(ResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
|
||||
public AuthException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = ResultCode.UNAUTHORIZED.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public AuthException(Integer code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public AuthException(ResultCode resultCode, Throwable cause) {
|
||||
super(resultCode.getMessage(), cause);
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.emotionmuseum.common.exception;
|
||||
|
||||
import com.emotionmuseum.common.result.ResultCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private final Integer code;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = ResultCode.BUSINESS_ERROR.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode, String message) {
|
||||
super(message);
|
||||
this.code = resultCode.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = ResultCode.BUSINESS_ERROR.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(Integer code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode, Throwable cause) {
|
||||
super(resultCode.getMessage(), cause);
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.emotionmuseum.common.exception;
|
||||
|
||||
import com.emotionmuseum.common.result.ResultCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 验证码异常
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Getter
|
||||
public class CaptchaException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private final Integer code;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
public CaptchaException(String message) {
|
||||
super(message);
|
||||
this.code = ResultCode.CAPTCHA_ERROR.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public CaptchaException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public CaptchaException(ResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
|
||||
public CaptchaException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = ResultCode.CAPTCHA_ERROR.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public CaptchaException(Integer code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public CaptchaException(ResultCode resultCode, Throwable cause) {
|
||||
super(resultCode.getMessage(), cause);
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package com.emotionmuseum.common.exception;
|
||||
|
||||
import com.emotionmuseum.common.result.Result;
|
||||
import com.emotionmuseum.common.result.ResultCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理认证异常
|
||||
*/
|
||||
@ExceptionHandler(AuthException.class)
|
||||
public Result<Void> handleAuthException(AuthException e, HttpServletRequest request) {
|
||||
log.warn("认证异常: {} {} - {}", request.getMethod(), request.getRequestURI(), e.getMessage());
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理令牌异常
|
||||
*/
|
||||
@ExceptionHandler(TokenException.class)
|
||||
public Result<Void> handleTokenException(TokenException e, HttpServletRequest request) {
|
||||
log.warn("令牌异常: {} {} - {}", request.getMethod(), request.getRequestURI(), e.getMessage());
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理验证码异常
|
||||
*/
|
||||
@ExceptionHandler(CaptchaException.class)
|
||||
public Result<Void> handleCaptchaException(CaptchaException e, HttpServletRequest request) {
|
||||
log.warn("验证码异常: {} {} - {}", request.getMethod(), request.getRequestURI(), e.getMessage());
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Result<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
|
||||
log.warn("业务异常: {} {} - {}", request.getMethod(), request.getRequestURI(), e.getMessage());
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数校验异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
|
||||
log.warn("参数校验失败: {} {}", request.getMethod(), request.getRequestURI(), e);
|
||||
|
||||
StringBuilder message = new StringBuilder("参数校验失败: ");
|
||||
for (FieldError error : e.getBindingResult().getFieldErrors()) {
|
||||
message.append(error.getField()).append(" ").append(error.getDefaultMessage()).append("; ");
|
||||
}
|
||||
|
||||
return Result.error(ResultCode.PARAM_VALIDATION_ERROR.getCode(), message.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理Bean校验异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleBindException(BindException e, HttpServletRequest request) {
|
||||
log.warn("参数绑定失败: {} {}", request.getMethod(), request.getRequestURI(), e);
|
||||
|
||||
StringBuilder message = new StringBuilder("参数绑定失败: ");
|
||||
for (FieldError error : e.getBindingResult().getFieldErrors()) {
|
||||
message.append(error.getField()).append(" ").append(error.getDefaultMessage()).append("; ");
|
||||
}
|
||||
|
||||
return Result.error(ResultCode.PARAM_VALIDATION_ERROR.getCode(), message.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理约束校验异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
|
||||
log.warn("约束校验失败: {} {}", request.getMethod(), request.getRequestURI(), e);
|
||||
|
||||
StringBuilder message = new StringBuilder("约束校验失败: ");
|
||||
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
|
||||
for (ConstraintViolation<?> violation : violations) {
|
||||
message.append(violation.getPropertyPath()).append(" ").append(violation.getMessage()).append("; ");
|
||||
}
|
||||
|
||||
return Result.error(ResultCode.PARAM_VALIDATION_ERROR.getCode(), message.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public Result<Void> handleIllegalArgumentException(IllegalArgumentException e, HttpServletRequest request) {
|
||||
log.warn("非法参数: {} {}", request.getMethod(), request.getRequestURI(), e);
|
||||
return Result.error(ResultCode.BAD_REQUEST.getCode(), "参数错误: " + e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理空指针异常
|
||||
*/
|
||||
@ExceptionHandler(NullPointerException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<Void> handleNullPointerException(NullPointerException e, HttpServletRequest request) {
|
||||
log.error("空指针异常: {} {}", request.getMethod(), request.getRequestURI(), e);
|
||||
return Result.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "系统内部错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
log.error("运行时异常: {} {}", request.getMethod(), request.getRequestURI(), e);
|
||||
return Result.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "系统运行异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有其他异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public Result<Void> handleException(Exception e, HttpServletRequest request) {
|
||||
log.error("未知异常: {} {}", request.getMethod(), request.getRequestURI(), e);
|
||||
return Result.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "系统异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.emotionmuseum.common.exception;
|
||||
|
||||
import com.emotionmuseum.common.result.ResultCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Token异常
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Getter
|
||||
public class TokenException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private final Integer code;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
public TokenException(String message) {
|
||||
super(message);
|
||||
this.code = ResultCode.TOKEN_INVALID.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public TokenException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public TokenException(ResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
|
||||
public TokenException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = ResultCode.TOKEN_INVALID.getCode();
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public TokenException(Integer code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public TokenException(ResultCode resultCode, Throwable cause) {
|
||||
super(resultCode.getMessage(), cause);
|
||||
this.code = resultCode.getCode();
|
||||
this.message = resultCode.getMessage();
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.emotionmuseum.common.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
/**
|
||||
* 基础分页请求类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "基础分页请求")
|
||||
public abstract class BasePageRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 页码,从1开始
|
||||
*/
|
||||
@Schema(description = "页码,从1开始", example = "1", minimum = "1")
|
||||
@Min(value = 1, message = "页码必须大于0")
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
@Schema(description = "每页大小", example = "20", minimum = "1", maximum = "100")
|
||||
@Min(value = 1, message = "每页大小必须大于0")
|
||||
@Max(value = 100, message = "每页大小不能超过100")
|
||||
private Integer pageSize = 20;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
@Schema(description = "排序字段", example = "create_time")
|
||||
private String sortField;
|
||||
|
||||
/**
|
||||
* 排序方向
|
||||
*/
|
||||
@Schema(description = "排序方向", example = "DESC", allowableValues = {"ASC", "DESC"})
|
||||
private String sortOrder = "DESC";
|
||||
|
||||
/**
|
||||
* 搜索关键词
|
||||
*/
|
||||
@Schema(description = "搜索关键词", example = "关键词")
|
||||
private String keyword;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.emotionmuseum.common.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 基础请求类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "基础请求")
|
||||
public abstract class BaseRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 请求ID,用于链路追踪
|
||||
*/
|
||||
@Schema(description = "请求ID", example = "req_123456789")
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 客户端IP地址
|
||||
*/
|
||||
@Schema(description = "客户端IP地址", example = "192.168.1.100")
|
||||
private String clientIp;
|
||||
|
||||
/**
|
||||
* 用户代理信息
|
||||
*/
|
||||
@Schema(description = "用户代理信息", example = "Mozilla/5.0...")
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* 请求时间戳
|
||||
*/
|
||||
@Schema(description = "请求时间戳", example = "1721808000000")
|
||||
private Long timestamp;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
*/
|
||||
@Schema(description = "设备类型", example = "WEB", allowableValues = {"WEB", "MOBILE", "APP"})
|
||||
private String deviceType;
|
||||
|
||||
/**
|
||||
* 应用版本
|
||||
*/
|
||||
@Schema(description = "应用版本", example = "1.0.0")
|
||||
private String appVersion;
|
||||
|
||||
public BaseRequest() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.emotionmuseum.common.response;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基础分页响应类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "基础分页响应")
|
||||
public class BasePageResponse<T> extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
@Schema(description = "当前页码", example = "1")
|
||||
private Long current;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
@Schema(description = "每页大小", example = "20")
|
||||
private Long size;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
@Schema(description = "总记录数", example = "100")
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
@Schema(description = "总页数", example = "5")
|
||||
private Long pages;
|
||||
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
@Schema(description = "数据列表")
|
||||
private List<T> records;
|
||||
|
||||
/**
|
||||
* 是否有下一页
|
||||
*/
|
||||
@Schema(description = "是否有下一页", example = "true")
|
||||
private Boolean hasNext;
|
||||
|
||||
/**
|
||||
* 是否有上一页
|
||||
*/
|
||||
@Schema(description = "是否有上一页", example = "false")
|
||||
private Boolean hasPrevious;
|
||||
|
||||
public BasePageResponse() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BasePageResponse(IPage<T> page) {
|
||||
super();
|
||||
this.current = page.getCurrent();
|
||||
this.size = page.getSize();
|
||||
this.total = page.getTotal();
|
||||
this.pages = page.getPages();
|
||||
this.records = page.getRecords();
|
||||
this.hasNext = page.hasNext();
|
||||
this.hasPrevious = page.hasPrevious();
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态工厂方法
|
||||
*/
|
||||
public static <T> BasePageResponse<T> of(IPage<T> page) {
|
||||
return new BasePageResponse<>(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态工厂方法
|
||||
*/
|
||||
public static <T> BasePageResponse<T> of(List<T> records, Long current, Long size, Long total) {
|
||||
BasePageResponse<T> response = new BasePageResponse<>();
|
||||
response.setRecords(records);
|
||||
response.setCurrent(current);
|
||||
response.setSize(size);
|
||||
response.setTotal(total);
|
||||
response.setPages((total + size - 1) / size);
|
||||
response.setHasNext(current < response.getPages());
|
||||
response.setHasPrevious(current > 1);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.emotionmuseum.common.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 基础响应类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Schema(description = "基础响应")
|
||||
public abstract class BaseResponse implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 响应时间戳
|
||||
*/
|
||||
@Schema(description = "响应时间戳", example = "1721808000000")
|
||||
private Long timestamp;
|
||||
|
||||
/**
|
||||
* 请求ID,用于链路追踪
|
||||
*/
|
||||
@Schema(description = "请求ID", example = "req_123456789")
|
||||
private String requestId;
|
||||
|
||||
/**
|
||||
* 服务器处理时间(毫秒)
|
||||
*/
|
||||
@Schema(description = "服务器处理时间(毫秒)", example = "150")
|
||||
private Long processingTime;
|
||||
|
||||
/**
|
||||
* 服务节点标识
|
||||
*/
|
||||
@Schema(description = "服务节点标识", example = "node-001")
|
||||
private String serverNode;
|
||||
|
||||
public BaseResponse() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求ID
|
||||
*/
|
||||
public BaseResponse requestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置处理时间
|
||||
*/
|
||||
public BaseResponse processingTime(Long processingTime) {
|
||||
this.processingTime = processingTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置服务节点
|
||||
*/
|
||||
public BaseResponse serverNode(String serverNode) {
|
||||
this.serverNode = serverNode;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+56
@@ -127,4 +127,60 @@ public class Result<T> implements Serializable {
|
||||
this.requestId = requestId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权响应
|
||||
*/
|
||||
public static <T> Result<T> unauthorized() {
|
||||
return new Result<>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权响应(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> unauthorized(String message) {
|
||||
return new Result<>(ResultCode.UNAUTHORIZED.getCode(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止访问响应
|
||||
*/
|
||||
public static <T> Result<T> forbidden() {
|
||||
return new Result<>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止访问响应(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> forbidden(String message) {
|
||||
return new Result<>(ResultCode.FORBIDDEN.getCode(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求参数错误响应
|
||||
*/
|
||||
public static <T> Result<T> badRequest() {
|
||||
return new Result<>(ResultCode.BAD_REQUEST.getCode(), ResultCode.BAD_REQUEST.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求参数错误响应(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> badRequest(String message) {
|
||||
return new Result<>(ResultCode.BAD_REQUEST.getCode(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源不存在响应
|
||||
*/
|
||||
public static <T> Result<T> notFound() {
|
||||
return new Result<>(ResultCode.NOT_FOUND.getCode(), ResultCode.NOT_FOUND.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源不存在响应(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> notFound(String message) {
|
||||
return new Result<>(ResultCode.NOT_FOUND.getCode(), message);
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.emotionmuseum.record.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.DecimalMax;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建情绪记录请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "创建情绪记录请求")
|
||||
public class CreateEmotionRecordRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID", example = "user_123")
|
||||
@NotBlank(message = "用户ID不能为空")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "记录日期", example = "2025-07-24")
|
||||
@NotNull(message = "记录日期不能为空")
|
||||
private LocalDate recordDate;
|
||||
|
||||
@Schema(description = "情绪类型", example = "joy")
|
||||
@NotBlank(message = "情绪类型不能为空")
|
||||
private String emotionType;
|
||||
|
||||
@Schema(description = "情绪强度", example = "0.8")
|
||||
@NotNull(message = "情绪强度不能为空")
|
||||
@DecimalMin(value = "0.0", message = "情绪强度不能小于0")
|
||||
@DecimalMax(value = "1.0", message = "情绪强度不能大于1")
|
||||
private Double intensity;
|
||||
|
||||
@Schema(description = "触发因素", example = "完成了重要项目")
|
||||
private String triggers;
|
||||
|
||||
@Schema(description = "详细描述", example = "今天心情很好,完成了一个重要的项目")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "标签列表", example = "[\"工作\", \"成就感\"]")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "天气情况", example = "晴天")
|
||||
private String weather;
|
||||
|
||||
@Schema(description = "所在位置", example = "办公室")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "当时活动", example = "工作")
|
||||
private String activity;
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.emotionmuseum.record.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 情绪记录响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "情绪记录响应")
|
||||
public class EmotionRecordResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "记录ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "记录日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate recordDate;
|
||||
|
||||
@Schema(description = "情绪类型")
|
||||
private String emotionType;
|
||||
|
||||
@Schema(description = "情绪强度")
|
||||
private Double intensity;
|
||||
|
||||
@Schema(description = "触发因素")
|
||||
private String triggers;
|
||||
|
||||
@Schema(description = "详细描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "标签列表")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "天气情况")
|
||||
private String weather;
|
||||
|
||||
@Schema(description = "所在位置")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "当时活动")
|
||||
private String activity;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
package com.emotionmuseum.user.controller;
|
||||
|
||||
import com.emotionmuseum.common.result.Result;
|
||||
import com.emotionmuseum.user.dto.UserUpdateRequest;
|
||||
import com.emotionmuseum.user.request.UserUpdateRequest;
|
||||
import com.emotionmuseum.user.service.UserService;
|
||||
import com.emotionmuseum.user.vo.UserInfoResponse;
|
||||
import com.emotionmuseum.user.response.UserInfoResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.emotionmuseum.user.request;
|
||||
|
||||
import com.emotionmuseum.common.request.BaseRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 用户信息更新请求
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "用户信息更新请求")
|
||||
public class UserUpdateRequest extends BaseRequest {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户名", example = "新用户名")
|
||||
@Size(min = 2, max = 20, message = "用户名长度必须在2-20位之间")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "邮箱", example = "new@example.com")
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号", example = "13900139000")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "头像URL", example = "https://example.com/avatar.jpg")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "昵称", example = "新昵称")
|
||||
@Size(min = 1, max = 20, message = "昵称长度必须在1-20位之间")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "生日", example = "1990-01-01")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "所在地", example = "上海市")
|
||||
@Size(max = 50, message = "所在地长度不能超过50位")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "个人简介", example = "更新后的个人简介")
|
||||
@Size(max = 200, message = "个人简介长度不能超过200位")
|
||||
private String bio;
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.emotionmuseum.user.response;
|
||||
|
||||
import com.emotionmuseum.common.response.BaseResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户信息响应
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @since 2025-07-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "用户信息响应")
|
||||
public class UserInfoResponse extends BaseResponse {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "账号")
|
||||
private String account;
|
||||
|
||||
@Schema(description = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "手机号")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "头像URL")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "生日")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate birthDate;
|
||||
|
||||
@Schema(description = "所在地")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "个人简介")
|
||||
private String bio;
|
||||
|
||||
@Schema(description = "会员等级")
|
||||
private String memberLevel;
|
||||
|
||||
@Schema(description = "使用天数")
|
||||
private Integer totalDays;
|
||||
|
||||
@Schema(description = "成长数据")
|
||||
private GrowthStatsVO growthStats;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "是否已验证")
|
||||
private Integer isVerified;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "最后活跃时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime lastActiveTime;
|
||||
|
||||
/**
|
||||
* 成长数据VO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "成长数据")
|
||||
public static class GrowthStatsVO {
|
||||
|
||||
@Schema(description = "自我感知")
|
||||
private BigDecimal selfAwareness;
|
||||
|
||||
@Schema(description = "情绪韧性")
|
||||
private BigDecimal emotionalResilience;
|
||||
|
||||
@Schema(description = "行动力")
|
||||
private BigDecimal actionPower;
|
||||
|
||||
@Schema(description = "共情力")
|
||||
private BigDecimal empathy;
|
||||
|
||||
@Schema(description = "生活热度")
|
||||
private BigDecimal lifeEnthusiasm;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user