优化并移除不再有任何用处的文件
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
---
|
||||
inclusion: always
|
||||
---
|
||||
# 项目开发规则
|
||||
|
||||
## 基础设置
|
||||
|
||||
1. 保持对话语言为中文
|
||||
2. 不允许在未经允许的情况下删除代码和文件,不允许破坏已经正常的业务代码
|
||||
3. 执行终端命令时要关注执行情况,避免无效等待
|
||||
|
||||
## 代码规范
|
||||
|
||||
4. 生成代码时必须添加类级和函数级注释
|
||||
5. 使用import导包,禁止使用全限定名称引用类
|
||||
6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型,禁止以枚举类作为方法的入参,确保接口的通用性和可扩展性
|
||||
7. 新增数据的id使用已存在的雪花算法生成器生成
|
||||
|
||||
## 架构规范
|
||||
|
||||
8. 所有开发必须遵循当前项目规范
|
||||
9. Controller层禁止添加业务逻辑
|
||||
10. 使用全局异常处理,禁止使用try-catch
|
||||
11. 前端接口访问尽可能走网关调用
|
||||
|
||||
## 接口设计规范
|
||||
|
||||
12. Controller层接口定义要完整:
|
||||
- 入参使用request封装传递到service层
|
||||
- service层的方法命名与controller层定义的接口的方法名称保持一致
|
||||
- 出参使用response封装由service层传递到controller层
|
||||
- 禁止在controller层做entity/domain对象与request/response的转换
|
||||
- 使用项目已有的Result做接口返回
|
||||
13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装
|
||||
14. Controller层路由禁止添加/api前缀
|
||||
15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空,必须明确指定value属性值且使用驼峰结构命名,避免使用下划线分隔符。所有接口注解必须显式指定value属性,如@GetMapping(value = "/page")、@PostMapping(value = "/create")等,禁止使用@GetMapping()或@PostMapping等空注解形式
|
||||
16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息
|
||||
17. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误
|
||||
18. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性
|
||||
19. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等
|
||||
20. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List
|
||||
21. 接口路径应避免层级过深,建议不超过3级路径结构
|
||||
22. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中
|
||||
23. Controller层接口应保持简洁,避免为特定字段创建独立的更新方法(如updateStatus等),应只保留一个通用的update方法,具体的业务逻辑在Service层实现
|
||||
24. Service层在执行更新操作时,应对每个字段进行空值检查,只更新非空字段,避免空字段覆盖原有值,确保数据完整性
|
||||
25. Controller层应避免创建多个特定条件的查询接口,只保留一个分页查询方法,通过在请求对象中包含所有可查询字段来支持不同的查询需求
|
||||
26. 分页查询接口应支持模糊查询和精确查询,通过在Service层根据字段类型和业务需求判断查询方式
|
||||
|
||||
## 环境配置
|
||||
|
||||
27. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择
|
||||
28. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置
|
||||
|
||||
## 数据库规范
|
||||
|
||||
29. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段
|
||||
30. 删除操作优先使用逻辑删除,添加deleted字段标识
|
||||
31. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名
|
||||
32. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名
|
||||
33. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全
|
||||
34. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰
|
||||
35. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误
|
||||
|
||||
## 安全规范
|
||||
|
||||
36. 所有外部输入必须进行参数校验
|
||||
37. 敏感信息不得在日志中输出
|
||||
38. 数据库操作必须使用参数化查询,防止SQL注入
|
||||
|
||||
## 性能规范
|
||||
|
||||
39. 避免N+1查询问题,合理使用批量查询
|
||||
40. 大数据量查询必须分页处理
|
||||
41. 缓存策略要考虑数据一致性问题
|
||||
|
||||
## 日志规范
|
||||
|
||||
42. 关键业务操作必须记录操作日志
|
||||
43. 异常信息要包含足够的上下文信息
|
||||
44. 生产环境禁止输出debug级别日志
|
||||
|
||||
---
|
||||
|
||||
## Java Spring Boot 项目开发与代码质量保障规范(扩展)
|
||||
|
||||
适用范围:本规范适用于 logistics-finance 项目所有后端模块(common、gateway、auth、user、order、waybill、vehicle、finance、report、ai、file)。若与本文件前文条款或现有项目规范冲突,以更严格者为准。
|
||||
|
||||
### 一、代码规范完善
|
||||
|
||||
- 编码标准
|
||||
- 严格开启编译参数校验与空指针警告,禁止忽略 IDE/编译器提示。
|
||||
- 代码提交前必须通过本地编译、单元测试、静态扫描(如存在)。
|
||||
- 严禁出现魔法值:使用常量或配置项;跨模块常量放在 common 模块统一管理。
|
||||
- 日志分级:业务关键信息用 info,调试用 debug(生产禁用),异常用 error,禁止打印敏感信息(密码、密钥、Token、手机号等)。
|
||||
|
||||
- 命名规范
|
||||
- 类:PascalCase;方法/变量:camelCase;常量:UPPER_SNAKE_CASE;包名全小写。
|
||||
- 接口路径参照现有规则(禁止 /api 前缀、禁止 PathVariable、Mapping value 必须显式且驼峰命名)。
|
||||
|
||||
- 注释规范
|
||||
- 类注释需包含:职责/用途、作者、创建时间、版本;方法注释需包含:用途、参数、返回、可能抛出的异常。
|
||||
- 复杂算法/易错逻辑必须添加行内注释;废弃方法使用 @Deprecated 并标注替代方案与移除计划。
|
||||
|
||||
- 代码结构规范(建议包结构)
|
||||
- controller、service、service.impl、repository(mapper/dao)、domain/entity、converter、config、client(feign)、event、facade、task、util、constant。
|
||||
- DTO/Request/Response 与 Entity 分离;Controller 不做对象互转,统一在 Service/Converter 层完成。
|
||||
|
||||
### 二、分层架构规范
|
||||
|
||||
- Controller 层职责
|
||||
- 接收请求、参数校验(Jakarta Validation)、鉴权校验(由网关/拦截器)、调用 Service,统一使用 `Result<T>` 返回。
|
||||
- 禁止:业务逻辑、事务控制、数据访问、实体与请求/响应的相互转换。
|
||||
|
||||
- Service 层职责
|
||||
- 聚合业务逻辑、领域编排、事务边界控制(@Transactional,读写分离时仅在写方法上标注),幂等与重试策略在此实现。
|
||||
- 方法命名与 Controller 对应,入参使用 Request 对象,出参使用 Response 对象。
|
||||
|
||||
- Repository 层职责
|
||||
- 仅做数据访问,统一使用 MyBatis-Plus(优先 LambdaQueryWrapper,避免硬编码字段)。
|
||||
|
||||
- Entity 规范
|
||||
- 统一继承 BaseEntity,字段包含:created_by、create_time、updated_by、update_time、logical_delete、remark(与项目约定一致)。
|
||||
- 禁止使用枚举作为实体/DTO 字段类型;以 String/Integer 存储并在注释中补充取值说明。
|
||||
|
||||
### 三、统一异常与返回结果
|
||||
|
||||
- 全局异常处理
|
||||
- 继续使用 common-web 的 GlobalExceptionHandler;禁止在业务代码中大面积 try-catch,异常由全局处理。
|
||||
- 业务异常一律抛出 BusinessException;校验异常统一转为 400 语义返回。
|
||||
|
||||
- 统一返回结构
|
||||
- 统一使用 common-core 的 Result<T> 与 ResultCode;保证 traceId、timestamp 一致性。
|
||||
- Controller 层仅返回 Result,不直接返回实体或集合。
|
||||
|
||||
- 参数校验
|
||||
- 使用 jakarta.validation 注解(@NotNull、@Size 等),在 Request 对象上标注;Controller 使用 @Validated。
|
||||
|
||||
### 四、数据库操作规范
|
||||
|
||||
- 设计规范
|
||||
- 数据表必须包含 create_time、update_time;删除优先逻辑删除 logical_delete;字段命名下划线风格。
|
||||
- 建议仅创建必要索引;避免外键约束(由应用层保证一致性)。
|
||||
|
||||
- 查询构建
|
||||
- 优先使用 LambdaQueryWrapper 和链式调用;分页查询必须使用分页插件;大数据量必须分页。
|
||||
- 避免 N+1 查询;需要时使用批量查询或联表映射。
|
||||
|
||||
- 事务管理
|
||||
- 仅在 Service 层声明事务;方法内仅包含数据库操作或与数据库一致性相关的远程调用;跨服务一致性使用可靠消息/补偿方案。
|
||||
|
||||
---
|
||||
|
||||
## 二、代码质量保障机制
|
||||
|
||||
- 代码审查流程(Code Review)
|
||||
- 所有变更必须走 PR;至少 1 名同组开发+1 名模块 Owner 审核通过方可合并。
|
||||
- 审查清单:命名/注释/分层边界/异常处理/日志/性能/安全/接口兼容性/测试覆盖率/静态扫描告警。
|
||||
- PR 模板需包含:改动描述、影响面、回归范围、测试说明、回滚方案。
|
||||
|
||||
- 静态代码分析与格式
|
||||
- 集成 Checkstyle(代码风格)、SpotBugs/PMD(潜在缺陷)、SonarQube(质量门禁),EditorConfig/格式化工具保持一致风格。
|
||||
- 质量门禁建议:
|
||||
- 新增代码覆盖率 >= 80%,全局覆盖率 >= 70%;
|
||||
- Blocker/Critical 问题为 0;
|
||||
- 重复代码率 < 3%;
|
||||
- 圈复杂度阈值:方法 < 10,类 < 80。
|
||||
|
||||
- 测试规范
|
||||
- 单元测试:命名 {ClassName}Test,方法名 should_xxx_when_xxx;使用 Mockito/AssertJ;隔离外部依赖。
|
||||
- 集成测试:使用 @SpringBootTest,测试容器或嵌入式组件替代真实依赖;构造典型场景与边界场景。
|
||||
- 覆盖率准则:核心业务与关键分支必须覆盖(正常/异常/边界/空数据)。
|
||||
- 用例分层:Service 层单测覆盖业务规则;Controller 层使用 MockMvc;Repository 层覆盖复杂 SQL 与多条件查询。
|
||||
|
||||
- 重构指导原则
|
||||
- 小步快跑、单一职责、先加测试再重构;保持对外行为一致(新增特性使用特性开关/版本控制)。
|
||||
- 严禁大规模跨模块重构合并在单次 PR;拆分为多个可审查的独立提交。
|
||||
|
||||
---
|
||||
|
||||
## 三、变更管理流程
|
||||
|
||||
- 变更前影响分析
|
||||
- 维度:接口兼容性(路径/入参/语义)、数据库(表/字段/索引/数据迁移)、配置(Nacos/环境变量)、依赖版本、性能、容量与并发、安全与合规、可回滚性。
|
||||
- 产出:影响分析文档与回滚方案(包含回滚脚本/开关)。
|
||||
|
||||
- 回归测试策略
|
||||
- 冒烟覆盖主干流程;模块级回归覆盖变更影响域;跨服务联调验证接口契约(通过网关)。
|
||||
- 回归范围基于依赖关系与影响分析确定;优先自动化回归,必要时补充手工用例。
|
||||
|
||||
- 版本管理与发布
|
||||
- 分支策略建议:
|
||||
- 主分支:master;
|
||||
- 功能分支:feature/{module}-{short-desc};
|
||||
- 预发布:release/x.y.z;
|
||||
- 紧急修复:hotfix/x.y.z;
|
||||
- 版本号:语义化版本 SemVer(MAJOR.MINOR.PATCH),发布时打 Tag;所有服务端口遵循 280xx 约定(配置文件统一)。
|
||||
|
||||
- 紧急修复与常规开发
|
||||
- 紧急修复:创建 hotfix → 修复 → 快速测试 → 合并 master 与 release → 立即发布 → 追补测试与文档。
|
||||
- 常规开发:feature → PR 审核 → 合并 → 集成测试 → 发布。
|
||||
|
||||
---
|
||||
|
||||
## 四、持续改进机制
|
||||
|
||||
- 规范评审与更新
|
||||
- 每月一次质量例会,评审新增问题与改进项;规范变更需在本文件留痕(版本/日期/变更点)。
|
||||
|
||||
- 质量指标监控(建议在 Sonar/日志/监控平台看板化)
|
||||
- 构建成功率、静态问题趋势、单元测试覆盖率、平均修复时长、缺陷密度、接口成功率、P95/P99 响应时间、数据库慢查询比例。
|
||||
|
||||
- 团队培训计划
|
||||
- 新人入项必读规范与代码演练;关键模块轮训;每季度至少一次专题分享(性能调优/安全加固/重构实践)。
|
||||
|
||||
- 反馈闭环
|
||||
- 通过代码评审、事后复盘(Postmortem)、问题工单收敛到规范条款;指定责任人与截止时间;下次评审验证落地结果。
|
||||
|
||||
---
|
||||
|
||||
## 附:标准检查清单(节选)
|
||||
|
||||
- PR 自检
|
||||
- 命名/注释达标;无魔法值;日志分级正确且无敏感信息;空指针风险已处理;删除代码/文件已获批准。
|
||||
- Controller 不含业务逻辑;路由命名与网关规范一致;统一 Result 返回;参数校验齐全;不使用 PathVariable。
|
||||
- Service 事务边界清晰;仅更新非空字段;分页/批量/幂等校验到位。
|
||||
- Repository 使用 LambdaQueryWrapper;避免硬编码字段;SQL 有必要索引;避免 N+1。
|
||||
- 测试覆盖关键路径与边界;新增功能附带测试;CI 静态扫描无阻断级问题。
|
||||
|
||||
- 回滚准备
|
||||
- 是否具备配置开关/灰度发布策略;是否提供回滚脚本;数据库迁移是否可逆(或给出补偿方案)。
|
||||
Vendored
+2
-1
@@ -8,5 +8,6 @@
|
||||
"Command Prompt": {
|
||||
"args": ["/K", "chcp 65001"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"kiroAgent.configureMCP": "Disabled"
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.emotion.dto.request.AiSummaryRequest;
|
||||
import com.emotion.dto.request.GuestChatRequest;
|
||||
import com.emotion.dto.request.ConversationCreateRequest;
|
||||
import com.emotion.dto.request.ChatStatsRequest;
|
||||
import com.emotion.dto.request.GuestUserInfoRequest;
|
||||
import com.emotion.dto.response.AiChatResponse;
|
||||
import com.emotion.dto.response.AiSummaryResponse;
|
||||
import com.emotion.dto.response.AiStatusResponse;
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.emotion.dto.request.EmotionRecordPageRequest;
|
||||
import com.emotion.dto.request.EmotionRecordUpdateRequest;
|
||||
import com.emotion.dto.response.EmotionRecordResponse;
|
||||
import com.emotion.service.EmotionRecordService;
|
||||
import com.emotion.util.CurrentUserUtil;
|
||||
import com.emotion.util.UserContextUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -41,7 +41,7 @@ public class EmotionRecordController {
|
||||
log.info("创建情绪记录: userId={}", request.getUserId());
|
||||
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
request.setUserId(userId);
|
||||
|
||||
EmotionRecordResponse response = emotionRecordService.createEmotionRecordWithResponse(request);
|
||||
@@ -54,7 +54,7 @@ public class EmotionRecordController {
|
||||
@GetMapping(value = "/page")
|
||||
public Result<PageResult<EmotionRecordResponse>> getPage(@Validated EmotionRecordPageRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
log.info("分页查询情绪记录: userId={}, current={}, size={}", userId, request.getCurrent(), request.getSize());
|
||||
|
||||
@@ -117,7 +117,7 @@ public class EmotionRecordController {
|
||||
@RequestParam(required = false) String endDate) {
|
||||
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
log.info("获取情绪统计: userId={}, startDate={}, endDate={}", userId, startDate, endDate);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.emotion.dto.request.EmotionSummaryStatusRequest;
|
||||
import com.emotion.dto.response.EmotionSummaryGenerateResponse;
|
||||
import com.emotion.dto.response.EmotionSummaryStatusResponse;
|
||||
import com.emotion.service.AiChatService;
|
||||
import com.emotion.util.CurrentUserUtil;
|
||||
import com.emotion.util.UserContextUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -38,7 +38,7 @@ public class EmotionSummaryController {
|
||||
@PostMapping(value = "/generate")
|
||||
public Result<EmotionSummaryGenerateResponse> generateEmotionSummary(
|
||||
@RequestBody @Valid EmotionSummaryGenerateRequest request) {
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
log.info("收到生成情绪记录总结请求: userId={}", userId);
|
||||
|
||||
// 调用AI服务生成情绪总结
|
||||
@@ -61,7 +61,7 @@ public class EmotionSummaryController {
|
||||
public Result<EmotionSummaryStatusResponse> getEmotionSummaryStatus(
|
||||
@Validated EmotionSummaryStatusRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
log.info("查询用户情绪记录总结状态: userId={}", userId);
|
||||
|
||||
// 调用AI服务获取状态信息
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.emotion.dto.response.MessageResponse;
|
||||
import com.emotion.entity.Message;
|
||||
import com.emotion.mapper.MessageMapper;
|
||||
import com.emotion.service.MessageService;
|
||||
import com.emotion.util.CurrentUserUtil;
|
||||
import com.emotion.util.UserContextUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -53,7 +53,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
||||
@Override
|
||||
public PageResult<MessageResponse> getPageWithResponse(MessagePageRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
// 调用用户消息分页查询方法
|
||||
PageResult<MessageResponse> pageResult = getUserMessagesWithPage(request);
|
||||
@@ -229,7 +229,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
||||
@Override
|
||||
public PageResult<MessageResponse> getUserMessagesWithPage(MessagePageRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
// 调用原有的分页查询方法
|
||||
IPage<Message> page = getByUserIdWithPage(userId, Math.toIntExact(request.getCurrent()),
|
||||
@@ -254,7 +254,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
||||
@Override
|
||||
public List<MessageResponse> searchUserMessages(MessageSearchRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
// 调用原有的搜索方法
|
||||
List<Message> messages = searchByUserIdAndKeyword(userId, request.getKeyword(), request.getLimit());
|
||||
@@ -268,7 +268,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
||||
@Override
|
||||
public List<MessageResponse> getUserRecentMessages(MessageRecentRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
// 调用原有的获取最近消息方法
|
||||
List<Message> messages = getRecentByUserId(userId, request.getLimit());
|
||||
@@ -282,7 +282,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
||||
@Override
|
||||
public MessageResponse createMessageFromRequest(MessageCreateRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
// 构建消息对象
|
||||
Message message = new Message();
|
||||
@@ -313,7 +313,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
||||
@Override
|
||||
public PageResult<MessageResponse> searchWithResponse(MessageSearchRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
// 构建分页对象
|
||||
Page<Message> page = new Page<>(1L, request.getLimit().longValue());
|
||||
@@ -344,7 +344,7 @@ public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> impl
|
||||
@Override
|
||||
public PageResult<MessageResponse> getRecentWithResponse(MessageRecentRequest request) {
|
||||
// 从上下文中获取当前用户ID
|
||||
String userId = CurrentUserUtil.requireCurrentUserId();
|
||||
String userId = UserContextUtils.requireCurrentUserId();
|
||||
|
||||
// 构建分页对象
|
||||
Page<Message> page = new Page<>(1L, request.getLimit().longValue());
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package com.emotion.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 当前用户工具类
|
||||
* 提供便捷的方法获取当前登录用户信息
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-25
|
||||
*/
|
||||
@Slf4j
|
||||
public class CurrentUserUtil {
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*
|
||||
* @return 当前用户ID,如果未登录则返回null
|
||||
*/
|
||||
public static String getCurrentUserId() {
|
||||
return UserContextHolder.getCurrentUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户名
|
||||
*
|
||||
* @return 当前用户名,如果未登录则返回null
|
||||
*/
|
||||
public static String getCurrentUsername() {
|
||||
return UserContextHolder.getCurrentUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户Token
|
||||
*
|
||||
* @return 当前用户Token,如果未登录则返回null
|
||||
*/
|
||||
public static String getCurrentToken() {
|
||||
return UserContextHolder.getCurrentToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前是否有用户登录
|
||||
*
|
||||
* @return 是否有用户登录
|
||||
*/
|
||||
public static boolean isUserLoggedIn() {
|
||||
return UserContextHolder.hasUserContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID,如果未登录则抛出异常
|
||||
*
|
||||
* @return 当前用户ID
|
||||
* @throws IllegalStateException 如果用户未登录
|
||||
*/
|
||||
public static String requireCurrentUserId() {
|
||||
String userId = getCurrentUserId();
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
throw new IllegalStateException("用户未登录或认证失败");
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户名,如果未登录则抛出异常
|
||||
*
|
||||
* @return 当前用户名
|
||||
* @throws IllegalStateException 如果用户未登录
|
||||
*/
|
||||
public static String requireCurrentUsername() {
|
||||
String username = getCurrentUsername();
|
||||
if (username == null || username.trim().isEmpty()) {
|
||||
throw new IllegalStateException("用户未登录或认证失败");
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户上下文摘要信息
|
||||
*
|
||||
* @return 用户上下文摘要
|
||||
*/
|
||||
public static String getContextSummary() {
|
||||
return UserContextHolder.getContextSummary();
|
||||
}
|
||||
}
|
||||
Generated
-445
@@ -1,445 +0,0 @@
|
||||
{
|
||||
"name": "emotion-museun",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3",
|
||||
"vue-tsc": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.0.tgz",
|
||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz",
|
||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.20",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.20.tgz",
|
||||
"integrity": "sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@volar/source-map": "2.4.20"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/source-map": {
|
||||
"version": "2.4.20",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.20.tgz",
|
||||
"integrity": "sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@volar/typescript": {
|
||||
"version": "2.4.20",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.20.tgz",
|
||||
"integrity": "sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.20",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz",
|
||||
"integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@vue/shared": "3.5.18",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz",
|
||||
"integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.18",
|
||||
"@vue/shared": "3.5.18"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-vue2": {
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
|
||||
"integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.4.tgz",
|
||||
"integrity": "sha512-BvueED4LfBCSNH66eeUQk37MQCb7hjdezzGgxniM0LbriW53AJIyLorgshAtStmjfsAuOCcTl/c1b+nz/ye8xQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.20",
|
||||
"@vue/compiler-dom": "^3.5.0",
|
||||
"@vue/compiler-vue2": "^2.7.16",
|
||||
"@vue/shared": "^3.5.0",
|
||||
"alien-signals": "^2.0.5",
|
||||
"muggle-string": "^0.4.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz",
|
||||
"integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.5.tgz",
|
||||
"integrity": "sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
|
||||
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.4.tgz",
|
||||
"integrity": "sha512-kZmSEjGtROApVBuaIcoprrXZsFNGon5ggkTJokmhQ/H1hMzCFRPQ0Ed8IHYFsmYJYvHBcdmEQVGVcRuxzPzNbw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@volar/typescript": "2.4.20",
|
||||
"@vue/language-core": "3.0.4"
|
||||
},
|
||||
"bin": {
|
||||
"vue-tsc": "bin/vue-tsc.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.0.tgz",
|
||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.28.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz",
|
||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1"
|
||||
}
|
||||
},
|
||||
"@volar/language-core": {
|
||||
"version": "2.4.20",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.20.tgz",
|
||||
"integrity": "sha512-dRDF1G33xaAIDqR6+mXUIjXYdu9vzSxlMGfMEwBxQsfY/JMUEXSpLTR057oTKlUQ2nIvCmP9k94A8h8z2VrNSA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@volar/source-map": "2.4.20"
|
||||
}
|
||||
},
|
||||
"@volar/source-map": {
|
||||
"version": "2.4.20",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.20.tgz",
|
||||
"integrity": "sha512-mVjmFQH8mC+nUaVwmbxoYUy8cww+abaO8dWzqPUjilsavjxH0jCJ3Mp8HFuHsdewZs2c+SP+EO7hCd8Z92whJg==",
|
||||
"dev": true
|
||||
},
|
||||
"@volar/typescript": {
|
||||
"version": "2.4.20",
|
||||
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.20.tgz",
|
||||
"integrity": "sha512-Oc4DczPwQyXcVbd+5RsNEqX6ia0+w3p+klwdZQ6ZKhFjWoBP9PCPQYlKYRi/tDemWphW93P/Vv13vcE9I9D2GQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@volar/language-core": "2.4.20",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-core": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz",
|
||||
"integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@vue/shared": "3.5.18",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-dom": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz",
|
||||
"integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@vue/compiler-core": "3.5.18",
|
||||
"@vue/shared": "3.5.18"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-vue2": {
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
|
||||
"integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@vue/language-core": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.4.tgz",
|
||||
"integrity": "sha512-BvueED4LfBCSNH66eeUQk37MQCb7hjdezzGgxniM0LbriW53AJIyLorgshAtStmjfsAuOCcTl/c1b+nz/ye8xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@volar/language-core": "2.4.20",
|
||||
"@vue/compiler-dom": "^3.5.0",
|
||||
"@vue/compiler-vue2": "^2.7.16",
|
||||
"@vue/shared": "^3.5.0",
|
||||
"alien-signals": "^2.0.5",
|
||||
"muggle-string": "^0.4.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"picomatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"@vue/shared": {
|
||||
"version": "3.5.18",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz",
|
||||
"integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
|
||||
"dev": true
|
||||
},
|
||||
"alien-signals": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.5.tgz",
|
||||
"integrity": "sha512-PdJB6+06nUNAClInE3Dweq7/2xVAYM64vvvS1IHVHSJmgeOtEdrAGyp7Z2oJtYm0B342/Exd2NT0uMJaThcjLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz",
|
||||
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
|
||||
"dev": true
|
||||
},
|
||||
"entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true
|
||||
},
|
||||
"estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true
|
||||
},
|
||||
"muggle-string": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
|
||||
"dev": true
|
||||
},
|
||||
"path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
|
||||
"dev": true
|
||||
},
|
||||
"vue-tsc": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.0.4.tgz",
|
||||
"integrity": "sha512-kZmSEjGtROApVBuaIcoprrXZsFNGon5ggkTJokmhQ/H1hMzCFRPQ0Ed8IHYFsmYJYvHBcdmEQVGVcRuxzPzNbw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@volar/typescript": "2.4.20",
|
||||
"@vue/language-core": "3.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"typescript": "^5.8.3",
|
||||
"vue-tsc": "^3.0.4"
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
# 情绪博物馆后端重构项目
|
||||
|
||||
## 项目概述
|
||||
|
||||
本项目是基于Spring Boot 3.4.8的情绪博物馆后端服务重构版本,从原有的Spring Boot 2.7.18升级而来。
|
||||
|
||||
## 重构进度
|
||||
|
||||
### 第一阶段:基础环境升级 ✅
|
||||
|
||||
#### 已完成的工作
|
||||
|
||||
1. **项目结构创建** ✅
|
||||
- 创建了标准的Maven项目结构
|
||||
- 配置了src/main/java和src/main/resources目录
|
||||
|
||||
2. **Maven配置** ✅
|
||||
- 创建了pom.xml文件
|
||||
- 配置了Spring Boot 3.4.8作为父项目
|
||||
- 添加了所有必要的依赖:
|
||||
- Spring Boot Starters (Web, Security, WebSocket, Redis, Validation, Actuator)
|
||||
- MyBatis-Plus 3.5.5
|
||||
- JWT 0.12.3
|
||||
- SpringDoc OpenAPI 3
|
||||
- Hutool 5.8.25
|
||||
- Lombok
|
||||
|
||||
3. **配置文件** ✅
|
||||
- application.yml (主配置文件)
|
||||
- application-local.yml (本地环境配置)
|
||||
- 配置了数据库连接、Redis、日志等
|
||||
|
||||
4. **基础配置类** ✅
|
||||
- SecurityConfig (Spring Security 6.x配置)
|
||||
- MybatisPlusConfig (MyBatis-Plus配置)
|
||||
- RedisConfig (Redis配置)
|
||||
- AsyncConfig (异步配置)
|
||||
- OpenApiConfig (OpenAPI配置)
|
||||
- WebClientConfig (HTTP客户端配置)
|
||||
- CozeConfig (Coze API配置)
|
||||
|
||||
5. **主启动类** ✅
|
||||
- EmotionMuseumApplication.java
|
||||
- 配置了组件扫描、缓存、异步、事务管理
|
||||
|
||||
6. **基础控制器** ✅
|
||||
- HealthController (健康检查接口)
|
||||
|
||||
#### 技术栈升级
|
||||
|
||||
- **Spring Boot**: 2.7.18 → 3.4.8 ✅
|
||||
- **Java版本**: JDK 17 (当前使用,计划升级到JDK 21)
|
||||
- **Spring Security**: 5.x → 6.x ✅
|
||||
- **MyBatis-Plus**: 3.5.3.1 → 3.5.5 ✅
|
||||
- **JWT**: 0.11.5 → 0.12.3 ✅
|
||||
- **API文档**: Swagger → SpringDoc OpenAPI 3 ✅
|
||||
|
||||
#### 当前状态
|
||||
|
||||
- ✅ 项目能够正常编译
|
||||
- ✅ 基础配置完成
|
||||
- ✅ 应用程序启动成功
|
||||
|
||||
### 第二阶段:核心功能重构 🔄
|
||||
|
||||
#### 已完成的工作
|
||||
|
||||
1. **实体类创建** ✅
|
||||
- User (用户实体)
|
||||
- DiaryPost (日记实体)
|
||||
- Message (消息实体)
|
||||
- Conversation (会话实体)
|
||||
|
||||
2. **DTO类创建** ✅
|
||||
- Result (通用响应DTO)
|
||||
- LoginRequest/LoginResponse (登录相关DTO)
|
||||
- RegisterRequest (注册DTO)
|
||||
|
||||
3. **Mapper接口创建** ✅
|
||||
- UserMapper (用户数据访问)
|
||||
- DiaryPostMapper (日记数据访问)
|
||||
- MessageMapper (消息数据访问)
|
||||
- ConversationMapper (会话数据访问)
|
||||
|
||||
4. **工具类创建** ✅
|
||||
- JwtUtil (JWT工具类,适配JWT 0.12.3)
|
||||
|
||||
5. **认证系统重构** ✅
|
||||
- AuthService (认证服务接口)
|
||||
- AuthServiceImpl (认证服务实现)
|
||||
- AuthController (认证控制器)
|
||||
- 支持用户注册、登录、登出、令牌刷新等功能
|
||||
|
||||
6. **用户管理系统重构** ✅
|
||||
- UserService (用户服务接口)
|
||||
- UserServiceImpl (用户服务实现)
|
||||
- UserController (用户控制器)
|
||||
- 支持用户信息管理、密码修改、用户列表等功能
|
||||
|
||||
7. **AI对话系统重构** ✅
|
||||
- CozeApiService (Coze API服务接口)
|
||||
- CozeApiServiceImpl (Coze API服务实现)
|
||||
- AiChatService (AI聊天服务接口)
|
||||
- AiChatServiceImpl (AI聊天服务实现)
|
||||
- AiChatController (AI聊天控制器)
|
||||
- 支持与Coze Bot的对话、会话管理、消息历史等功能
|
||||
|
||||
8. **日记系统重构** ✅
|
||||
- DiaryPostRequest (日记请求DTO)
|
||||
- DiaryPostService (日记服务接口)
|
||||
- DiaryPostServiceImpl (日记服务实现)
|
||||
- DiaryPostController (日记控制器)
|
||||
- 支持日记CRUD、AI点评、点赞、情绪标签等功能
|
||||
|
||||
9. **WebSocket系统重构** ✅
|
||||
- WebSocketConfig (WebSocket配置)
|
||||
- ChatMessage (WebSocket消息DTO)
|
||||
- WebSocketController (WebSocket控制器)
|
||||
- 支持实时聊天、AI对话、消息推送等功能
|
||||
|
||||
10. **社区系统重构** 🔄
|
||||
- Comment (评论实体)
|
||||
- UserFollow (用户关注实体)
|
||||
- CommentRequest/CommentResponse (评论DTO)
|
||||
- CommentMapper/UserFollowMapper (数据访问层)
|
||||
- CommentService/UserFollowService (服务接口)
|
||||
- CommentServiceImpl (评论服务实现)
|
||||
- 支持评论、回复、点赞、用户关注等功能
|
||||
|
||||
#### 当前状态
|
||||
|
||||
- ✅ 认证系统重构完成
|
||||
- ✅ 用户管理系统重构完成
|
||||
- ✅ AI对话系统重构完成
|
||||
- ✅ 日记系统重构完成
|
||||
- ✅ WebSocket系统重构完成
|
||||
- ✅ 社区系统基础重构完成
|
||||
- ✅ JWT 0.12.3适配完成
|
||||
- ✅ 基础数据访问层完成
|
||||
- 🔄 继续其他核心功能重构
|
||||
|
||||
## 下一步计划
|
||||
|
||||
### 第二阶段:核心功能重构
|
||||
|
||||
1. **认证系统重构**
|
||||
- 重构AuthController
|
||||
- 升级JWT认证机制
|
||||
- 优化Spring Security配置
|
||||
|
||||
2. **AI对话系统重构**
|
||||
- 重构AiChatController
|
||||
- 实现Coze API客户端
|
||||
- 优化异步处理机制
|
||||
|
||||
3. **用户管理系统重构**
|
||||
- 重构UserController
|
||||
- 优化用户信息管理
|
||||
|
||||
4. **日记系统重构**
|
||||
- 重构DiaryPostController
|
||||
- 优化日记CRUD操作
|
||||
|
||||
## 运行说明
|
||||
|
||||
### 环境要求
|
||||
|
||||
- JDK 17+
|
||||
- Maven 3.6+
|
||||
- MySQL 8.0+
|
||||
- Redis 7.0+
|
||||
|
||||
### 启动步骤
|
||||
|
||||
1. 配置环境变量或修改application-local.yml中的数据库和Redis连接信息
|
||||
2. 执行编译:`mvn clean compile`
|
||||
3. 启动应用:`mvn spring-boot:run`
|
||||
|
||||
### 访问地址
|
||||
|
||||
- 应用地址:http://localhost:19089
|
||||
- API文档:http://localhost:19089/api/swagger-ui.html
|
||||
- 健康检查:http://localhost:19089/api/health
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 当前使用JDK 17,后续将升级到JDK 21
|
||||
2. 数据库和Redis需要提前启动并配置
|
||||
3. Coze API配置需要设置环境变量COZE_API_KEY和COZE_BOT_ID
|
||||
|
||||
## 问题记录
|
||||
|
||||
1. Spring Boot Actuator依赖问题 - 已解决
|
||||
2. Spring Security 6.x配置调整 - 已解决
|
||||
3. 应用程序启动测试 - 进行中
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.8</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.emotionmuseum</groupId>
|
||||
<artifactId>emotion-museum-backend</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>emotion-museum-backend</name>
|
||||
<description>情绪博物馆后端服务</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Starters -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- HTTP客户端 (用于调用Coze API) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库相关 -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.12.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.12.3</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.12.3</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- API文档 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.25</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 测试 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>false</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.emotionmuseum;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* 情绪博物馆后端服务启动类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.emotionmuseum.mapper")
|
||||
@EnableCaching
|
||||
@EnableAsync
|
||||
@EnableTransactionManagement
|
||||
public class EmotionMuseumApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EmotionMuseumApplication.class, args);
|
||||
System.out.println("=================================");
|
||||
System.out.println("情绪博物馆后端服务启动成功!");
|
||||
System.out.println("服务端口: 19089");
|
||||
System.out.println("API文档: http://localhost:19089/api/swagger-ui.html");
|
||||
System.out.println("健康检查: http://localhost:19089/api/health");
|
||||
System.out.println("=================================");
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.emotionmuseum.config;
|
||||
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 异步配置类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig implements AsyncConfigurer {
|
||||
|
||||
/**
|
||||
* 默认异步执行器
|
||||
*/
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(20);
|
||||
executor.setMaxPoolSize(100);
|
||||
executor.setQueueCapacity(500);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("emotion-async-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* AI任务执行器
|
||||
*/
|
||||
@Bean("aiTaskExecutor")
|
||||
public Executor aiTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(10);
|
||||
executor.setMaxPoolSize(50);
|
||||
executor.setQueueCapacity(200);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("ai-task-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步异常处理器
|
||||
*/
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler();
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.emotionmuseum.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* Coze配置类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "emotion.coze")
|
||||
@Data
|
||||
@Slf4j
|
||||
public class CozeConfig {
|
||||
|
||||
/**
|
||||
* API密钥
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 机器人ID
|
||||
*/
|
||||
private String botId;
|
||||
|
||||
/**
|
||||
* 基础URL
|
||||
*/
|
||||
private String baseUrl = "https://www.coze.cn/api";
|
||||
|
||||
/**
|
||||
* 超时时间(毫秒)
|
||||
*/
|
||||
private int timeout = 30000;
|
||||
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private int maxRetries = 3;
|
||||
|
||||
/**
|
||||
* 验证配置
|
||||
*/
|
||||
@PostConstruct
|
||||
public void validateConfig() {
|
||||
if (!StringUtils.hasText(apiKey)) {
|
||||
log.warn("Coze API Key未配置,AI功能可能无法正常使用");
|
||||
}
|
||||
if (!StringUtils.hasText(botId)) {
|
||||
log.warn("Coze Bot ID未配置,AI功能可能无法正常使用");
|
||||
}
|
||||
if (StringUtils.hasText(apiKey) && StringUtils.hasText(botId)) {
|
||||
log.info("Coze配置验证通过,Bot ID: {}", botId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.emotionmuseum.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus配置类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
/**
|
||||
* MyBatis-Plus拦截器配置
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局配置
|
||||
*/
|
||||
@Bean
|
||||
public GlobalConfig globalConfig() {
|
||||
GlobalConfig globalConfig = new GlobalConfig();
|
||||
|
||||
// 设置数据库类型
|
||||
globalConfig.setDbConfig(new GlobalConfig.DbConfig());
|
||||
globalConfig.getDbConfig().setIdType(com.baomidou.mybatisplus.annotation.IdType.ASSIGN_ID);
|
||||
|
||||
// 设置逻辑删除
|
||||
globalConfig.getDbConfig().setLogicDeleteField("deleted");
|
||||
globalConfig.getDbConfig().setLogicDeleteValue("1");
|
||||
globalConfig.getDbConfig().setLogicNotDeleteValue("0");
|
||||
|
||||
return globalConfig;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.emotionmuseum.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* OpenAPI配置类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
/**
|
||||
* OpenAPI配置
|
||||
*/
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("情绪博物馆API文档")
|
||||
.version("1.0.0")
|
||||
.description("情绪博物馆后端服务API文档")
|
||||
.contact(new Contact()
|
||||
.name("情绪博物馆团队")
|
||||
.email("support@emotion-museum.com")))
|
||||
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes("Bearer Authentication",
|
||||
new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")));
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package com.emotionmuseum.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Redis配置类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig {
|
||||
|
||||
/**
|
||||
* Redis模板配置
|
||||
*/
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
// 使用Jackson2JsonRedisSerializer
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
|
||||
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
|
||||
mapper.registerModule(new JavaTimeModule());
|
||||
serializer.setObjectMapper(mapper);
|
||||
|
||||
// 设置序列化器
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存管理器配置
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager(RedisConnectionFactory factory) {
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
|
||||
.entryTtl(Duration.ofMinutes(30))
|
||||
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
|
||||
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
|
||||
|
||||
return RedisCacheManager.builder(factory)
|
||||
.cacheDefaults(config)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package com.emotionmuseum.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Spring Security配置类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig {
|
||||
|
||||
/**
|
||||
* 安全过滤器链配置
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// 禁用CSRF
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
// 配置CORS
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
// 会话管理
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
// 授权配置
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
// 允许访问的路径
|
||||
.requestMatchers("/health/**").permitAll()
|
||||
.requestMatchers("/auth/**").permitAll()
|
||||
.requestMatchers("/actuator/**").permitAll()
|
||||
.requestMatchers("/ws/**").permitAll()
|
||||
.requestMatchers("/ai/guest/**").permitAll()
|
||||
.requestMatchers("/swagger-ui/**").permitAll()
|
||||
.requestMatchers("/v3/api-docs/**").permitAll()
|
||||
.requestMatchers("/swagger-ui.html").permitAll()
|
||||
.requestMatchers("/favicon.ico").permitAll()
|
||||
// 其他请求需要认证
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* CORS配置
|
||||
*/
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
// 允许的源
|
||||
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
|
||||
// 允许的方法
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
// 允许的头部
|
||||
configuration.setAllowedHeaders(Arrays.asList("*"));
|
||||
// 允许携带凭证
|
||||
configuration.setAllowCredentials(true);
|
||||
// 预检请求的有效期
|
||||
configuration.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码编码器
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(12);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package com.emotionmuseum.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* HTTP客户端配置类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class WebClientConfig {
|
||||
|
||||
/**
|
||||
* WebClient构建器
|
||||
*/
|
||||
@Bean
|
||||
public WebClient.Builder webClientBuilder() {
|
||||
return WebClient.builder()
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024))
|
||||
.filter(ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
|
||||
log.debug("Request: {} {}", clientRequest.method(), clientRequest.url());
|
||||
return Mono.just(clientRequest);
|
||||
}))
|
||||
.filter(ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
|
||||
log.debug("Response: {}", clientResponse.statusCode());
|
||||
return Mono.just(clientResponse);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* RestTemplate配置
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
// 设置超时时间
|
||||
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(30000);
|
||||
factory.setReadTimeout(30000);
|
||||
restTemplate.setRequestFactory(factory);
|
||||
|
||||
// 添加请求拦截器
|
||||
restTemplate.getInterceptors().add(new ClientHttpRequestInterceptor() {
|
||||
@Override
|
||||
public org.springframework.http.client.ClientHttpResponse intercept(
|
||||
org.springframework.http.HttpRequest request,
|
||||
byte[] body,
|
||||
org.springframework.http.client.ClientHttpRequestExecution execution) throws IOException {
|
||||
log.debug("Request: {} {}", request.getMethod(), request.getURI());
|
||||
org.springframework.http.client.ClientHttpResponse response = execution.execute(request, body);
|
||||
log.debug("Response: {}", response.getStatusCode());
|
||||
return response;
|
||||
}
|
||||
});
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.emotionmuseum.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
|
||||
/**
|
||||
* WebSocket配置类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
// 注册STOMP端点
|
||||
registry.addEndpoint("/ws")
|
||||
.setAllowedOriginPatterns("*")
|
||||
.withSockJS();
|
||||
|
||||
// 支持原生WebSocket
|
||||
registry.addEndpoint("/ws")
|
||||
.setAllowedOriginPatterns("*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
||||
// 启用简单的消息代理
|
||||
registry.enableSimpleBroker("/topic", "/queue");
|
||||
|
||||
// 设置应用程序目标前缀
|
||||
registry.setApplicationDestinationPrefixes("/app");
|
||||
|
||||
// 设置用户目标前缀
|
||||
registry.setUserDestinationPrefix("/user");
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
package com.emotionmuseum.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.entity.Conversation;
|
||||
import com.emotionmuseum.entity.Message;
|
||||
import com.emotionmuseum.service.AiChatService;
|
||||
import com.emotionmuseum.service.AuthService;
|
||||
import com.emotionmuseum.service.CozeApiService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* AI聊天控制器
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/ai")
|
||||
@Tag(name = "AI聊天", description = "AI聊天相关接口")
|
||||
@Slf4j
|
||||
public class AiChatController {
|
||||
|
||||
@Autowired
|
||||
private AiChatService aiChatService;
|
||||
|
||||
@Autowired
|
||||
private CozeApiService cozeApiService;
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
@PostMapping("/chat/send")
|
||||
@Operation(summary = "发送消息", description = "发送消息并获取AI回复")
|
||||
public Result<Message> sendMessage(HttpServletRequest request,
|
||||
@RequestParam String content,
|
||||
@RequestParam(required = false) String conversationId) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户发送消息: {}, 会话: {}", userId, conversationId);
|
||||
return aiChatService.sendMessage(userId, content, conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新会话
|
||||
*/
|
||||
@PostMapping("/conversation/create")
|
||||
@Operation(summary = "创建会话", description = "创建新的聊天会话")
|
||||
public Result<Conversation> createConversation(HttpServletRequest request,
|
||||
@RequestParam(required = false) String title) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户创建会话: {}, 标题: {}", userId, title);
|
||||
return aiChatService.createConversation(userId, title);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户会话列表
|
||||
*/
|
||||
@GetMapping("/conversation/list")
|
||||
@Operation(summary = "获取会话列表", description = "获取当前用户的会话列表")
|
||||
public Result<IPage<Conversation>> getUserConversations(HttpServletRequest request,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int size) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
return aiChatService.getUserConversations(userId, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话消息列表
|
||||
*/
|
||||
@GetMapping("/conversation/{conversationId}/messages")
|
||||
@Operation(summary = "获取会话消息", description = "获取指定会话的消息列表")
|
||||
public Result<IPage<Message>> getConversationMessages(@PathVariable String conversationId,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
return aiChatService.getConversationMessages(conversationId, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
*/
|
||||
@DeleteMapping("/conversation/{conversationId}")
|
||||
@Operation(summary = "删除会话", description = "删除指定的聊天会话")
|
||||
public Result<String> deleteConversation(HttpServletRequest request,
|
||||
@PathVariable String conversationId) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户删除会话: {}, 会话: {}", userId, conversationId);
|
||||
return aiChatService.deleteConversation(userId, conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话详情
|
||||
*/
|
||||
@GetMapping("/conversation/{conversationId}")
|
||||
@Operation(summary = "获取会话详情", description = "获取指定会话的详细信息")
|
||||
public Result<Conversation> getConversationById(@PathVariable String conversationId) {
|
||||
return aiChatService.getConversationById(conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空会话消息
|
||||
*/
|
||||
@PostMapping("/conversation/{conversationId}/clear")
|
||||
@Operation(summary = "清空会话", description = "清空指定会话的所有消息")
|
||||
public Result<String> clearConversation(HttpServletRequest request,
|
||||
@PathVariable String conversationId) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户清空会话: {}, 会话: {}", userId, conversationId);
|
||||
return aiChatService.clearConversation(userId, conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查AI服务状态
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
@Operation(summary = "检查AI状态", description = "检查AI服务是否正常运行")
|
||||
public Result<Boolean> checkAiStatus() {
|
||||
return cozeApiService.checkConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Bot信息
|
||||
*/
|
||||
@GetMapping("/bot/info")
|
||||
@Operation(summary = "获取Bot信息", description = "获取AI机器人的详细信息")
|
||||
public Result<String> getBotInfo() {
|
||||
return cozeApiService.getBotInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中提取令牌
|
||||
*/
|
||||
private String extractToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package com.emotionmuseum.controller;
|
||||
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.auth.LoginRequest;
|
||||
import com.emotionmuseum.dto.auth.LoginResponse;
|
||||
import com.emotionmuseum.dto.auth.RegisterRequest;
|
||||
import com.emotionmuseum.service.AuthService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
@Tag(name = "认证管理", description = "用户认证相关接口")
|
||||
@Slf4j
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "用户登录", description = "用户登录接口")
|
||||
public Result<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||
log.info("用户登录请求: {}", request.getUsername());
|
||||
return authService.login(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
@Operation(summary = "用户注册", description = "用户注册接口")
|
||||
public Result<String> register(@Valid @RequestBody RegisterRequest request) {
|
||||
log.info("用户注册请求: {}", request.getUsername());
|
||||
return authService.register(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@Operation(summary = "用户登出", description = "用户登出接口")
|
||||
public Result<String> logout(HttpServletRequest request) {
|
||||
String token = extractToken(request);
|
||||
log.info("用户登出请求");
|
||||
return authService.logout(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
@Operation(summary = "刷新令牌", description = "刷新访问令牌")
|
||||
public Result<String> refreshToken(@RequestParam String refreshToken) {
|
||||
log.info("刷新令牌请求");
|
||||
return authService.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌
|
||||
*/
|
||||
@GetMapping("/validate")
|
||||
@Operation(summary = "验证令牌", description = "验证访问令牌是否有效")
|
||||
public Result<Boolean> validateToken(HttpServletRequest request) {
|
||||
String token = extractToken(request);
|
||||
boolean isValid = authService.validateToken(token);
|
||||
return Result.success("令牌验证完成", isValid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中提取令牌
|
||||
*/
|
||||
private String extractToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
package com.emotionmuseum.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.diary.DiaryPostRequest;
|
||||
import com.emotionmuseum.entity.DiaryPost;
|
||||
import com.emotionmuseum.service.AuthService;
|
||||
import com.emotionmuseum.service.DiaryPostService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 日记控制器
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/diary")
|
||||
@Tag(name = "日记管理", description = "日记相关接口")
|
||||
@Slf4j
|
||||
public class DiaryPostController {
|
||||
|
||||
@Autowired
|
||||
private DiaryPostService diaryPostService;
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
/**
|
||||
* 创建日记
|
||||
*/
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建日记", description = "创建新的日记")
|
||||
public Result<DiaryPost> createDiary(HttpServletRequest request, @Valid @RequestBody DiaryPostRequest diaryRequest) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户创建日记: {}", userId);
|
||||
return diaryPostService.createDiary(userId, diaryRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新日记
|
||||
*/
|
||||
@PutMapping("/{diaryId}")
|
||||
@Operation(summary = "更新日记", description = "更新指定的日记")
|
||||
public Result<String> updateDiary(HttpServletRequest request,
|
||||
@PathVariable String diaryId,
|
||||
@Valid @RequestBody DiaryPostRequest diaryRequest) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户更新日记: {}, 日记: {}", userId, diaryId);
|
||||
return diaryPostService.updateDiary(userId, diaryId, diaryRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日记详情
|
||||
*/
|
||||
@GetMapping("/{diaryId}")
|
||||
@Operation(summary = "获取日记详情", description = "获取指定日记的详细信息")
|
||||
public Result<DiaryPost> getDiaryById(@PathVariable String diaryId) {
|
||||
return diaryPostService.getDiaryById(diaryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户日记列表
|
||||
*/
|
||||
@GetMapping("/user/list")
|
||||
@Operation(summary = "获取用户日记列表", description = "获取当前用户的日记列表")
|
||||
public Result<IPage<DiaryPost>> getUserDiaries(HttpServletRequest request,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int size) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
return diaryPostService.getUserDiaries(userId, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开日记列表
|
||||
*/
|
||||
@GetMapping("/public/list")
|
||||
@Operation(summary = "获取公开日记列表", description = "获取所有公开的日记列表")
|
||||
public Result<IPage<DiaryPost>> getPublicDiaries(@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int size) {
|
||||
return diaryPostService.getPublicDiaries(page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据情绪标签查询日记
|
||||
*/
|
||||
@GetMapping("/emotion/{emotionTag}")
|
||||
@Operation(summary = "根据情绪标签查询日记", description = "根据情绪标签查询公开日记")
|
||||
public Result<IPage<DiaryPost>> getDiariesByEmotionTag(@PathVariable String emotionTag,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int size) {
|
||||
return diaryPostService.getDiariesByEmotionTag(emotionTag, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除日记
|
||||
*/
|
||||
@DeleteMapping("/{diaryId}")
|
||||
@Operation(summary = "删除日记", description = "删除指定的日记")
|
||||
public Result<String> deleteDiary(HttpServletRequest request, @PathVariable String diaryId) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户删除日记: {}, 日记: {}", userId, diaryId);
|
||||
return diaryPostService.deleteDiary(userId, diaryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 点赞日记
|
||||
*/
|
||||
@PostMapping("/{diaryId}/like")
|
||||
@Operation(summary = "点赞日记", description = "对指定日记进行点赞")
|
||||
public Result<String> likeDiary(HttpServletRequest request, @PathVariable String diaryId) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户点赞日记: {}, 日记: {}", userId, diaryId);
|
||||
return diaryPostService.likeDiary(userId, diaryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消点赞
|
||||
*/
|
||||
@PostMapping("/{diaryId}/unlike")
|
||||
@Operation(summary = "取消点赞", description = "取消对指定日记的点赞")
|
||||
public Result<String> unlikeDiary(HttpServletRequest request, @PathVariable String diaryId) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
log.info("用户取消点赞: {}, 日记: {}", userId, diaryId);
|
||||
return diaryPostService.unlikeDiary(userId, diaryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI点评
|
||||
*/
|
||||
@GetMapping("/{diaryId}/ai-comment")
|
||||
@Operation(summary = "获取AI点评", description = "获取指定日记的AI点评")
|
||||
public Result<String> getAiComment(HttpServletRequest request, @PathVariable String diaryId) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
|
||||
return diaryPostService.getAiComment(userId, diaryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中提取令牌
|
||||
*/
|
||||
private String extractToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.emotionmuseum.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 健康检查控制器
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/health")
|
||||
@Slf4j
|
||||
public class HealthController {
|
||||
|
||||
/**
|
||||
* 健康检查接口
|
||||
*/
|
||||
@GetMapping
|
||||
public Map<String, Object> health() {
|
||||
log.info("健康检查请求");
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("status", "UP");
|
||||
result.put("timestamp", LocalDateTime.now());
|
||||
result.put("service", "emotion-museum-backend");
|
||||
result.put("version", "1.0.0");
|
||||
result.put("message", "系统运行正常");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统信息接口
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
public Map<String, Object> info() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("name", "情绪博物馆后端服务");
|
||||
result.put("version", "1.0.0");
|
||||
result.put("description", "基于Spring Boot 3.4.8的情绪博物馆后端服务");
|
||||
result.put("javaVersion", System.getProperty("java.version"));
|
||||
result.put("startTime", LocalDateTime.now());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package com.emotionmuseum.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.entity.User;
|
||||
import com.emotionmuseum.service.AuthService;
|
||||
import com.emotionmuseum.service.UserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 用户控制器
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
@Tag(name = "用户管理", description = "用户相关接口")
|
||||
@Slf4j
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
@GetMapping("/profile")
|
||||
@Operation(summary = "获取用户信息", description = "获取当前登录用户的详细信息")
|
||||
public Result<User> getProfile(HttpServletRequest request) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
return userService.getUserById(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
@PutMapping("/profile")
|
||||
@Operation(summary = "更新用户信息", description = "更新当前登录用户的信息")
|
||||
public Result<String> updateProfile(HttpServletRequest request, @Valid @RequestBody User user) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
return userService.updateUser(userId, user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
@PostMapping("/change-password")
|
||||
@Operation(summary = "修改密码", description = "修改当前登录用户的密码")
|
||||
public Result<String> changePassword(HttpServletRequest request,
|
||||
@RequestParam String oldPassword,
|
||||
@RequestParam String newPassword) {
|
||||
String token = extractToken(request);
|
||||
String userId = authService.getUserIdFromToken(token);
|
||||
if (userId == null) {
|
||||
return Result.unauthorized();
|
||||
}
|
||||
return userService.changePassword(userId, oldPassword, newPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表(管理员功能)
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获取用户列表", description = "分页获取用户列表(管理员功能)")
|
||||
public Result<IPage<User>> getUserList(@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "10") int size) {
|
||||
return userService.getUserList(page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取用户信息(管理员功能)
|
||||
*/
|
||||
@GetMapping("/{userId}")
|
||||
@Operation(summary = "获取指定用户信息", description = "根据用户ID获取用户信息(管理员功能)")
|
||||
public Result<User> getUserById(@PathVariable String userId) {
|
||||
return userService.getUserById(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户(管理员功能)
|
||||
*/
|
||||
@DeleteMapping("/{userId}")
|
||||
@Operation(summary = "删除用户", description = "删除指定用户(管理员功能)")
|
||||
public Result<String> deleteUser(@PathVariable String userId) {
|
||||
return userService.deleteUser(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用用户(管理员功能)
|
||||
*/
|
||||
@PostMapping("/{userId}/status")
|
||||
@Operation(summary = "更新用户状态", description = "启用或禁用用户(管理员功能)")
|
||||
public Result<String> toggleUserStatus(@PathVariable String userId, @RequestParam Integer status) {
|
||||
return userService.toggleUserStatus(userId, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中提取令牌
|
||||
*/
|
||||
private String extractToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package com.emotionmuseum.controller;
|
||||
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.websocket.ChatMessage;
|
||||
import com.emotionmuseum.service.AiChatService;
|
||||
import com.emotionmuseum.service.AuthService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.handler.annotation.SendTo;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
/**
|
||||
* WebSocket控制器
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Controller
|
||||
@Slf4j
|
||||
public class WebSocketController {
|
||||
|
||||
@Autowired
|
||||
private SimpMessagingTemplate messagingTemplate;
|
||||
|
||||
@Autowired
|
||||
private AiChatService aiChatService;
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
/**
|
||||
* 处理聊天消息
|
||||
*/
|
||||
@MessageMapping("/chat.sendMessage")
|
||||
@SendTo("/topic/public")
|
||||
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
|
||||
log.info("收到WebSocket消息: {}", chatMessage.getContent());
|
||||
return chatMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户加入聊天
|
||||
*/
|
||||
@MessageMapping("/chat.addUser")
|
||||
@SendTo("/topic/public")
|
||||
public ChatMessage addUser(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) {
|
||||
// 添加用户名到WebSocket会话
|
||||
headerAccessor.getSessionAttributes().put("username", chatMessage.getSenderId());
|
||||
log.info("用户加入聊天: {}", chatMessage.getSenderId());
|
||||
return chatMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理AI聊天消息
|
||||
*/
|
||||
@MessageMapping("/ai.chat")
|
||||
public void handleAiChat(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) {
|
||||
try {
|
||||
String userId = chatMessage.getSenderId();
|
||||
String content = chatMessage.getContent();
|
||||
String conversationId = chatMessage.getConversationId();
|
||||
|
||||
log.info("处理AI聊天消息: 用户={}, 内容={}, 会话={}", userId, content, conversationId);
|
||||
|
||||
// 调用AI服务获取回复
|
||||
var result = aiChatService.sendMessage(userId, content, conversationId);
|
||||
|
||||
if (result.getCode() == 200) {
|
||||
// 发送AI回复
|
||||
ChatMessage aiResponse = new ChatMessage();
|
||||
aiResponse.setType("CHAT");
|
||||
aiResponse.setConversationId(conversationId);
|
||||
aiResponse.setSenderId("AI");
|
||||
aiResponse.setSenderType("AI");
|
||||
aiResponse.setContent(result.getData().getContent());
|
||||
aiResponse.setMessageType("TEXT");
|
||||
|
||||
// 发送给特定用户
|
||||
messagingTemplate.convertAndSendToUser(
|
||||
userId,
|
||||
"/queue/ai.response",
|
||||
aiResponse
|
||||
);
|
||||
|
||||
log.info("AI回复发送成功: {}", aiResponse.getContent());
|
||||
} else {
|
||||
// 发送错误消息
|
||||
ChatMessage errorResponse = new ChatMessage();
|
||||
errorResponse.setType("ERROR");
|
||||
errorResponse.setConversationId(conversationId);
|
||||
errorResponse.setSenderId("SYSTEM");
|
||||
errorResponse.setSenderType("SYSTEM");
|
||||
errorResponse.setContent("抱歉,AI暂时无法回复,请稍后再试。");
|
||||
errorResponse.setMessageType("TEXT");
|
||||
|
||||
messagingTemplate.convertAndSendToUser(
|
||||
userId,
|
||||
"/queue/ai.response",
|
||||
errorResponse
|
||||
);
|
||||
|
||||
log.error("AI回复失败: {}", result.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理AI聊天消息时发生错误: {}", e.getMessage(), e);
|
||||
|
||||
// 发送错误消息
|
||||
ChatMessage errorResponse = new ChatMessage();
|
||||
errorResponse.setType("ERROR");
|
||||
errorResponse.setConversationId(chatMessage.getConversationId());
|
||||
errorResponse.setSenderId("SYSTEM");
|
||||
errorResponse.setSenderType("SYSTEM");
|
||||
errorResponse.setContent("系统错误,请稍后再试。");
|
||||
errorResponse.setMessageType("TEXT");
|
||||
|
||||
messagingTemplate.convertAndSendToUser(
|
||||
chatMessage.getSenderId(),
|
||||
"/queue/ai.response",
|
||||
errorResponse
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户输入状态
|
||||
*/
|
||||
@MessageMapping("/chat.typing")
|
||||
@SendTo("/topic/public")
|
||||
public ChatMessage handleTyping(@Payload ChatMessage chatMessage) {
|
||||
log.info("用户正在输入: {}", chatMessage.getSenderId());
|
||||
return chatMessage;
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package com.emotionmuseum.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 通用响应结果
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class Result<T> {
|
||||
|
||||
/**
|
||||
* 响应码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
|
||||
public Result() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Result(Integer code, String message) {
|
||||
this();
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Result(Integer code, String message, T data) {
|
||||
this(code, message);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应
|
||||
*/
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(200, "操作成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(带数据)
|
||||
*/
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(200, "操作成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> success(String message, T data) {
|
||||
return new Result<>(200, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*/
|
||||
public static <T> Result<T> error() {
|
||||
return new Result<>(500, "操作失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> error(String message) {
|
||||
return new Result<>(500, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应(自定义码和消息)
|
||||
*/
|
||||
public static <T> Result<T> error(Integer code, String message) {
|
||||
return new Result<>(code, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权响应
|
||||
*/
|
||||
public static <T> Result<T> unauthorized() {
|
||||
return new Result<>(401, "未授权访问");
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止访问响应
|
||||
*/
|
||||
public static <T> Result<T> forbidden() {
|
||||
return new Result<>(403, "禁止访问");
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源不存在响应
|
||||
*/
|
||||
public static <T> Result<T> notFound() {
|
||||
return new Result<>(404, "资源不存在");
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.emotionmuseum.dto.auth;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录请求DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
|
||||
/**
|
||||
* 用户名或邮箱
|
||||
*/
|
||||
@NotBlank(message = "用户名或邮箱不能为空")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
private String captcha;
|
||||
|
||||
/**
|
||||
* 验证码ID
|
||||
*/
|
||||
private String captchaId;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package com.emotionmuseum.dto.auth;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录响应DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class LoginResponse {
|
||||
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
private String refreshToken;
|
||||
|
||||
/**
|
||||
* 令牌类型
|
||||
*/
|
||||
private String tokenType = "Bearer";
|
||||
|
||||
/**
|
||||
* 过期时间(秒)
|
||||
*/
|
||||
private Long expiresIn;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
private UserInfo userInfo;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
@Data
|
||||
public static class UserInfo {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private Integer userType;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.emotionmuseum.dto.auth;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 注册请求DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class RegisterRequest {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 确认密码
|
||||
*/
|
||||
@NotBlank(message = "确认密码不能为空")
|
||||
private String confirmPassword;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@NotBlank(message = "邮箱不能为空")
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@Size(max = 50, message = "昵称长度不能超过50个字符")
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
private String captcha;
|
||||
|
||||
/**
|
||||
* 验证码ID
|
||||
*/
|
||||
private String captchaId;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.emotionmuseum.dto.comment;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 评论请求DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class CommentRequest {
|
||||
|
||||
/**
|
||||
* 父评论ID (用于回复功能)
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 内容类型 (DIARY, POST)
|
||||
*/
|
||||
@NotBlank(message = "内容类型不能为空")
|
||||
private String contentType;
|
||||
|
||||
/**
|
||||
* 内容ID
|
||||
*/
|
||||
@NotBlank(message = "内容ID不能为空")
|
||||
private String contentId;
|
||||
|
||||
/**
|
||||
* 评论内容
|
||||
*/
|
||||
@NotBlank(message = "评论内容不能为空")
|
||||
@Size(max = 1000, message = "评论内容长度不能超过1000个字符")
|
||||
private String content;
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package com.emotionmuseum.dto.comment;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 评论响应DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class CommentResponse {
|
||||
|
||||
/**
|
||||
* 评论ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 父评论ID
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 内容类型
|
||||
*/
|
||||
private String contentType;
|
||||
|
||||
/**
|
||||
* 内容ID
|
||||
*/
|
||||
private String contentId;
|
||||
|
||||
/**
|
||||
* 评论者ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 评论者昵称
|
||||
*/
|
||||
private String userNickname;
|
||||
|
||||
/**
|
||||
* 评论者头像
|
||||
*/
|
||||
private String userAvatar;
|
||||
|
||||
/**
|
||||
* 评论内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 回复数
|
||||
*/
|
||||
private Integer replyCount;
|
||||
|
||||
/**
|
||||
* 是否已点赞
|
||||
*/
|
||||
private Boolean isLiked;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 子评论列表
|
||||
*/
|
||||
private List<CommentResponse> replies;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.emotionmuseum.dto.diary;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 日记请求DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class DiaryPostRequest {
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
@NotBlank(message = "标题不能为空")
|
||||
@Size(max = 100, message = "标题长度不能超过100个字符")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 内容
|
||||
*/
|
||||
@NotBlank(message = "内容不能为空")
|
||||
@Size(max = 10000, message = "内容长度不能超过10000个字符")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 情绪标签
|
||||
*/
|
||||
private String emotionTags;
|
||||
|
||||
/**
|
||||
* 情绪评分 (1-10)
|
||||
*/
|
||||
private Integer emotionScore;
|
||||
|
||||
/**
|
||||
* 天气
|
||||
*/
|
||||
private String weather;
|
||||
|
||||
/**
|
||||
* 位置
|
||||
*/
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 图片URL列表 (JSON格式)
|
||||
*/
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 是否公开 (0:私密, 1:公开)
|
||||
*/
|
||||
private Integer isPublic = 0;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.emotionmuseum.dto.websocket;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* WebSocket聊天消息DTO
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class ChatMessage {
|
||||
|
||||
/**
|
||||
* 消息类型 (CHAT, JOIN, LEAVE, TYPING)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 发送者ID
|
||||
*/
|
||||
private String senderId;
|
||||
|
||||
/**
|
||||
* 发送者类型 (USER, AI)
|
||||
*/
|
||||
private String senderType;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 消息类型 (TEXT, IMAGE, FILE)
|
||||
*/
|
||||
private String messageType;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
|
||||
public ChatMessage() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public ChatMessage(String type, String conversationId, String senderId, String senderType, String content) {
|
||||
this();
|
||||
this.type = type;
|
||||
this.conversationId = conversationId;
|
||||
this.senderId = senderId;
|
||||
this.senderType = senderType;
|
||||
this.content = content;
|
||||
this.messageType = "TEXT";
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.emotionmuseum.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 评论实体类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("comment")
|
||||
public class Comment {
|
||||
|
||||
/**
|
||||
* 评论ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 父评论ID (用于回复功能)
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 内容类型 (DIARY, POST)
|
||||
*/
|
||||
private String contentType;
|
||||
|
||||
/**
|
||||
* 内容ID
|
||||
*/
|
||||
private String contentId;
|
||||
|
||||
/**
|
||||
* 评论者ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 评论内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 回复数
|
||||
*/
|
||||
private Integer replyCount;
|
||||
|
||||
/**
|
||||
* 状态 (0:待审核, 1:正常, 2:已删除)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 (0:未删除, 1:已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.emotionmuseum.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 会话实体类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("conversation")
|
||||
public class Conversation {
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 会话标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 会话类型 (CHAT, SUMMARY)
|
||||
*/
|
||||
private String conversationType;
|
||||
|
||||
/**
|
||||
* 消息数量
|
||||
*/
|
||||
private Integer messageCount;
|
||||
|
||||
/**
|
||||
* 最后消息时间
|
||||
*/
|
||||
private LocalDateTime lastMessageTime;
|
||||
|
||||
/**
|
||||
* 会话状态 (0:进行中, 1:已结束, 2:已删除)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 (0:未删除, 1:已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package com.emotionmuseum.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日记实体类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("diary_post")
|
||||
public class DiaryPost {
|
||||
|
||||
/**
|
||||
* 日记ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 情绪标签
|
||||
*/
|
||||
private String emotionTags;
|
||||
|
||||
/**
|
||||
* 情绪评分 (1-10)
|
||||
*/
|
||||
private Integer emotionScore;
|
||||
|
||||
/**
|
||||
* 天气
|
||||
*/
|
||||
private String weather;
|
||||
|
||||
/**
|
||||
* 位置
|
||||
*/
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 图片URL列表 (JSON格式)
|
||||
*/
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 是否公开 (0:私密, 1:公开)
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer likeCount;
|
||||
|
||||
/**
|
||||
* 评论数
|
||||
*/
|
||||
private Integer commentCount;
|
||||
|
||||
/**
|
||||
* 分享数
|
||||
*/
|
||||
private Integer shareCount;
|
||||
|
||||
/**
|
||||
* AI点评
|
||||
*/
|
||||
private String aiComment;
|
||||
|
||||
/**
|
||||
* 状态 (0:草稿, 1:已发布, 2:已删除)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 (0:未删除, 1:已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.emotionmuseum.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 消息实体类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("message")
|
||||
public class Message {
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String conversationId;
|
||||
|
||||
/**
|
||||
* 发送者ID
|
||||
*/
|
||||
private String senderId;
|
||||
|
||||
/**
|
||||
* 发送者类型 (USER, AI)
|
||||
*/
|
||||
private String senderType;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 消息类型 (TEXT, IMAGE, FILE)
|
||||
*/
|
||||
private String messageType;
|
||||
|
||||
/**
|
||||
* 消息状态 (0:未读, 1:已读, 2:已发送, 3:发送失败)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 (0:未删除, 1:已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package com.emotionmuseum.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户实体类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("user")
|
||||
public class User {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 性别 (0:未知, 1:男, 2:女)
|
||||
*/
|
||||
private Integer gender;
|
||||
|
||||
/**
|
||||
* 生日
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
|
||||
/**
|
||||
* 个人简介
|
||||
*/
|
||||
private String bio;
|
||||
|
||||
/**
|
||||
* 状态 (0:禁用, 1:正常)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 用户类型 (0:普通用户, 1:VIP用户, 2:管理员)
|
||||
*/
|
||||
private Integer userType;
|
||||
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
/**
|
||||
* 最后登录IP
|
||||
*/
|
||||
private String lastLoginIp;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 (0:未删除, 1:已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.emotionmuseum.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户关注实体类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("user_follow")
|
||||
public class UserFollow {
|
||||
|
||||
/**
|
||||
* 关注ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 关注者ID
|
||||
*/
|
||||
private String followerId;
|
||||
|
||||
/**
|
||||
* 被关注者ID
|
||||
*/
|
||||
private String followingId;
|
||||
|
||||
/**
|
||||
* 关注状态 (0:取消关注, 1:已关注)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除 (0:未删除, 1:已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.emotionmuseum.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.entity.Comment;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 评论Mapper接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface CommentMapper extends BaseMapper<Comment> {
|
||||
|
||||
/**
|
||||
* 分页查询内容的评论
|
||||
*/
|
||||
@Select("SELECT * FROM comment WHERE content_type = #{contentType} AND content_id = #{contentId} AND parent_id IS NULL AND deleted = 0 ORDER BY create_time DESC")
|
||||
IPage<Comment> selectContentComments(Page<Comment> page, @Param("contentType") String contentType, @Param("contentId") String contentId);
|
||||
|
||||
/**
|
||||
* 查询评论的回复
|
||||
*/
|
||||
@Select("SELECT * FROM comment WHERE parent_id = #{parentId} AND deleted = 0 ORDER BY create_time ASC")
|
||||
List<Comment> selectCommentReplies(@Param("parentId") String parentId);
|
||||
|
||||
/**
|
||||
* 统计内容的评论数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM comment WHERE content_type = #{contentType} AND content_id = #{contentId} AND deleted = 0")
|
||||
int countByContent(@Param("contentType") String contentType, @Param("contentId") String contentId);
|
||||
|
||||
/**
|
||||
* 统计用户的评论数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM comment WHERE user_id = #{userId} AND deleted = 0")
|
||||
int countByUserId(@Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* 查询用户的最新评论
|
||||
*/
|
||||
@Select("SELECT * FROM comment WHERE user_id = #{userId} AND deleted = 0 ORDER BY create_time DESC LIMIT #{limit}")
|
||||
List<Comment> selectUserLatestComments(@Param("userId") String userId, @Param("limit") int limit);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.emotionmuseum.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.entity.Conversation;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 会话Mapper接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConversationMapper extends BaseMapper<Conversation> {
|
||||
|
||||
/**
|
||||
* 分页查询用户会话
|
||||
*/
|
||||
@Select("SELECT * FROM conversation WHERE user_id = #{userId} AND deleted = 0 ORDER BY last_message_time DESC")
|
||||
IPage<Conversation> selectUserConversations(Page<Conversation> page, @Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* 统计用户会话数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM conversation WHERE user_id = #{userId} AND deleted = 0")
|
||||
int countByUserId(@Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* 查询用户最新的会话
|
||||
*/
|
||||
@Select("SELECT * FROM conversation WHERE user_id = #{userId} AND deleted = 0 ORDER BY last_message_time DESC LIMIT 1")
|
||||
Conversation selectLatestConversation(@Param("userId") String userId);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.emotionmuseum.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.entity.DiaryPost;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 日记Mapper接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface DiaryPostMapper extends BaseMapper<DiaryPost> {
|
||||
|
||||
/**
|
||||
* 分页查询用户的日记
|
||||
*/
|
||||
@Select("SELECT * FROM diary_post WHERE user_id = #{userId} AND deleted = 0 ORDER BY create_time DESC")
|
||||
IPage<DiaryPost> selectUserDiaries(Page<DiaryPost> page, @Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* 分页查询公开的日记
|
||||
*/
|
||||
@Select("SELECT * FROM diary_post WHERE is_public = 1 AND status = 1 AND deleted = 0 ORDER BY create_time DESC")
|
||||
IPage<DiaryPost> selectPublicDiaries(Page<DiaryPost> page);
|
||||
|
||||
/**
|
||||
* 根据情绪标签查询日记
|
||||
*/
|
||||
@Select("SELECT * FROM diary_post WHERE emotion_tags LIKE CONCAT('%', #{emotionTag}, '%') AND is_public = 1 AND status = 1 AND deleted = 0 ORDER BY create_time DESC")
|
||||
IPage<DiaryPost> selectDiariesByEmotionTag(Page<DiaryPost> page, @Param("emotionTag") String emotionTag);
|
||||
|
||||
/**
|
||||
* 统计用户的日记数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM diary_post WHERE user_id = #{userId} AND deleted = 0")
|
||||
int countByUserId(@Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* 统计公开日记数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM diary_post WHERE is_public = 1 AND status = 1 AND deleted = 0")
|
||||
int countPublicDiaries();
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package com.emotionmuseum.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.entity.Message;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 消息Mapper接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface MessageMapper extends BaseMapper<Message> {
|
||||
|
||||
/**
|
||||
* 分页查询会话消息
|
||||
*/
|
||||
@Select("SELECT * FROM message WHERE conversation_id = #{conversationId} AND deleted = 0 ORDER BY create_time ASC")
|
||||
IPage<Message> selectConversationMessages(Page<Message> page, @Param("conversationId") String conversationId);
|
||||
|
||||
/**
|
||||
* 查询会话的最新消息
|
||||
*/
|
||||
@Select("SELECT * FROM message WHERE conversation_id = #{conversationId} AND deleted = 0 ORDER BY create_time DESC LIMIT 1")
|
||||
Message selectLatestMessage(@Param("conversationId") String conversationId);
|
||||
|
||||
/**
|
||||
* 查询会话的所有消息
|
||||
*/
|
||||
@Select("SELECT * FROM message WHERE conversation_id = #{conversationId} AND deleted = 0 ORDER BY create_time ASC")
|
||||
List<Message> selectAllMessages(@Param("conversationId") String conversationId);
|
||||
|
||||
/**
|
||||
* 统计会话消息数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM message WHERE conversation_id = #{conversationId} AND deleted = 0")
|
||||
int countByConversationId(@Param("conversationId") String conversationId);
|
||||
|
||||
/**
|
||||
* 统计用户未读消息数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM message m JOIN conversation c ON m.conversation_id = c.id WHERE c.user_id = #{userId} AND m.sender_type = 'AI' AND m.status = 0 AND m.deleted = 0")
|
||||
int countUnreadMessages(@Param("userId") String userId);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.emotionmuseum.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.entity.UserFollow;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 用户关注Mapper接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserFollowMapper extends BaseMapper<UserFollow> {
|
||||
|
||||
/**
|
||||
* 分页查询用户的关注列表
|
||||
*/
|
||||
@Select("SELECT * FROM user_follow WHERE follower_id = #{followerId} AND status = 1 AND deleted = 0 ORDER BY create_time DESC")
|
||||
IPage<UserFollow> selectUserFollowings(Page<UserFollow> page, @Param("followerId") String followerId);
|
||||
|
||||
/**
|
||||
* 分页查询用户的粉丝列表
|
||||
*/
|
||||
@Select("SELECT * FROM user_follow WHERE following_id = #{followingId} AND status = 1 AND deleted = 0 ORDER BY create_time DESC")
|
||||
IPage<UserFollow> selectUserFollowers(Page<UserFollow> page, @Param("followingId") String followingId);
|
||||
|
||||
/**
|
||||
* 检查是否已关注
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM user_follow WHERE follower_id = #{followerId} AND following_id = #{followingId} AND status = 1 AND deleted = 0")
|
||||
int checkIsFollowing(@Param("followerId") String followerId, @Param("followingId") String followingId);
|
||||
|
||||
/**
|
||||
* 统计用户关注数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM user_follow WHERE follower_id = #{followerId} AND status = 1 AND deleted = 0")
|
||||
int countFollowings(@Param("followerId") String followerId);
|
||||
|
||||
/**
|
||||
* 统计用户粉丝数量
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM user_follow WHERE following_id = #{followingId} AND status = 1 AND deleted = 0")
|
||||
int countFollowers(@Param("followingId") String followingId);
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.emotionmuseum.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.emotionmuseum.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* 用户Mapper接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户
|
||||
*/
|
||||
@Select("SELECT * FROM user WHERE username = #{username} AND deleted = 0")
|
||||
User findByUsername(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* 根据邮箱查询用户
|
||||
*/
|
||||
@Select("SELECT * FROM user WHERE email = #{email} AND deleted = 0")
|
||||
User findByEmail(@Param("email") String email);
|
||||
|
||||
/**
|
||||
* 根据手机号查询用户
|
||||
*/
|
||||
@Select("SELECT * FROM user WHERE phone = #{phone} AND deleted = 0")
|
||||
User findByPhone(@Param("phone") String phone);
|
||||
|
||||
/**
|
||||
* 检查用户名是否存在
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM user WHERE username = #{username} AND deleted = 0")
|
||||
int countByUsername(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* 检查邮箱是否存在
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM user WHERE email = #{email} AND deleted = 0")
|
||||
int countByEmail(@Param("email") String email);
|
||||
|
||||
/**
|
||||
* 检查手机号是否存在
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM user WHERE phone = #{phone} AND deleted = 0")
|
||||
int countByPhone(@Param("phone") String phone);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.emotionmuseum.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.entity.Conversation;
|
||||
import com.emotionmuseum.entity.Message;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI聊天服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface AiChatService {
|
||||
|
||||
/**
|
||||
* 发送消息并获取AI回复
|
||||
*/
|
||||
Result<Message> sendMessage(String userId, String content, String conversationId);
|
||||
|
||||
/**
|
||||
* 创建新会话
|
||||
*/
|
||||
Result<Conversation> createConversation(String userId, String title);
|
||||
|
||||
/**
|
||||
* 获取用户会话列表
|
||||
*/
|
||||
Result<IPage<Conversation>> getUserConversations(String userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 获取会话消息列表
|
||||
*/
|
||||
Result<IPage<Message>> getConversationMessages(String conversationId, int page, int size);
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
*/
|
||||
Result<String> deleteConversation(String userId, String conversationId);
|
||||
|
||||
/**
|
||||
* 获取会话详情
|
||||
*/
|
||||
Result<Conversation> getConversationById(String conversationId);
|
||||
|
||||
/**
|
||||
* 清空会话消息
|
||||
*/
|
||||
Result<String> clearConversation(String userId, String conversationId);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.emotionmuseum.service;
|
||||
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.auth.LoginRequest;
|
||||
import com.emotionmuseum.dto.auth.LoginResponse;
|
||||
import com.emotionmuseum.dto.auth.RegisterRequest;
|
||||
|
||||
/**
|
||||
* 认证服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
Result<LoginResponse> login(LoginRequest request);
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
Result<String> register(RegisterRequest request);
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
*/
|
||||
Result<String> logout(String token);
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
Result<String> refreshToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 验证令牌
|
||||
*/
|
||||
boolean validateToken(String token);
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户ID
|
||||
*/
|
||||
String getUserIdFromToken(String token);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.emotionmuseum.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.comment.CommentRequest;
|
||||
import com.emotionmuseum.dto.comment.CommentResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 评论服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface CommentService {
|
||||
|
||||
/**
|
||||
* 创建评论
|
||||
*/
|
||||
Result<CommentResponse> createComment(String userId, CommentRequest request);
|
||||
|
||||
/**
|
||||
* 获取内容评论列表
|
||||
*/
|
||||
Result<IPage<CommentResponse>> getContentComments(String contentType, String contentId, int page, int size);
|
||||
|
||||
/**
|
||||
* 获取评论详情
|
||||
*/
|
||||
Result<CommentResponse> getCommentById(String commentId);
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
*/
|
||||
Result<String> deleteComment(String userId, String commentId);
|
||||
|
||||
/**
|
||||
* 点赞评论
|
||||
*/
|
||||
Result<String> likeComment(String userId, String commentId);
|
||||
|
||||
/**
|
||||
* 取消点赞评论
|
||||
*/
|
||||
Result<String> unlikeComment(String userId, String commentId);
|
||||
|
||||
/**
|
||||
* 获取用户评论列表
|
||||
*/
|
||||
Result<IPage<CommentResponse>> getUserComments(String userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 获取评论回复列表
|
||||
*/
|
||||
Result<List<CommentResponse>> getCommentReplies(String commentId);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.emotionmuseum.service;
|
||||
|
||||
import com.emotionmuseum.dto.Result;
|
||||
|
||||
/**
|
||||
* Coze API服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface CozeApiService {
|
||||
|
||||
/**
|
||||
* 发送消息到Coze Bot
|
||||
*/
|
||||
Result<String> sendMessage(String message, String userId);
|
||||
|
||||
/**
|
||||
* 发送消息到Coze Bot(带上下文)
|
||||
*/
|
||||
Result<String> sendMessageWithContext(String message, String userId, String conversationId);
|
||||
|
||||
/**
|
||||
* 获取Bot信息
|
||||
*/
|
||||
Result<String> getBotInfo();
|
||||
|
||||
/**
|
||||
* 检查API连接状态
|
||||
*/
|
||||
Result<Boolean> checkConnection();
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.emotionmuseum.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.diary.DiaryPostRequest;
|
||||
import com.emotionmuseum.entity.DiaryPost;
|
||||
|
||||
/**
|
||||
* 日记服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface DiaryPostService {
|
||||
|
||||
/**
|
||||
* 创建日记
|
||||
*/
|
||||
Result<DiaryPost> createDiary(String userId, DiaryPostRequest request);
|
||||
|
||||
/**
|
||||
* 更新日记
|
||||
*/
|
||||
Result<String> updateDiary(String userId, String diaryId, DiaryPostRequest request);
|
||||
|
||||
/**
|
||||
* 获取日记详情
|
||||
*/
|
||||
Result<DiaryPost> getDiaryById(String diaryId);
|
||||
|
||||
/**
|
||||
* 获取用户日记列表
|
||||
*/
|
||||
Result<IPage<DiaryPost>> getUserDiaries(String userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 获取公开日记列表
|
||||
*/
|
||||
Result<IPage<DiaryPost>> getPublicDiaries(int page, int size);
|
||||
|
||||
/**
|
||||
* 根据情绪标签查询日记
|
||||
*/
|
||||
Result<IPage<DiaryPost>> getDiariesByEmotionTag(String emotionTag, int page, int size);
|
||||
|
||||
/**
|
||||
* 删除日记
|
||||
*/
|
||||
Result<String> deleteDiary(String userId, String diaryId);
|
||||
|
||||
/**
|
||||
* 点赞日记
|
||||
*/
|
||||
Result<String> likeDiary(String userId, String diaryId);
|
||||
|
||||
/**
|
||||
* 取消点赞
|
||||
*/
|
||||
Result<String> unlikeDiary(String userId, String diaryId);
|
||||
|
||||
/**
|
||||
* 获取AI点评
|
||||
*/
|
||||
Result<String> getAiComment(String userId, String diaryId);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.emotionmuseum.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.entity.UserFollow;
|
||||
|
||||
/**
|
||||
* 用户关注服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface UserFollowService {
|
||||
|
||||
/**
|
||||
* 关注用户
|
||||
*/
|
||||
Result<String> followUser(String followerId, String followingId);
|
||||
|
||||
/**
|
||||
* 取消关注
|
||||
*/
|
||||
Result<String> unfollowUser(String followerId, String followingId);
|
||||
|
||||
/**
|
||||
* 检查是否已关注
|
||||
*/
|
||||
Result<Boolean> checkIsFollowing(String followerId, String followingId);
|
||||
|
||||
/**
|
||||
* 获取用户关注列表
|
||||
*/
|
||||
Result<IPage<UserFollow>> getUserFollowings(String userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 获取用户粉丝列表
|
||||
*/
|
||||
Result<IPage<UserFollow>> getUserFollowers(String userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 获取用户关注数量
|
||||
*/
|
||||
Result<Integer> getFollowingsCount(String userId);
|
||||
|
||||
/**
|
||||
* 获取用户粉丝数量
|
||||
*/
|
||||
Result<Integer> getFollowersCount(String userId);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.emotionmuseum.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.entity.User;
|
||||
|
||||
/**
|
||||
* 用户服务接口
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface UserService {
|
||||
|
||||
/**
|
||||
* 根据ID获取用户信息
|
||||
*/
|
||||
Result<User> getUserById(String userId);
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
Result<String> updateUser(String userId, User user);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
Result<String> changePassword(String userId, String oldPassword, String newPassword);
|
||||
|
||||
/**
|
||||
* 分页查询用户列表
|
||||
*/
|
||||
Result<IPage<User>> getUserList(int page, int size);
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
Result<String> deleteUser(String userId);
|
||||
|
||||
/**
|
||||
* 启用/禁用用户
|
||||
*/
|
||||
Result<String> toggleUserStatus(String userId, Integer status);
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
package com.emotionmuseum.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.entity.Conversation;
|
||||
import com.emotionmuseum.entity.Message;
|
||||
import com.emotionmuseum.mapper.ConversationMapper;
|
||||
import com.emotionmuseum.mapper.MessageMapper;
|
||||
import com.emotionmuseum.service.AiChatService;
|
||||
import com.emotionmuseum.service.CozeApiService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI聊天服务实现类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AiChatServiceImpl implements AiChatService {
|
||||
|
||||
@Autowired
|
||||
private ConversationMapper conversationMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
@Autowired
|
||||
private CozeApiService cozeApiService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<Message> sendMessage(String userId, String content, String conversationId) {
|
||||
try {
|
||||
// 验证会话是否存在
|
||||
Conversation conversation = null;
|
||||
if (StringUtils.hasText(conversationId)) {
|
||||
conversation = conversationMapper.selectById(conversationId);
|
||||
if (conversation == null) {
|
||||
return Result.error("会话不存在");
|
||||
}
|
||||
if (!conversation.getUserId().equals(userId)) {
|
||||
return Result.error("无权访问此会话");
|
||||
}
|
||||
} else {
|
||||
// 创建新会话
|
||||
conversation = new Conversation();
|
||||
conversation.setUserId(userId);
|
||||
conversation.setTitle("新对话");
|
||||
conversation.setConversationType("CHAT");
|
||||
conversation.setMessageCount(0);
|
||||
conversation.setStatus(0);
|
||||
conversation.setCreateTime(LocalDateTime.now());
|
||||
conversation.setUpdateTime(LocalDateTime.now());
|
||||
conversationMapper.insert(conversation);
|
||||
}
|
||||
|
||||
// 保存用户消息
|
||||
Message userMessage = new Message();
|
||||
userMessage.setConversationId(conversation.getId());
|
||||
userMessage.setSenderId(userId);
|
||||
userMessage.setSenderType("USER");
|
||||
userMessage.setContent(content);
|
||||
userMessage.setMessageType("TEXT");
|
||||
userMessage.setStatus(2); // 已发送
|
||||
userMessage.setCreateTime(LocalDateTime.now());
|
||||
userMessage.setUpdateTime(LocalDateTime.now());
|
||||
messageMapper.insert(userMessage);
|
||||
|
||||
// 异步调用AI获取回复
|
||||
String aiReply = getAiReply(userId, content, conversation.getId());
|
||||
|
||||
// 保存AI回复
|
||||
Message aiMessage = new Message();
|
||||
aiMessage.setConversationId(conversation.getId());
|
||||
aiMessage.setSenderId("AI");
|
||||
aiMessage.setSenderType("AI");
|
||||
aiMessage.setContent(aiReply);
|
||||
aiMessage.setMessageType("TEXT");
|
||||
aiMessage.setStatus(2); // 已发送
|
||||
aiMessage.setCreateTime(LocalDateTime.now());
|
||||
aiMessage.setUpdateTime(LocalDateTime.now());
|
||||
messageMapper.insert(aiMessage);
|
||||
|
||||
// 更新会话信息
|
||||
conversation.setMessageCount(conversation.getMessageCount() + 2);
|
||||
conversation.setLastMessageTime(LocalDateTime.now());
|
||||
conversation.setUpdateTime(LocalDateTime.now());
|
||||
conversationMapper.updateById(conversation);
|
||||
|
||||
log.info("消息发送成功,用户: {}, 会话: {}", userId, conversation.getId());
|
||||
return Result.success("消息发送成功", aiMessage);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("发送消息失败: {}", e.getMessage(), e);
|
||||
return Result.error("发送消息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Conversation> createConversation(String userId, String title) {
|
||||
try {
|
||||
Conversation conversation = new Conversation();
|
||||
conversation.setUserId(userId);
|
||||
conversation.setTitle(StringUtils.hasText(title) ? title : "新对话");
|
||||
conversation.setConversationType("CHAT");
|
||||
conversation.setMessageCount(0);
|
||||
conversation.setStatus(0);
|
||||
conversation.setCreateTime(LocalDateTime.now());
|
||||
conversation.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
conversationMapper.insert(conversation);
|
||||
|
||||
log.info("创建会话成功,用户: {}, 会话: {}", userId, conversation.getId());
|
||||
return Result.success("创建会话成功", conversation);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建会话失败: {}", e.getMessage(), e);
|
||||
return Result.error("创建会话失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IPage<Conversation>> getUserConversations(String userId, int page, int size) {
|
||||
try {
|
||||
Page<Conversation> pageParam = new Page<>(page, size);
|
||||
IPage<Conversation> conversations = conversationMapper.selectUserConversations(pageParam, userId);
|
||||
return Result.success("获取会话列表成功", conversations);
|
||||
} catch (Exception e) {
|
||||
log.error("获取会话列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取会话列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IPage<Message>> getConversationMessages(String conversationId, int page, int size) {
|
||||
try {
|
||||
Page<Message> pageParam = new Page<>(page, size);
|
||||
IPage<Message> messages = messageMapper.selectConversationMessages(pageParam, conversationId);
|
||||
return Result.success("获取消息列表成功", messages);
|
||||
} catch (Exception e) {
|
||||
log.error("获取消息列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取消息列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> deleteConversation(String userId, String conversationId) {
|
||||
try {
|
||||
Conversation conversation = conversationMapper.selectById(conversationId);
|
||||
if (conversation == null) {
|
||||
return Result.error("会话不存在");
|
||||
}
|
||||
if (!conversation.getUserId().equals(userId)) {
|
||||
return Result.error("无权删除此会话");
|
||||
}
|
||||
|
||||
// 删除会话下的所有消息
|
||||
QueryWrapper<Message> messageWrapper = new QueryWrapper<>();
|
||||
messageWrapper.eq("conversation_id", conversationId);
|
||||
messageMapper.delete(messageWrapper);
|
||||
|
||||
// 删除会话
|
||||
conversationMapper.deleteById(conversationId);
|
||||
|
||||
log.info("删除会话成功,用户: {}, 会话: {}", userId, conversationId);
|
||||
return Result.success("删除会话成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("删除会话失败: {}", e.getMessage(), e);
|
||||
return Result.error("删除会话失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Conversation> getConversationById(String conversationId) {
|
||||
try {
|
||||
Conversation conversation = conversationMapper.selectById(conversationId);
|
||||
if (conversation == null) {
|
||||
return Result.error("会话不存在");
|
||||
}
|
||||
return Result.success("获取会话详情成功", conversation);
|
||||
} catch (Exception e) {
|
||||
log.error("获取会话详情失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取会话详情失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> clearConversation(String userId, String conversationId) {
|
||||
try {
|
||||
Conversation conversation = conversationMapper.selectById(conversationId);
|
||||
if (conversation == null) {
|
||||
return Result.error("会话不存在");
|
||||
}
|
||||
if (!conversation.getUserId().equals(userId)) {
|
||||
return Result.error("无权清空此会话");
|
||||
}
|
||||
|
||||
// 删除会话下的所有消息
|
||||
QueryWrapper<Message> messageWrapper = new QueryWrapper<>();
|
||||
messageWrapper.eq("conversation_id", conversationId);
|
||||
messageMapper.delete(messageWrapper);
|
||||
|
||||
// 重置会话消息数量
|
||||
conversation.setMessageCount(0);
|
||||
conversation.setUpdateTime(LocalDateTime.now());
|
||||
conversationMapper.updateById(conversation);
|
||||
|
||||
log.info("清空会话成功,用户: {}, 会话: {}", userId, conversationId);
|
||||
return Result.success("清空会话成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("清空会话失败: {}", e.getMessage(), e);
|
||||
return Result.error("清空会话失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI回复
|
||||
*/
|
||||
private String getAiReply(String userId, String content, String conversationId) {
|
||||
try {
|
||||
Result<String> result = cozeApiService.sendMessageWithContext(content, userId, conversationId);
|
||||
if (result.getCode() == 200) {
|
||||
return result.getData();
|
||||
} else {
|
||||
log.error("AI回复失败: {}", result.getMessage());
|
||||
return "抱歉,我现在无法回复,请稍后再试。";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取AI回复时发生错误: {}", e.getMessage(), e);
|
||||
return "抱歉,系统出现错误,请稍后再试。";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
package com.emotionmuseum.service.impl;
|
||||
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.auth.LoginRequest;
|
||||
import com.emotionmuseum.dto.auth.LoginResponse;
|
||||
import com.emotionmuseum.dto.auth.RegisterRequest;
|
||||
import com.emotionmuseum.entity.User;
|
||||
import com.emotionmuseum.mapper.UserMapper;
|
||||
import com.emotionmuseum.service.AuthService;
|
||||
import com.emotionmuseum.util.JwtUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 认证服务实现类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Override
|
||||
public Result<LoginResponse> login(LoginRequest request) {
|
||||
try {
|
||||
// 参数验证
|
||||
if (!StringUtils.hasText(request.getUsername()) || !StringUtils.hasText(request.getPassword())) {
|
||||
return Result.error("用户名和密码不能为空");
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
User user = userMapper.findByUsername(request.getUsername());
|
||||
if (user == null) {
|
||||
user = userMapper.findByEmail(request.getUsername());
|
||||
}
|
||||
if (user == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
||||
return Result.error("密码错误");
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (user.getStatus() != 1) {
|
||||
return Result.error("用户已被禁用");
|
||||
}
|
||||
|
||||
// 生成令牌
|
||||
String accessToken = jwtUtil.generateToken(user.getId(), user.getUsername());
|
||||
String refreshToken = jwtUtil.generateToken(user.getId(), user.getUsername());
|
||||
|
||||
// 更新最后登录时间
|
||||
user.setLastLoginTime(LocalDateTime.now());
|
||||
userMapper.updateById(user);
|
||||
|
||||
// 构建响应
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setAccessToken(accessToken);
|
||||
response.setRefreshToken(refreshToken);
|
||||
response.setExpiresIn(86400L); // 24小时
|
||||
|
||||
LoginResponse.UserInfo userInfo = new LoginResponse.UserInfo();
|
||||
userInfo.setId(user.getId());
|
||||
userInfo.setUsername(user.getUsername());
|
||||
userInfo.setNickname(user.getNickname());
|
||||
userInfo.setEmail(user.getEmail());
|
||||
userInfo.setAvatar(user.getAvatar());
|
||||
userInfo.setUserType(user.getUserType());
|
||||
response.setUserInfo(userInfo);
|
||||
|
||||
// 将令牌存储到Redis
|
||||
String tokenKey = "token:" + user.getId();
|
||||
redisTemplate.opsForValue().set(tokenKey, accessToken, 24, TimeUnit.HOURS);
|
||||
|
||||
log.info("用户登录成功: {}", user.getUsername());
|
||||
return Result.success("登录成功", response);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("用户登录失败: {}", e.getMessage(), e);
|
||||
return Result.error("登录失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> register(RegisterRequest request) {
|
||||
try {
|
||||
// 参数验证
|
||||
if (!StringUtils.hasText(request.getUsername()) || !StringUtils.hasText(request.getPassword())) {
|
||||
return Result.error("用户名和密码不能为空");
|
||||
}
|
||||
|
||||
if (!request.getPassword().equals(request.getConfirmPassword())) {
|
||||
return Result.error("两次输入的密码不一致");
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
if (userMapper.countByUsername(request.getUsername()) > 0) {
|
||||
return Result.error("用户名已存在");
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if (StringUtils.hasText(request.getEmail()) && userMapper.countByEmail(request.getEmail()) > 0) {
|
||||
return Result.error("邮箱已被注册");
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在
|
||||
if (StringUtils.hasText(request.getPhone()) && userMapper.countByPhone(request.getPhone()) > 0) {
|
||||
return Result.error("手机号已被注册");
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
User user = new User();
|
||||
user.setUsername(request.getUsername());
|
||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||
user.setEmail(request.getEmail());
|
||||
user.setPhone(request.getPhone());
|
||||
user.setNickname(StringUtils.hasText(request.getNickname()) ? request.getNickname() : request.getUsername());
|
||||
user.setStatus(1);
|
||||
user.setUserType(0);
|
||||
user.setCreateTime(LocalDateTime.now());
|
||||
user.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
userMapper.insert(user);
|
||||
|
||||
log.info("用户注册成功: {}", user.getUsername());
|
||||
return Result.success("注册成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("用户注册失败: {}", e.getMessage(), e);
|
||||
return Result.error("注册失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> logout(String token) {
|
||||
try {
|
||||
if (StringUtils.hasText(token)) {
|
||||
String userId = jwtUtil.getUserIdFromToken(token);
|
||||
if (StringUtils.hasText(userId)) {
|
||||
// 从Redis中删除令牌
|
||||
String tokenKey = "token:" + userId;
|
||||
redisTemplate.delete(tokenKey);
|
||||
}
|
||||
}
|
||||
return Result.success("登出成功");
|
||||
} catch (Exception e) {
|
||||
log.error("用户登出失败: {}", e.getMessage(), e);
|
||||
return Result.error("登出失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> refreshToken(String refreshToken) {
|
||||
try {
|
||||
if (!StringUtils.hasText(refreshToken)) {
|
||||
return Result.error("刷新令牌不能为空");
|
||||
}
|
||||
|
||||
if (!jwtUtil.validateToken(refreshToken)) {
|
||||
return Result.error("刷新令牌无效或已过期");
|
||||
}
|
||||
|
||||
String userId = jwtUtil.getUserIdFromToken(refreshToken);
|
||||
String username = jwtUtil.getUsernameFromToken(refreshToken);
|
||||
|
||||
// 生成新的访问令牌
|
||||
String newAccessToken = jwtUtil.generateToken(userId, username);
|
||||
|
||||
// 更新Redis中的令牌
|
||||
String tokenKey = "token:" + userId;
|
||||
redisTemplate.opsForValue().set(tokenKey, newAccessToken, 24, TimeUnit.HOURS);
|
||||
|
||||
return Result.success("令牌刷新成功", newAccessToken);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("刷新令牌失败: {}", e.getMessage(), e);
|
||||
return Result.error("刷新令牌失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateToken(String token) {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 验证JWT令牌
|
||||
if (!jwtUtil.validateToken(token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查Redis中是否存在令牌
|
||||
String userId = jwtUtil.getUserIdFromToken(token);
|
||||
String tokenKey = "token:" + userId;
|
||||
String storedToken = (String) redisTemplate.opsForValue().get(tokenKey);
|
||||
|
||||
return token.equals(storedToken);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("验证令牌失败: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserIdFromToken(String token) {
|
||||
try {
|
||||
return jwtUtil.getUserIdFromToken(token);
|
||||
} catch (Exception e) {
|
||||
log.error("从令牌中获取用户ID失败: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
package com.emotionmuseum.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.comment.CommentRequest;
|
||||
import com.emotionmuseum.dto.comment.CommentResponse;
|
||||
import com.emotionmuseum.entity.Comment;
|
||||
import com.emotionmuseum.mapper.CommentMapper;
|
||||
import com.emotionmuseum.service.CommentService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 评论服务实现类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CommentServiceImpl implements CommentService {
|
||||
|
||||
@Autowired
|
||||
private CommentMapper commentMapper;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<CommentResponse> createComment(String userId, CommentRequest request) {
|
||||
try {
|
||||
Comment comment = new Comment();
|
||||
comment.setParentId(request.getParentId());
|
||||
comment.setContentType(request.getContentType());
|
||||
comment.setContentId(request.getContentId());
|
||||
comment.setUserId(userId);
|
||||
comment.setContent(request.getContent());
|
||||
comment.setLikeCount(0);
|
||||
comment.setReplyCount(0);
|
||||
comment.setStatus(1);
|
||||
comment.setCreateTime(LocalDateTime.now());
|
||||
comment.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
commentMapper.insert(comment);
|
||||
|
||||
CommentResponse response = buildCommentResponse(comment);
|
||||
log.info("创建评论成功,用户: {}, 评论: {}", userId, comment.getId());
|
||||
return Result.success("创建评论成功", response);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建评论失败: {}", e.getMessage(), e);
|
||||
return Result.error("创建评论失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IPage<CommentResponse>> getContentComments(String contentType, String contentId, int page, int size) {
|
||||
try {
|
||||
Page<Comment> pageParam = new Page<>(page, size);
|
||||
IPage<Comment> comments = commentMapper.selectContentComments(pageParam, contentType, contentId);
|
||||
|
||||
IPage<CommentResponse> responsePage = new Page<>(page, size);
|
||||
responsePage.setTotal(comments.getTotal());
|
||||
responsePage.setPages(comments.getPages());
|
||||
responsePage.setCurrent(comments.getCurrent());
|
||||
responsePage.setSize(comments.getSize());
|
||||
|
||||
List<CommentResponse> responses = comments.getRecords().stream()
|
||||
.map(this::buildCommentResponse)
|
||||
.collect(Collectors.toList());
|
||||
responsePage.setRecords(responses);
|
||||
|
||||
return Result.success("获取评论列表成功", responsePage);
|
||||
} catch (Exception e) {
|
||||
log.error("获取评论列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取评论列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<CommentResponse> getCommentById(String commentId) {
|
||||
try {
|
||||
Comment comment = commentMapper.selectById(commentId);
|
||||
if (comment == null) {
|
||||
return Result.error("评论不存在");
|
||||
}
|
||||
|
||||
CommentResponse response = buildCommentResponse(comment);
|
||||
return Result.success("获取评论详情成功", response);
|
||||
} catch (Exception e) {
|
||||
log.error("获取评论详情失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取评论详情失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> deleteComment(String userId, String commentId) {
|
||||
try {
|
||||
Comment comment = commentMapper.selectById(commentId);
|
||||
if (comment == null) {
|
||||
return Result.error("评论不存在");
|
||||
}
|
||||
if (!comment.getUserId().equals(userId)) {
|
||||
return Result.error("无权删除此评论");
|
||||
}
|
||||
|
||||
commentMapper.deleteById(commentId);
|
||||
log.info("删除评论成功,用户: {}, 评论: {}", userId, commentId);
|
||||
return Result.success("删除评论成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("删除评论失败: {}", e.getMessage(), e);
|
||||
return Result.error("删除评论失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> likeComment(String userId, String commentId) {
|
||||
try {
|
||||
Comment comment = commentMapper.selectById(commentId);
|
||||
if (comment == null) {
|
||||
return Result.error("评论不存在");
|
||||
}
|
||||
|
||||
comment.setLikeCount(comment.getLikeCount() + 1);
|
||||
comment.setUpdateTime(LocalDateTime.now());
|
||||
commentMapper.updateById(comment);
|
||||
|
||||
log.info("点赞评论成功,用户: {}, 评论: {}", userId, commentId);
|
||||
return Result.success("点赞成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("点赞评论失败: {}", e.getMessage(), e);
|
||||
return Result.error("点赞失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> unlikeComment(String userId, String commentId) {
|
||||
try {
|
||||
Comment comment = commentMapper.selectById(commentId);
|
||||
if (comment == null) {
|
||||
return Result.error("评论不存在");
|
||||
}
|
||||
|
||||
if (comment.getLikeCount() > 0) {
|
||||
comment.setLikeCount(comment.getLikeCount() - 1);
|
||||
comment.setUpdateTime(LocalDateTime.now());
|
||||
commentMapper.updateById(comment);
|
||||
}
|
||||
|
||||
log.info("取消点赞成功,用户: {}, 评论: {}", userId, commentId);
|
||||
return Result.success("取消点赞成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("取消点赞失败: {}", e.getMessage(), e);
|
||||
return Result.error("取消点赞失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IPage<CommentResponse>> getUserComments(String userId, int page, int size) {
|
||||
try {
|
||||
Page<Comment> pageParam = new Page<>(page, size);
|
||||
IPage<Comment> comments = commentMapper.selectPage(pageParam, null);
|
||||
|
||||
IPage<CommentResponse> responsePage = new Page<>(page, size);
|
||||
responsePage.setTotal(comments.getTotal());
|
||||
responsePage.setPages(comments.getPages());
|
||||
responsePage.setCurrent(comments.getCurrent());
|
||||
responsePage.setSize(comments.getSize());
|
||||
|
||||
List<CommentResponse> responses = comments.getRecords().stream()
|
||||
.map(this::buildCommentResponse)
|
||||
.collect(Collectors.toList());
|
||||
responsePage.setRecords(responses);
|
||||
|
||||
return Result.success("获取用户评论列表成功", responsePage);
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户评论列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取用户评论列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<CommentResponse>> getCommentReplies(String commentId) {
|
||||
try {
|
||||
List<Comment> replies = commentMapper.selectCommentReplies(commentId);
|
||||
List<CommentResponse> responses = replies.stream()
|
||||
.map(this::buildCommentResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success("获取评论回复列表成功", responses);
|
||||
} catch (Exception e) {
|
||||
log.error("获取评论回复列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取评论回复列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建评论响应DTO
|
||||
*/
|
||||
private CommentResponse buildCommentResponse(Comment comment) {
|
||||
CommentResponse response = new CommentResponse();
|
||||
response.setId(comment.getId());
|
||||
response.setParentId(comment.getParentId());
|
||||
response.setContentType(comment.getContentType());
|
||||
response.setContentId(comment.getContentId());
|
||||
response.setUserId(comment.getUserId());
|
||||
response.setContent(comment.getContent());
|
||||
response.setLikeCount(comment.getLikeCount());
|
||||
response.setReplyCount(comment.getReplyCount());
|
||||
response.setStatus(comment.getStatus());
|
||||
response.setCreateTime(comment.getCreateTime());
|
||||
response.setIsLiked(false);
|
||||
response.setReplies(new ArrayList<>());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
package com.emotionmuseum.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.emotionmuseum.config.CozeConfig;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.service.CozeApiService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Coze API服务实现类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CozeApiServiceImpl implements CozeApiService {
|
||||
|
||||
@Autowired
|
||||
private CozeConfig cozeConfig;
|
||||
|
||||
@Autowired
|
||||
private WebClient.Builder webClientBuilder;
|
||||
|
||||
@Override
|
||||
public Result<String> sendMessage(String message, String userId) {
|
||||
try {
|
||||
if (!StrUtil.isNotBlank(cozeConfig.getApiKey()) || !StrUtil.isNotBlank(cozeConfig.getBotId())) {
|
||||
return Result.error("Coze API配置不完整");
|
||||
}
|
||||
|
||||
if (!StrUtil.isNotBlank(message)) {
|
||||
return Result.error("消息内容不能为空");
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("bot_id", cozeConfig.getBotId());
|
||||
requestBody.put("user_id", userId);
|
||||
requestBody.put("query", message);
|
||||
requestBody.put("stream", false);
|
||||
|
||||
String response = webClientBuilder.build()
|
||||
.post()
|
||||
.uri(cozeConfig.getBaseUrl() + "/bot/chat")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + cozeConfig.getApiKey())
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.bodyValue(requestBody)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofMillis(cozeConfig.getTimeout()))
|
||||
.block();
|
||||
|
||||
if (StrUtil.isNotBlank(response)) {
|
||||
JSONObject jsonResponse = JSONUtil.parseObj(response);
|
||||
if (jsonResponse.getInt("code", -1) == 0) {
|
||||
JSONObject data = jsonResponse.getJSONObject("data");
|
||||
String reply = data.getStr("reply");
|
||||
log.info("Coze API调用成功,用户: {}, 消息: {}", userId, message);
|
||||
return Result.success("AI回复成功", reply);
|
||||
} else {
|
||||
String errorMsg = jsonResponse.getStr("message", "未知错误");
|
||||
log.error("Coze API调用失败: {}", errorMsg);
|
||||
return Result.error("AI回复失败: " + errorMsg);
|
||||
}
|
||||
} else {
|
||||
log.error("Coze API返回空响应");
|
||||
return Result.error("AI回复失败: 空响应");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("调用Coze API时发生错误: {}", e.getMessage(), e);
|
||||
return Result.error("AI回复失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> sendMessageWithContext(String message, String userId, String conversationId) {
|
||||
try {
|
||||
if (!StrUtil.isNotBlank(cozeConfig.getApiKey()) || !StrUtil.isNotBlank(cozeConfig.getBotId())) {
|
||||
return Result.error("Coze API配置不完整");
|
||||
}
|
||||
|
||||
if (!StrUtil.isNotBlank(message)) {
|
||||
return Result.error("消息内容不能为空");
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("bot_id", cozeConfig.getBotId());
|
||||
requestBody.put("user_id", userId);
|
||||
requestBody.put("query", message);
|
||||
requestBody.put("conversation_id", conversationId);
|
||||
requestBody.put("stream", false);
|
||||
|
||||
String response = webClientBuilder.build()
|
||||
.post()
|
||||
.uri(cozeConfig.getBaseUrl() + "/bot/chat")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + cozeConfig.getApiKey())
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.bodyValue(requestBody)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofMillis(cozeConfig.getTimeout()))
|
||||
.block();
|
||||
|
||||
if (StrUtil.isNotBlank(response)) {
|
||||
JSONObject jsonResponse = JSONUtil.parseObj(response);
|
||||
if (jsonResponse.getInt("code", -1) == 0) {
|
||||
JSONObject data = jsonResponse.getJSONObject("data");
|
||||
String reply = data.getStr("reply");
|
||||
log.info("Coze API调用成功,用户: {}, 会话: {}, 消息: {}", userId, conversationId, message);
|
||||
return Result.success("AI回复成功", reply);
|
||||
} else {
|
||||
String errorMsg = jsonResponse.getStr("message", "未知错误");
|
||||
log.error("Coze API调用失败: {}", errorMsg);
|
||||
return Result.error("AI回复失败: " + errorMsg);
|
||||
}
|
||||
} else {
|
||||
log.error("Coze API返回空响应");
|
||||
return Result.error("AI回复失败: 空响应");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("调用Coze API时发生错误: {}", e.getMessage(), e);
|
||||
return Result.error("AI回复失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> getBotInfo() {
|
||||
try {
|
||||
if (!StrUtil.isNotBlank(cozeConfig.getApiKey()) || !StrUtil.isNotBlank(cozeConfig.getBotId())) {
|
||||
return Result.error("Coze API配置不完整");
|
||||
}
|
||||
|
||||
String response = webClientBuilder.build()
|
||||
.get()
|
||||
.uri(cozeConfig.getBaseUrl() + "/bot/" + cozeConfig.getBotId())
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + cozeConfig.getApiKey())
|
||||
.retrieve()
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofMillis(cozeConfig.getTimeout()))
|
||||
.block();
|
||||
|
||||
if (StrUtil.isNotBlank(response)) {
|
||||
JSONObject jsonResponse = JSONUtil.parseObj(response);
|
||||
if (jsonResponse.getInt("code", -1) == 0) {
|
||||
JSONObject data = jsonResponse.getJSONObject("data");
|
||||
log.info("获取Bot信息成功");
|
||||
return Result.success("获取Bot信息成功", data.toString());
|
||||
} else {
|
||||
String errorMsg = jsonResponse.getStr("message", "未知错误");
|
||||
log.error("获取Bot信息失败: {}", errorMsg);
|
||||
return Result.error("获取Bot信息失败: " + errorMsg);
|
||||
}
|
||||
} else {
|
||||
log.error("获取Bot信息返回空响应");
|
||||
return Result.error("获取Bot信息失败: 空响应");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取Bot信息时发生错误: {}", e.getMessage(), e);
|
||||
return Result.error("获取Bot信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Boolean> checkConnection() {
|
||||
try {
|
||||
if (!StrUtil.isNotBlank(cozeConfig.getApiKey()) || !StrUtil.isNotBlank(cozeConfig.getBotId())) {
|
||||
return Result.error("Coze API配置不完整");
|
||||
}
|
||||
|
||||
// 尝试获取Bot信息来检查连接
|
||||
Result<String> botInfoResult = getBotInfo();
|
||||
if (botInfoResult.getCode() == 200) {
|
||||
log.info("Coze API连接正常");
|
||||
return Result.success("连接正常", true);
|
||||
} else {
|
||||
log.error("Coze API连接失败: {}", botInfoResult.getMessage());
|
||||
return Result.error("连接失败: " + botInfoResult.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("检查Coze API连接时发生错误: {}", e.getMessage(), e);
|
||||
return Result.error("连接检查失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
package com.emotionmuseum.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.dto.diary.DiaryPostRequest;
|
||||
import com.emotionmuseum.entity.DiaryPost;
|
||||
import com.emotionmuseum.mapper.DiaryPostMapper;
|
||||
import com.emotionmuseum.service.CozeApiService;
|
||||
import com.emotionmuseum.service.DiaryPostService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 日记服务实现类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DiaryPostServiceImpl implements DiaryPostService {
|
||||
|
||||
@Autowired
|
||||
private DiaryPostMapper diaryPostMapper;
|
||||
|
||||
@Autowired
|
||||
private CozeApiService cozeApiService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<DiaryPost> createDiary(String userId, DiaryPostRequest request) {
|
||||
try {
|
||||
DiaryPost diaryPost = new DiaryPost();
|
||||
diaryPost.setUserId(userId);
|
||||
diaryPost.setTitle(request.getTitle());
|
||||
diaryPost.setContent(request.getContent());
|
||||
diaryPost.setEmotionTags(request.getEmotionTags());
|
||||
diaryPost.setEmotionScore(request.getEmotionScore());
|
||||
diaryPost.setWeather(request.getWeather());
|
||||
diaryPost.setLocation(request.getLocation());
|
||||
diaryPost.setImages(request.getImages());
|
||||
diaryPost.setIsPublic(request.getIsPublic());
|
||||
diaryPost.setLikeCount(0);
|
||||
diaryPost.setCommentCount(0);
|
||||
diaryPost.setShareCount(0);
|
||||
diaryPost.setStatus(1); // 已发布
|
||||
diaryPost.setCreateTime(LocalDateTime.now());
|
||||
diaryPost.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
diaryPostMapper.insert(diaryPost);
|
||||
|
||||
// 异步生成AI点评
|
||||
generateAiComment(diaryPost);
|
||||
|
||||
log.info("创建日记成功,用户: {}, 日记: {}", userId, diaryPost.getId());
|
||||
return Result.success("创建日记成功", diaryPost);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("创建日记失败: {}", e.getMessage(), e);
|
||||
return Result.error("创建日记失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> updateDiary(String userId, String diaryId, DiaryPostRequest request) {
|
||||
try {
|
||||
DiaryPost diaryPost = diaryPostMapper.selectById(diaryId);
|
||||
if (diaryPost == null) {
|
||||
return Result.error("日记不存在");
|
||||
}
|
||||
if (!diaryPost.getUserId().equals(userId)) {
|
||||
return Result.error("无权修改此日记");
|
||||
}
|
||||
|
||||
diaryPost.setTitle(request.getTitle());
|
||||
diaryPost.setContent(request.getContent());
|
||||
diaryPost.setEmotionTags(request.getEmotionTags());
|
||||
diaryPost.setEmotionScore(request.getEmotionScore());
|
||||
diaryPost.setWeather(request.getWeather());
|
||||
diaryPost.setLocation(request.getLocation());
|
||||
diaryPost.setImages(request.getImages());
|
||||
diaryPost.setIsPublic(request.getIsPublic());
|
||||
diaryPost.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
diaryPostMapper.updateById(diaryPost);
|
||||
|
||||
log.info("更新日记成功,用户: {}, 日记: {}", userId, diaryId);
|
||||
return Result.success("更新日记成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("更新日记失败: {}", e.getMessage(), e);
|
||||
return Result.error("更新日记失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<DiaryPost> getDiaryById(String diaryId) {
|
||||
try {
|
||||
DiaryPost diaryPost = diaryPostMapper.selectById(diaryId);
|
||||
if (diaryPost == null) {
|
||||
return Result.error("日记不存在");
|
||||
}
|
||||
return Result.success("获取日记详情成功", diaryPost);
|
||||
} catch (Exception e) {
|
||||
log.error("获取日记详情失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取日记详情失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IPage<DiaryPost>> getUserDiaries(String userId, int page, int size) {
|
||||
try {
|
||||
Page<DiaryPost> pageParam = new Page<>(page, size);
|
||||
IPage<DiaryPost> diaries = diaryPostMapper.selectUserDiaries(pageParam, userId);
|
||||
return Result.success("获取用户日记列表成功", diaries);
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户日记列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取用户日记列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IPage<DiaryPost>> getPublicDiaries(int page, int size) {
|
||||
try {
|
||||
Page<DiaryPost> pageParam = new Page<>(page, size);
|
||||
IPage<DiaryPost> diaries = diaryPostMapper.selectPublicDiaries(pageParam);
|
||||
return Result.success("获取公开日记列表成功", diaries);
|
||||
} catch (Exception e) {
|
||||
log.error("获取公开日记列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取公开日记列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IPage<DiaryPost>> getDiariesByEmotionTag(String emotionTag, int page, int size) {
|
||||
try {
|
||||
Page<DiaryPost> pageParam = new Page<>(page, size);
|
||||
IPage<DiaryPost> diaries = diaryPostMapper.selectDiariesByEmotionTag(pageParam, emotionTag);
|
||||
return Result.success("获取情绪标签日记列表成功", diaries);
|
||||
} catch (Exception e) {
|
||||
log.error("获取情绪标签日记列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取情绪标签日记列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> deleteDiary(String userId, String diaryId) {
|
||||
try {
|
||||
DiaryPost diaryPost = diaryPostMapper.selectById(diaryId);
|
||||
if (diaryPost == null) {
|
||||
return Result.error("日记不存在");
|
||||
}
|
||||
if (!diaryPost.getUserId().equals(userId)) {
|
||||
return Result.error("无权删除此日记");
|
||||
}
|
||||
|
||||
diaryPostMapper.deleteById(diaryId);
|
||||
|
||||
log.info("删除日记成功,用户: {}, 日记: {}", userId, diaryId);
|
||||
return Result.success("删除日记成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("删除日记失败: {}", e.getMessage(), e);
|
||||
return Result.error("删除日记失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> likeDiary(String userId, String diaryId) {
|
||||
try {
|
||||
DiaryPost diaryPost = diaryPostMapper.selectById(diaryId);
|
||||
if (diaryPost == null) {
|
||||
return Result.error("日记不存在");
|
||||
}
|
||||
|
||||
diaryPost.setLikeCount(diaryPost.getLikeCount() + 1);
|
||||
diaryPost.setUpdateTime(LocalDateTime.now());
|
||||
diaryPostMapper.updateById(diaryPost);
|
||||
|
||||
log.info("点赞日记成功,用户: {}, 日记: {}", userId, diaryId);
|
||||
return Result.success("点赞成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("点赞日记失败: {}", e.getMessage(), e);
|
||||
return Result.error("点赞失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<String> unlikeDiary(String userId, String diaryId) {
|
||||
try {
|
||||
DiaryPost diaryPost = diaryPostMapper.selectById(diaryId);
|
||||
if (diaryPost == null) {
|
||||
return Result.error("日记不存在");
|
||||
}
|
||||
|
||||
if (diaryPost.getLikeCount() > 0) {
|
||||
diaryPost.setLikeCount(diaryPost.getLikeCount() - 1);
|
||||
diaryPost.setUpdateTime(LocalDateTime.now());
|
||||
diaryPostMapper.updateById(diaryPost);
|
||||
}
|
||||
|
||||
log.info("取消点赞成功,用户: {}, 日记: {}", userId, diaryId);
|
||||
return Result.success("取消点赞成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("取消点赞失败: {}", e.getMessage(), e);
|
||||
return Result.error("取消点赞失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> getAiComment(String userId, String diaryId) {
|
||||
try {
|
||||
DiaryPost diaryPost = diaryPostMapper.selectById(diaryId);
|
||||
if (diaryPost == null) {
|
||||
return Result.error("日记不存在");
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(diaryPost.getAiComment())) {
|
||||
return Result.success("获取AI点评成功", diaryPost.getAiComment());
|
||||
}
|
||||
|
||||
// 生成AI点评
|
||||
String aiComment = generateAiComment(diaryPost);
|
||||
return Result.success("获取AI点评成功", aiComment);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取AI点评失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取AI点评失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成AI点评
|
||||
*/
|
||||
private String generateAiComment(DiaryPost diaryPost) {
|
||||
try {
|
||||
String prompt = String.format(
|
||||
"请对以下日记进行情感分析和点评,要求:\n" +
|
||||
"1. 分析作者的情感状态\n" +
|
||||
"2. 提供积极正面的建议\n" +
|
||||
"3. 字数控制在200字以内\n" +
|
||||
"4. 语言温暖友善\n\n" +
|
||||
"日记标题:%s\n" +
|
||||
"日记内容:%s\n" +
|
||||
"情绪标签:%s\n" +
|
||||
"情绪评分:%d/10",
|
||||
diaryPost.getTitle(),
|
||||
diaryPost.getContent(),
|
||||
diaryPost.getEmotionTags(),
|
||||
diaryPost.getEmotionScore()
|
||||
);
|
||||
|
||||
Result<String> result = cozeApiService.sendMessage(prompt, diaryPost.getUserId());
|
||||
if (result.getCode() == 200) {
|
||||
String aiComment = result.getData();
|
||||
diaryPost.setAiComment(aiComment);
|
||||
diaryPost.setUpdateTime(LocalDateTime.now());
|
||||
diaryPostMapper.updateById(diaryPost);
|
||||
return aiComment;
|
||||
} else {
|
||||
log.error("生成AI点评失败: {}", result.getMessage());
|
||||
return "AI正在思考中,请稍后再试。";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("生成AI点评时发生错误: {}", e.getMessage(), e);
|
||||
return "AI点评生成失败,请稍后再试。";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package com.emotionmuseum.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.emotionmuseum.dto.Result;
|
||||
import com.emotionmuseum.entity.User;
|
||||
import com.emotionmuseum.mapper.UserMapper;
|
||||
import com.emotionmuseum.service.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户服务实现类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public Result<User> getUserById(String userId) {
|
||||
try {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
// 清除敏感信息
|
||||
user.setPassword(null);
|
||||
return Result.success("获取用户信息成功", user);
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取用户信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> updateUser(String userId, User user) {
|
||||
try {
|
||||
User existingUser = userMapper.selectById(userId);
|
||||
if (existingUser == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
// 只允许更新特定字段
|
||||
if (StringUtils.hasText(user.getNickname())) {
|
||||
existingUser.setNickname(user.getNickname());
|
||||
}
|
||||
if (StringUtils.hasText(user.getEmail())) {
|
||||
existingUser.setEmail(user.getEmail());
|
||||
}
|
||||
if (StringUtils.hasText(user.getPhone())) {
|
||||
existingUser.setPhone(user.getPhone());
|
||||
}
|
||||
if (StringUtils.hasText(user.getAvatar())) {
|
||||
existingUser.setAvatar(user.getAvatar());
|
||||
}
|
||||
if (StringUtils.hasText(user.getBio())) {
|
||||
existingUser.setBio(user.getBio());
|
||||
}
|
||||
if (user.getGender() != null) {
|
||||
existingUser.setGender(user.getGender());
|
||||
}
|
||||
if (user.getBirthday() != null) {
|
||||
existingUser.setBirthday(user.getBirthday());
|
||||
}
|
||||
|
||||
existingUser.setUpdateTime(LocalDateTime.now());
|
||||
userMapper.updateById(existingUser);
|
||||
|
||||
log.info("用户信息更新成功: {}", userId);
|
||||
return Result.success("用户信息更新成功");
|
||||
} catch (Exception e) {
|
||||
log.error("更新用户信息失败: {}", e.getMessage(), e);
|
||||
return Result.error("更新用户信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> changePassword(String userId, String oldPassword, String newPassword) {
|
||||
try {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
|
||||
return Result.error("原密码错误");
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
user.setPassword(passwordEncoder.encode(newPassword));
|
||||
user.setUpdateTime(LocalDateTime.now());
|
||||
userMapper.updateById(user);
|
||||
|
||||
log.info("用户密码修改成功: {}", userId);
|
||||
return Result.success("密码修改成功");
|
||||
} catch (Exception e) {
|
||||
log.error("修改密码失败: {}", e.getMessage(), e);
|
||||
return Result.error("修改密码失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<IPage<User>> getUserList(int page, int size) {
|
||||
try {
|
||||
Page<User> pageParam = new Page<>(page, size);
|
||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.orderByDesc("create_time");
|
||||
|
||||
IPage<User> userPage = userMapper.selectPage(pageParam, queryWrapper);
|
||||
|
||||
// 清除敏感信息
|
||||
userPage.getRecords().forEach(user -> user.setPassword(null));
|
||||
|
||||
return Result.success("获取用户列表成功", userPage);
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户列表失败: {}", e.getMessage(), e);
|
||||
return Result.error("获取用户列表失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> deleteUser(String userId) {
|
||||
try {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
userMapper.deleteById(userId);
|
||||
log.info("用户删除成功: {}", userId);
|
||||
return Result.success("用户删除成功");
|
||||
} catch (Exception e) {
|
||||
log.error("删除用户失败: {}", e.getMessage(), e);
|
||||
return Result.error("删除用户失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> toggleUserStatus(String userId, Integer status) {
|
||||
try {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
user.setStatus(status);
|
||||
user.setUpdateTime(LocalDateTime.now());
|
||||
userMapper.updateById(user);
|
||||
|
||||
String message = status == 1 ? "用户启用成功" : "用户禁用成功";
|
||||
log.info("用户状态更新成功: {} -> {}", userId, status);
|
||||
return Result.success(message);
|
||||
} catch (Exception e) {
|
||||
log.error("更新用户状态失败: {}", e.getMessage(), e);
|
||||
return Result.error("更新用户状态失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package com.emotionmuseum.util;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT工具类
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @version 1.0.0
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${emotion.jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${emotion.jwt.expiration}")
|
||||
private Long expiration;
|
||||
|
||||
/**
|
||||
* 生成JWT令牌
|
||||
*/
|
||||
public String generateToken(String userId, String username) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
return createToken(claims, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建令牌
|
||||
*/
|
||||
private String createToken(Map<String, Object> claims, String subject) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expiration);
|
||||
|
||||
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
|
||||
|
||||
return Jwts.builder()
|
||||
.claims(claims)
|
||||
.subject(subject)
|
||||
.issuedAt(now)
|
||||
.expiration(expiryDate)
|
||||
.signWith(key, Jwts.SIG.HS512)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户ID
|
||||
*/
|
||||
public String getUserIdFromToken(String token) {
|
||||
return getClaimFromToken(token, "userId", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
return getClaimFromToken(token, "username", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取过期时间
|
||||
*/
|
||||
public Date getExpirationDateFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取指定声明
|
||||
*/
|
||||
public <T> T getClaimFromToken(String token, String claimName, Class<T> requiredType) {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
return claims.get(claimName, requiredType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取指定声明
|
||||
*/
|
||||
public <T> T getClaimFromToken(String token, java.util.function.Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取所有声明
|
||||
*/
|
||||
private Claims getAllClaimsFromToken(String token) {
|
||||
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
|
||||
return Jwts.parser()
|
||||
.verifyWith(key)
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查令牌是否过期
|
||||
*/
|
||||
public Boolean isTokenExpired(String token) {
|
||||
try {
|
||||
final Date expiration = getExpirationDateFromToken(token);
|
||||
return expiration.before(new Date());
|
||||
} catch (Exception e) {
|
||||
log.error("检查令牌过期时发生错误: {}", e.getMessage());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证令牌
|
||||
*/
|
||||
public Boolean validateToken(String token) {
|
||||
try {
|
||||
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
|
||||
Jwts.parser()
|
||||
.verifyWith(key)
|
||||
.build()
|
||||
.parseSignedClaims(token);
|
||||
return !isTokenExpired(token);
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
log.error("验证令牌时发生错误: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
public String refreshToken(String token) {
|
||||
try {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
// 创建新的声明,因为Claims是不可变的
|
||||
Map<String, Object> newClaims = new HashMap<>(claims);
|
||||
newClaims.put("iat", new Date().getTime() / 1000);
|
||||
return createToken(newClaims, claims.getSubject());
|
||||
} catch (Exception e) {
|
||||
log.error("刷新令牌时发生错误: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
server:
|
||||
port: 19089
|
||||
servlet:
|
||||
context-path: /api
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/emotion_museum_local?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:password}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
hikari:
|
||||
maximum-pool-size: 20
|
||||
minimum-idle: 5
|
||||
connection-timeout: 30000
|
||||
idle-timeout: 600000
|
||||
max-lifetime: 1800000
|
||||
leak-detection-threshold: 60000
|
||||
connection-test-query: SELECT 1
|
||||
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
database: 0
|
||||
timeout: 2000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 20
|
||||
max-idle: 10
|
||||
min-idle: 5
|
||||
max-wait: 1000ms
|
||||
|
||||
# MyBatis Plus配置
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
cache-enabled: true
|
||||
lazy-loading-enabled: true
|
||||
aggressive-lazy-loading: false
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: assign_id
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.emotionmuseum: DEBUG
|
||||
org.springframework.security: WARN
|
||||
org.springframework.web: DEBUG
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
|
||||
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"
|
||||
file:
|
||||
name: logs/emotion-museum.log
|
||||
max-size: 100MB
|
||||
max-history: 30
|
||||
|
||||
# SpringDoc OpenAPI配置
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
info:
|
||||
title: 情绪博物馆API文档
|
||||
description: 情绪博物馆后端服务API文档
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: 情绪博物馆团队
|
||||
email: support@emotion-museum.com
|
||||
@@ -1,54 +0,0 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: ${SPRING_PROFILES_ACTIVE:local}
|
||||
application:
|
||||
name: emotion-museum
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: GMT+8
|
||||
serialization:
|
||||
write-dates-as-timestamps: false
|
||||
default-property-inclusion: non_null
|
||||
|
||||
# 管理端点配置
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics,prometheus
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
metrics:
|
||||
enabled: true
|
||||
metrics:
|
||||
export:
|
||||
prometheus:
|
||||
enabled: true
|
||||
|
||||
# 应用配置
|
||||
emotion:
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:emotion-museum-jwt-secret-key-2024}
|
||||
expiration: 86400000 # 24小时
|
||||
coze:
|
||||
api-key: ${COZE_API_KEY:}
|
||||
bot-id: ${COZE_BOT_ID:}
|
||||
base-url: https://www.coze.cn/api
|
||||
timeout: 30000
|
||||
max-retries: 3
|
||||
file:
|
||||
upload-path: ${FILE_UPLOAD_PATH:./uploads}
|
||||
max-size: 10485760 # 10MB
|
||||
|
||||
# 安全配置
|
||||
security:
|
||||
ignore-urls:
|
||||
- /auth/**
|
||||
- /health/**
|
||||
- /actuator/**
|
||||
- /ws/**
|
||||
- /ai/guest/**
|
||||
- /swagger-ui/**
|
||||
- /v3/api-docs/**
|
||||
- /favicon.ico
|
||||
@@ -1,856 +0,0 @@
|
||||
# 情绪博物馆后端功能模块技术规范说明
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
### 1.1 项目基本信息
|
||||
- **项目名称**: 情绪博物馆后端服务 (emotion-single)
|
||||
- **技术架构**: Spring Boot 2.7.18 单体架构
|
||||
- **Java版本**: JDK 17
|
||||
- **服务端口**: 19089
|
||||
- **API前缀**: /api
|
||||
- **项目类型**: 情感记录与AI对话平台
|
||||
|
||||
### 1.2 核心功能模块
|
||||
- **用户认证系统**: 登录、注册、JWT认证
|
||||
- **AI对话系统**: 基于Coze平台的智能对话
|
||||
- **情绪日记系统**: 日记发布、AI点评、社交分享
|
||||
- **WebSocket实时通信**: 实时聊天、消息推送
|
||||
- **数据分析系统**: 情绪分析、用户统计
|
||||
- **社区互动系统**: 评论、点赞、分享
|
||||
- **成就奖励系统**: 用户成长、奖励机制
|
||||
|
||||
## 2. 技术架构设计
|
||||
|
||||
### 2.1 技术栈选型
|
||||
|
||||
#### 2.1.1 核心框架
|
||||
```xml
|
||||
<!-- Spring Boot 2.7.18 -->
|
||||
<spring-boot.version>2.7.18</spring-boot.version>
|
||||
|
||||
<!-- 核心依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 2.1.2 数据存储
|
||||
```xml
|
||||
<!-- MySQL 8.0 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus 3.5.3.1 -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.3.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 2.1.3 安全认证
|
||||
```xml
|
||||
<!-- JWT 0.11.5 -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 2.2 项目结构设计
|
||||
|
||||
```
|
||||
src/main/java/com/emotion/
|
||||
├── EmotionSimpleApplication.java # 启动类
|
||||
├── config/ # 配置类
|
||||
│ ├── AsyncConfig.java # 异步配置
|
||||
│ ├── MybatisPlusConfig.java # MyBatis-Plus配置
|
||||
│ ├── RedisConfig.java # Redis配置
|
||||
│ ├── WebSocketConfig.java # WebSocket配置
|
||||
│ ├── SecurityConfig.java # Spring Security配置
|
||||
│ ├── IdGeneratorConfig.java # ID生成器配置
|
||||
│ └── WebMvcConfig.java # Web MVC配置
|
||||
├── controller/ # 控制器层 (24个控制器)
|
||||
│ ├── AuthController.java # 认证控制器
|
||||
│ ├── AiChatController.java # AI聊天控制器
|
||||
│ ├── DiaryPostController.java # 日记控制器
|
||||
│ ├── UserController.java # 用户控制器
|
||||
│ ├── WebSocketController.java # WebSocket控制器
|
||||
│ ├── MessageController.java # 消息控制器
|
||||
│ ├── ConversationController.java # 会话控制器
|
||||
│ ├── EmotionAnalysisController.java # 情绪分析控制器
|
||||
│ ├── CommunityPostController.java # 社区帖子控制器
|
||||
│ ├── CommentController.java # 评论控制器
|
||||
│ ├── AchievementController.java # 成就控制器
|
||||
│ ├── UserStatsController.java # 用户统计控制器
|
||||
│ ├── RewardController.java # 奖励控制器
|
||||
│ ├── GuestUserController.java # 访客用户控制器
|
||||
│ ├── CozeApiCallController.java # Coze API调用控制器
|
||||
│ ├── EmotionRecordController.java # 情绪记录控制器
|
||||
│ ├── TopicInteractionController.java # 话题互动控制器
|
||||
│ ├── GrowthTopicController.java # 成长话题控制器
|
||||
│ ├── DiaryCommentController.java # 日记评论控制器
|
||||
│ ├── EmotionSummaryController.java # 情绪总结控制器
|
||||
│ ├── TokenController.java # Token控制器
|
||||
│ ├── ChatWebSocketController.java # 聊天WebSocket控制器
|
||||
│ └── HealthController.java # 健康检查控制器
|
||||
├── service/ # 服务层 (20个服务)
|
||||
│ ├── AuthService.java # 认证服务
|
||||
│ ├── AiChatService.java # AI聊天服务
|
||||
│ ├── DiaryPostService.java # 日记服务
|
||||
│ ├── UserService.java # 用户服务
|
||||
│ ├── WebSocketService.java # WebSocket服务
|
||||
│ ├── MessageService.java # 消息服务
|
||||
│ ├── ConversationService.java # 会话服务
|
||||
│ ├── EmotionAnalysisService.java # 情绪分析服务
|
||||
│ ├── CommunityPostService.java # 社区帖子服务
|
||||
│ ├── CommentService.java # 评论服务
|
||||
│ ├── AchievementService.java # 成就服务
|
||||
│ ├── UserStatsService.java # 用户统计服务
|
||||
│ ├── RewardService.java # 奖励服务
|
||||
│ ├── GuestUserService.java # 访客用户服务
|
||||
│ ├── CozeApiCallService.java # Coze API调用服务
|
||||
│ ├── EmotionRecordService.java # 情绪记录服务
|
||||
│ ├── TopicInteractionService.java # 话题互动服务
|
||||
│ ├── GrowthTopicService.java # 成长话题服务
|
||||
│ ├── DiaryCommentService.java # 日记评论服务
|
||||
│ └── TokenService.java # Token服务
|
||||
├── mapper/ # 数据访问层
|
||||
├── entity/ # 实体类 (19个实体)
|
||||
│ ├── User.java # 用户实体
|
||||
│ ├── DiaryPost.java # 日记实体
|
||||
│ ├── Message.java # 消息实体
|
||||
│ ├── Conversation.java # 会话实体
|
||||
│ ├── Comment.java # 评论实体
|
||||
│ ├── CommunityPost.java # 社区帖子实体
|
||||
│ ├── Achievement.java # 成就实体
|
||||
│ ├── Reward.java # 奖励实体
|
||||
│ ├── GuestUser.java # 访客用户实体
|
||||
│ ├── EmotionRecord.java # 情绪记录实体
|
||||
│ ├── EmotionAnalysis.java # 情绪分析实体
|
||||
│ ├── UserStats.java # 用户统计实体
|
||||
│ ├── GrowthTopic.java # 成长话题实体
|
||||
│ ├── TopicInteraction.java # 话题互动实体
|
||||
│ ├── LocationPin.java # 位置标记实体
|
||||
│ ├── DiaryComment.java # 日记评论实体
|
||||
│ └── CozeApiCall.java # Coze API调用记录实体
|
||||
├── dto/ # 数据传输对象
|
||||
│ ├── request/ # 请求对象
|
||||
│ └── response/ # 响应对象
|
||||
├── common/ # 公共组件
|
||||
│ ├── BaseEntity.java # 基础实体
|
||||
│ ├── BasePageRequest.java # 基础分页请求
|
||||
│ ├── PageResult.java # 分页结果
|
||||
│ └── Result.java # 统一返回结果
|
||||
├── config/ # 配置类
|
||||
├── interceptor/ # 拦截器
|
||||
├── handler/ # 处理器
|
||||
├── exception/ # 异常处理
|
||||
└── util/ # 工具类
|
||||
```
|
||||
|
||||
## 3. 核心功能模块详解
|
||||
|
||||
### 3.1 用户认证系统 (AuthController)
|
||||
|
||||
#### 3.1.1 功能概述
|
||||
提供完整的用户认证服务,包括登录、注册、Token管理、验证码等功能。
|
||||
|
||||
#### 3.1.2 核心接口
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
// 用户登录
|
||||
@PostMapping("/login")
|
||||
public Result<AuthResponse> login(@Valid @RequestBody LoginRequest request)
|
||||
|
||||
// 用户注册
|
||||
@PostMapping("/register")
|
||||
public Result<AuthResponse> register(@Valid @RequestBody RegisterRequest request)
|
||||
|
||||
// 获取当前用户信息
|
||||
@GetMapping("/user/info")
|
||||
public Result<UserInfoResponse> getCurrentUserInfo(HttpServletRequest request)
|
||||
|
||||
// 生成验证码
|
||||
@GetMapping("/captcha")
|
||||
public Result<CaptchaResponse> generateCaptcha()
|
||||
|
||||
// 用户登出
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout(HttpServletRequest request)
|
||||
|
||||
// 刷新访问令牌
|
||||
@PostMapping("/refresh")
|
||||
public Result<AuthResponse> refreshToken(@Valid @RequestBody RefreshTokenRequest request)
|
||||
|
||||
// 验证访问令牌
|
||||
@GetMapping("/validate")
|
||||
public Result<Boolean> validateToken(HttpServletRequest request)
|
||||
|
||||
// 检查账号是否存在
|
||||
@GetMapping("/check-account")
|
||||
public Result<Boolean> checkAccount(@RequestParam String account)
|
||||
|
||||
// 检查邮箱是否存在
|
||||
@GetMapping("/check-email")
|
||||
public Result<Boolean> checkEmail(@RequestParam String email)
|
||||
|
||||
// 检查手机号是否存在
|
||||
@GetMapping("/check-phone")
|
||||
public Result<Boolean> checkPhone(@RequestParam String phone)
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.3 技术特点
|
||||
- **JWT认证**: 使用JWT进行无状态认证
|
||||
- **验证码支持**: 图形验证码生成和验证
|
||||
- **Token刷新**: 支持访问令牌自动刷新
|
||||
- **参数校验**: 使用@Valid进行请求参数校验
|
||||
- **统一返回**: 使用Result<T>统一返回格式
|
||||
|
||||
### 3.2 AI对话系统 (AiChatController)
|
||||
|
||||
#### 3.2.1 功能概述
|
||||
基于Coze平台的AI对话服务,支持智能聊天、对话总结、访客聊天等功能。
|
||||
|
||||
#### 3.2.2 核心接口
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/ai")
|
||||
public class AiChatController {
|
||||
|
||||
// 发送聊天消息
|
||||
@PostMapping("/chat")
|
||||
public Result<AiChatResponse> sendChatMessage(@Valid @RequestBody AiChatRequest request)
|
||||
|
||||
// 生成对话总结
|
||||
@PostMapping("/summary")
|
||||
public Result<AiSummaryResponse> generateSummary(@Valid @RequestBody AiSummaryRequest request)
|
||||
|
||||
// 获取AI服务状态
|
||||
@GetMapping("/status")
|
||||
public Result<AiStatusResponse> getServiceStatus()
|
||||
|
||||
// 获取聊天统计
|
||||
@GetMapping("/stats")
|
||||
public Result<ChatStatsResponse> getChatStats(@RequestParam(required = false) String userId,
|
||||
@RequestParam(required = false) String conversationId)
|
||||
|
||||
// 访客聊天
|
||||
@PostMapping("/guest/chat")
|
||||
public Result<GuestChatResponse> guestChat(@Valid @RequestBody GuestChatRequest request,
|
||||
HttpServletRequest httpRequest)
|
||||
|
||||
// 获取访客用户信息
|
||||
@GetMapping("/guest/user/info")
|
||||
public Result<GuestUserInfoResponse> getGuestUserInfo(HttpServletRequest request)
|
||||
|
||||
// 创建会话
|
||||
@PostMapping("/conversation/create")
|
||||
public Result<ConversationResponse> createConversation(@Valid @RequestBody ConversationCreateRequest request,
|
||||
HttpServletRequest httpRequest)
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.3 技术特点
|
||||
- **Coze集成**: 集成Coze AI平台进行智能对话
|
||||
- **会话管理**: 支持多会话管理和历史记录
|
||||
- **访客支持**: 支持未登录用户的AI对话
|
||||
- **异步处理**: 支持异步AI调用和响应
|
||||
- **统计分析**: 提供聊天数据统计分析
|
||||
|
||||
### 3.3 情绪日记系统 (DiaryPostController)
|
||||
|
||||
#### 3.3.1 功能概述
|
||||
用户情绪日记的发布、管理、AI点评、社交分享等完整功能。
|
||||
|
||||
#### 3.3.2 核心接口
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/diary-post")
|
||||
public class DiaryPostController {
|
||||
|
||||
// 分页查询日记
|
||||
@GetMapping("/page")
|
||||
public Result<PageResult<DiaryPostResponse>> getPage(@Validated BasePageRequest request)
|
||||
|
||||
// 根据用户ID分页查询日记
|
||||
@GetMapping("/user/{userId}/page")
|
||||
public Result<PageResult<DiaryPostResponse>> getPageByUserId(@PathVariable String userId,
|
||||
@Validated BasePageRequest request)
|
||||
|
||||
// 根据用户ID查询公开日记
|
||||
@GetMapping("/user/{userId}/public/page")
|
||||
public Result<PageResult<DiaryPostResponse>> getPublicPageByUserId(@PathVariable String userId,
|
||||
@Validated BasePageRequest request)
|
||||
|
||||
// 查询精选日记
|
||||
@GetMapping("/featured/page")
|
||||
public Result<PageResult<DiaryPostResponse>> getFeaturedPage(@Validated BasePageRequest request)
|
||||
|
||||
// 根据ID查询日记
|
||||
@GetMapping("/{id}")
|
||||
public Result<DiaryPostResponse> getById(@PathVariable String id)
|
||||
|
||||
// 创建日记
|
||||
@PostMapping
|
||||
public Result<DiaryPostResponse> create(@Valid @RequestBody DiaryPostCreateRequest request)
|
||||
|
||||
// 发布日记
|
||||
@PostMapping("/publish")
|
||||
public Result<DiaryPostResponse> publish(@Valid @RequestBody DiaryPostCreateRequest request)
|
||||
|
||||
// 更新日记
|
||||
@PutMapping("/{id}")
|
||||
public Result<DiaryPostResponse> update(@PathVariable String id, @Valid @RequestBody DiaryPostUpdateRequest request)
|
||||
|
||||
// 删除日记
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> delete(@PathVariable String id)
|
||||
|
||||
// 软删除日记
|
||||
@DeleteMapping("/{id}/soft")
|
||||
public Result<Void> softDelete(@PathVariable String id)
|
||||
|
||||
// 恢复日记
|
||||
@PutMapping("/{id}/restore")
|
||||
public Result<Void> restore(@PathVariable String id)
|
||||
|
||||
// 点赞日记
|
||||
@PostMapping("/{id}/like")
|
||||
public Result<Void> like(@PathVariable String id)
|
||||
|
||||
// 取消点赞
|
||||
@DeleteMapping("/{id}/like")
|
||||
public Result<Void> unlike(@PathVariable String id)
|
||||
|
||||
// 分享日记
|
||||
@PostMapping("/{id}/share")
|
||||
public Result<Void> share(@PathVariable String id)
|
||||
|
||||
// 设置精选状态
|
||||
@PutMapping("/{id}/featured/{featured}")
|
||||
public Result<Void> setFeatured(@PathVariable String id, @PathVariable Integer featured)
|
||||
|
||||
// 设置优先级
|
||||
@PutMapping("/{id}/priority/{priority}")
|
||||
public Result<Void> setPriority(@PathVariable String id, @PathVariable Integer priority)
|
||||
|
||||
// 统计用户日记数量
|
||||
@GetMapping("/user/{userId}/count")
|
||||
public Result<Long> countByUserId(@PathVariable String userId)
|
||||
|
||||
// 统计用户公开日记数量
|
||||
@GetMapping("/user/{userId}/public/count")
|
||||
public Result<Long> countPublicByUserId(@PathVariable String userId)
|
||||
|
||||
// 统计精选日记数量
|
||||
@GetMapping("/featured/count")
|
||||
public Result<Long> countFeatured()
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.3 技术特点
|
||||
- **分页查询**: 支持灵活的分页查询
|
||||
- **软删除**: 支持数据软删除和恢复
|
||||
- **权限控制**: 支持公开/私密日记管理
|
||||
- **社交功能**: 支持点赞、分享等社交功能
|
||||
- **AI点评**: 集成AI自动点评功能
|
||||
- **数据统计**: 提供丰富的统计功能
|
||||
|
||||
### 3.4 WebSocket实时通信系统
|
||||
|
||||
#### 3.4.1 功能概述
|
||||
基于Spring WebSocket的实时通信系统,支持AI对话、消息推送、在线状态管理。
|
||||
|
||||
#### 3.4.2 核心组件
|
||||
```java
|
||||
// WebSocket配置
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||
config.enableSimpleBroker("/topic", "/queue", "/user");
|
||||
config.setApplicationDestinationPrefixes("/app");
|
||||
config.setUserDestinationPrefix("/user");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/ws/chat")
|
||||
.setAllowedOriginPatterns("*")
|
||||
.withSockJS();
|
||||
|
||||
registry.addEndpoint("/ws/chat")
|
||||
.setAllowedOriginPatterns("*");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureClientInboundChannel(ChannelRegistration registration) {
|
||||
registration.interceptors(webSocketAuthInterceptor);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4.3 技术特点
|
||||
- **STOMP协议**: 使用STOMP消息协议
|
||||
- **Token认证**: 支持WebSocket连接时的Token认证
|
||||
- **消息路由**: 支持点对点和广播消息
|
||||
- **会话管理**: 支持用户会话隔离
|
||||
- **心跳检测**: 支持连接心跳检测
|
||||
|
||||
### 3.5 数据分析系统
|
||||
|
||||
#### 3.5.1 情绪分析 (EmotionAnalysisController)
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/emotion-analysis")
|
||||
public class EmotionAnalysisController {
|
||||
|
||||
// 分析用户情绪趋势
|
||||
@GetMapping("/user/{userId}/trend")
|
||||
public Result<EmotionTrendResponse> analyzeUserEmotionTrend(@PathVariable String userId,
|
||||
@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate)
|
||||
|
||||
// 分析日记情绪
|
||||
@PostMapping("/diary")
|
||||
public Result<EmotionAnalysisResponse> analyzeDiaryEmotion(@Valid @RequestBody DiaryEmotionAnalysisRequest request)
|
||||
|
||||
// 获取情绪统计
|
||||
@GetMapping("/user/{userId}/stats")
|
||||
public Result<EmotionStatsResponse> getEmotionStats(@PathVariable String userId)
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.5.2 用户统计 (UserStatsController)
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/user-stats")
|
||||
public class UserStatsController {
|
||||
|
||||
// 获取用户成长数据
|
||||
@GetMapping("/user/{userId}/growth")
|
||||
public Result<UserGrowthResponse> getUserGrowth(@PathVariable String userId)
|
||||
|
||||
// 获取用户活跃度
|
||||
@GetMapping("/user/{userId}/activity")
|
||||
public Result<UserActivityResponse> getUserActivity(@PathVariable String userId)
|
||||
|
||||
// 获取用户成就统计
|
||||
@GetMapping("/user/{userId}/achievements")
|
||||
public Result<UserAchievementsResponse> getUserAchievements(@PathVariable String userId)
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 数据模型设计
|
||||
|
||||
### 4.1 核心实体关系
|
||||
|
||||
#### 4.1.1 用户相关实体
|
||||
```java
|
||||
// 用户实体
|
||||
@Entity
|
||||
public class User {
|
||||
private String id; // 用户ID
|
||||
private String username; // 用户名
|
||||
private String email; // 邮箱
|
||||
private String phone; // 手机号
|
||||
private String avatar; // 头像
|
||||
private String nickname; // 昵称
|
||||
private Integer status; // 状态
|
||||
private LocalDateTime createTime; // 创建时间
|
||||
private LocalDateTime updateTime; // 更新时间
|
||||
}
|
||||
|
||||
// 访客用户实体
|
||||
@Entity
|
||||
public class GuestUser {
|
||||
private String id; // 访客ID
|
||||
private String sessionId; // 会话ID
|
||||
private String ipAddress; // IP地址
|
||||
private String userAgent; // 用户代理
|
||||
private LocalDateTime createTime; // 创建时间
|
||||
private LocalDateTime lastActiveTime; // 最后活跃时间
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.1.2 内容相关实体
|
||||
```java
|
||||
// 日记实体
|
||||
@Entity
|
||||
public class DiaryPost {
|
||||
private String id; // 日记ID
|
||||
private String userId; // 用户ID
|
||||
private String title; // 标题
|
||||
private String content; // 内容
|
||||
private String aiComment; // AI点评
|
||||
private Integer visibility; // 可见性
|
||||
private Integer featured; // 精选状态
|
||||
private Integer priority; // 优先级
|
||||
private Integer likeCount; // 点赞数
|
||||
private Integer shareCount; // 分享数
|
||||
private LocalDateTime createTime; // 创建时间
|
||||
private LocalDateTime updateTime; // 更新时间
|
||||
}
|
||||
|
||||
// 会话实体
|
||||
@Entity
|
||||
public class Conversation {
|
||||
private String id; // 会话ID
|
||||
private String userId; // 用户ID
|
||||
private String title; // 会话标题
|
||||
private String summary; // 会话总结
|
||||
private Integer messageCount; // 消息数量
|
||||
private LocalDateTime createTime; // 创建时间
|
||||
private LocalDateTime updateTime; // 更新时间
|
||||
}
|
||||
|
||||
// 消息实体
|
||||
@Entity
|
||||
public class Message {
|
||||
private String id; // 消息ID
|
||||
private String conversationId; // 会话ID
|
||||
private String senderId; // 发送者ID
|
||||
private String senderType; // 发送者类型
|
||||
private String content; // 消息内容
|
||||
private String messageType; // 消息类型
|
||||
private Integer status; // 消息状态
|
||||
private LocalDateTime createTime; // 创建时间
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.1.3 社交相关实体
|
||||
```java
|
||||
// 社区帖子实体
|
||||
@Entity
|
||||
public class CommunityPost {
|
||||
private String id; // 帖子ID
|
||||
private String userId; // 用户ID
|
||||
private String title; // 标题
|
||||
private String content; // 内容
|
||||
private String category; // 分类
|
||||
private Integer likeCount; // 点赞数
|
||||
private Integer commentCount; // 评论数
|
||||
private Integer shareCount; // 分享数
|
||||
private LocalDateTime createTime; // 创建时间
|
||||
}
|
||||
|
||||
// 评论实体
|
||||
@Entity
|
||||
public class Comment {
|
||||
private String id; // 评论ID
|
||||
private String userId; // 用户ID
|
||||
private String targetId; // 目标ID
|
||||
private String targetType; // 目标类型
|
||||
private String content; // 评论内容
|
||||
private String parentId; // 父评论ID
|
||||
private Integer likeCount; // 点赞数
|
||||
private LocalDateTime createTime; // 创建时间
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 数据访问层设计
|
||||
|
||||
#### 4.2.1 MyBatis Plus配置
|
||||
```java
|
||||
@Configuration
|
||||
@MapperScan("com.emotion.mapper")
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2.2 基础实体设计
|
||||
```java
|
||||
@MappedSuperclass
|
||||
@Data
|
||||
public abstract class BaseEntity {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Integer isDeleted;
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 安全认证设计
|
||||
|
||||
### 5.1 JWT认证机制
|
||||
|
||||
#### 5.1.1 JWT配置
|
||||
```yaml
|
||||
# application.yml
|
||||
emotion:
|
||||
jwt:
|
||||
secret: EmotionMuseumJWTSecretKey2025ForAuthenticationAndAuthorizationSecureEnoughForHS512Algorithm
|
||||
expiration: 86400000 # 24小时
|
||||
header: Authorization
|
||||
prefix: "Bearer "
|
||||
```
|
||||
|
||||
#### 5.1.2 JWT工具类
|
||||
```java
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${emotion.jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${emotion.jwt.expiration}")
|
||||
private Long expiration;
|
||||
|
||||
// 生成Token
|
||||
public String generateToken(String userId, String username) {
|
||||
return Jwts.builder()
|
||||
.setSubject(userId)
|
||||
.claim("username", username)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(SignatureAlgorithm.HS512, secret)
|
||||
.compact();
|
||||
}
|
||||
|
||||
// 验证Token
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 从Token中获取用户ID
|
||||
public String getUserIdFromToken(String token) {
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
return claims.getSubject();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Spring Security配置
|
||||
|
||||
#### 5.2.1 安全配置
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf().disable()
|
||||
.cors().and()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/auth/**", "/health/**", "/actuator/**").permitAll()
|
||||
.antMatchers("/ai/guest/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2.2 JWT认证过滤器
|
||||
```java
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
String token = extractTokenFromRequest(request);
|
||||
|
||||
if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
|
||||
String userId = jwtUtil.getUserIdFromToken(token);
|
||||
String username = jwtUtil.getUsernameFromToken(token);
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userId, null,
|
||||
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 缓存设计
|
||||
|
||||
### 6.1 Redis配置
|
||||
```java
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
// 设置序列化器
|
||||
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
|
||||
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
|
||||
serializer.setObjectMapper(mapper);
|
||||
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(serializer);
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 缓存策略
|
||||
- **用户信息缓存**: 用户基本信息缓存,TTL 30分钟
|
||||
- **会话缓存**: 用户会话信息缓存,TTL 24小时
|
||||
- **验证码缓存**: 验证码缓存,TTL 5分钟
|
||||
- **热点数据缓存**: 热门日记、评论等缓存,TTL 1小时
|
||||
|
||||
## 7. 异步处理设计
|
||||
|
||||
### 7.1 异步配置
|
||||
```java
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig implements AsyncConfigurer {
|
||||
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(10);
|
||||
executor.setMaxPoolSize(50);
|
||||
executor.setQueueCapacity(200);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("emotion-async-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new SimpleAsyncUncaughtExceptionHandler();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 异步应用场景
|
||||
- **AI调用**: AI对话和总结生成
|
||||
- **消息推送**: 实时消息推送
|
||||
- **数据统计**: 用户行为统计分析
|
||||
- **文件处理**: 图片上传和处理
|
||||
|
||||
## 8. 统一返回结果设计
|
||||
|
||||
### 8.1 返回结果封装
|
||||
```java
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Result<T> {
|
||||
private Integer code; // 状态码
|
||||
private String message; // 消息
|
||||
private T data; // 数据
|
||||
private Long timestamp; // 时间戳
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(200, "操作成功", null, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(200, "操作成功", data, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(String message, T data) {
|
||||
return new Result<>(200, message, data, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(Integer code, String message) {
|
||||
return new Result<>(code, message, null, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 分页结果封装
|
||||
```java
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class PageResult<T> {
|
||||
private Long current; // 当前页
|
||||
private Long size; // 页大小
|
||||
private Long total; // 总记录数
|
||||
private Long pages; // 总页数
|
||||
private List<T> records; // 数据列表
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 异常处理设计
|
||||
|
||||
### 9.1 全局异常处理器
|
||||
```java
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Result<Void> handleBusinessException(BusinessExceptio
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,724 +0,0 @@
|
||||
# 情绪博物馆后端重构计划
|
||||
|
||||
## 1. 重构概述
|
||||
|
||||
### 1.1 重构目标
|
||||
基于现有的Spring Boot 2.7.18单体架构,通过系统性的重构升级到Spring Boot 3.4.8版本,全面提升系统的性能、安全性、可维护性和扩展性,同时保持业务功能的完整性和稳定性。
|
||||
|
||||
### 1.2 重构原则
|
||||
- **业务连续性**: 确保重构过程中业务功能不受影响
|
||||
- **渐进式重构**: 分阶段、分模块进行重构,降低风险
|
||||
- **向后兼容**: 保持现有API接口的兼容性
|
||||
- **性能优先**: 充分利用新版本的技术优势
|
||||
- **安全第一**: 采用最新的安全标准和最佳实践
|
||||
- **质量保证**: 每个阶段都要进行充分的测试验证
|
||||
|
||||
### 1.3 重构范围
|
||||
- **核心框架**: Spring Boot 2.7.18 → 3.4.8
|
||||
- **Java版本**: JDK 17 → JDK 21 (LTS)
|
||||
- **安全框架**: Spring Security 5.x → 6.x
|
||||
- **数据访问**: MyBatis-Plus 3.5.3.1 → 3.5.5
|
||||
- **JWT库**: 0.11.5 → 0.12.3
|
||||
- **API文档**: Swagger → SpringDoc OpenAPI 3
|
||||
- **AI集成**: 优化Coze API调用方式
|
||||
|
||||
## 2. 现状分析
|
||||
|
||||
### 2.1 当前系统架构
|
||||
- **技术栈**: Spring Boot 2.7.18 + JDK 17
|
||||
- **架构模式**: 单体架构
|
||||
- **核心模块**: 24个控制器,涵盖认证、AI对话、日记、社区等功能
|
||||
- **数据存储**: MySQL 8.0 + Redis
|
||||
- **安全认证**: JWT + Spring Security 5.x
|
||||
- **实时通信**: WebSocket + STOMP
|
||||
|
||||
### 2.2 存在的问题
|
||||
1. **技术栈老旧**: Spring Boot 2.7.18已接近生命周期末期
|
||||
2. **安全风险**: 旧版本存在已知安全漏洞
|
||||
3. **性能瓶颈**: 缺乏现代化的性能优化特性
|
||||
4. **维护困难**: 代码结构需要优化,缺乏统一规范
|
||||
5. **扩展性差**: 单体架构限制了系统的扩展能力
|
||||
|
||||
### 2.3 重构收益
|
||||
1. **性能提升**: 预期响应时间提升20%以上
|
||||
2. **安全增强**: 采用最新的安全特性和标准
|
||||
3. **开发效率**: 使用最新的Java特性和工具
|
||||
4. **维护性**: 更好的代码结构和文档
|
||||
5. **扩展性**: 为未来微服务化奠定基础
|
||||
|
||||
## 3. 重构策略
|
||||
|
||||
### 3.1 整体策略
|
||||
采用**渐进式重构**策略,将整个重构过程分为4个主要阶段,每个阶段都有明确的目标和验收标准,确保重构过程的可控性和风险最小化。
|
||||
|
||||
### 3.2 技术选型
|
||||
- **核心框架**: Spring Boot 3.4.8 (最新稳定版)
|
||||
- **Java版本**: JDK 21 (LTS版本)
|
||||
- **安全框架**: Spring Security 6.x
|
||||
- **数据访问**: MyBatis-Plus 3.5.5
|
||||
- **HTTP客户端**: WebClient + RestTemplate
|
||||
- **API文档**: SpringDoc OpenAPI 3
|
||||
- **缓存**: Redis 8.x
|
||||
- **数据库**: MySQL 8.2
|
||||
|
||||
### 3.3 架构优化
|
||||
- **分层架构**: 优化Controller-Service-Mapper分层
|
||||
- **配置管理**: 统一配置管理,支持多环境
|
||||
- **异常处理**: 全局异常处理机制
|
||||
- **日志系统**: 结构化日志,支持ELK
|
||||
- **监控告警**: 集成Prometheus + Grafana
|
||||
|
||||
## 4. 重构阶段规划
|
||||
|
||||
### 第一阶段:基础环境升级 (2-3周)
|
||||
|
||||
#### 4.1.1 目标
|
||||
完成基础框架和开发环境的升级,建立新的技术栈基础。
|
||||
|
||||
#### 4.1.2 具体任务
|
||||
- [ ] **环境准备**
|
||||
- [ ] 升级JDK到21版本
|
||||
- [ ] 更新Maven配置
|
||||
- [ ] 配置新的开发环境
|
||||
- [ ] 建立新的代码仓库分支
|
||||
|
||||
- [ ] **依赖升级**
|
||||
- [ ] 升级Spring Boot到3.4.8
|
||||
- [ ] 升级Spring Security到6.x
|
||||
- [ ] 升级MyBatis-Plus到3.5.5
|
||||
- [ ] 升级JWT到0.12.3
|
||||
- [ ] 添加SpringDoc OpenAPI 3依赖
|
||||
|
||||
- [ ] **配置迁移**
|
||||
- [ ] 迁移application.yml配置
|
||||
- [ ] 更新数据库连接配置
|
||||
- [ ] 配置Redis连接
|
||||
- [ ] 设置多环境配置
|
||||
|
||||
#### 4.1.3 验收标准
|
||||
- [ ] 项目能够正常启动
|
||||
- [ ] 基础依赖无冲突
|
||||
- [ ] 配置加载正常
|
||||
- [ ] 数据库连接正常
|
||||
|
||||
#### 4.1.4 风险控制
|
||||
- **风险**: 依赖版本冲突
|
||||
- **应对**: 逐步升级,及时解决冲突
|
||||
- **回滚**: 保留原版本代码分支
|
||||
|
||||
### 第二阶段:核心功能重构 (4-5周)
|
||||
|
||||
#### 4.2.1 目标
|
||||
重构核心业务功能,确保主要功能模块在新框架下正常工作。
|
||||
|
||||
#### 4.2.2 具体任务
|
||||
- [ ] **认证系统重构**
|
||||
- [ ] 重构AuthController
|
||||
- [ ] 升级JWT认证机制
|
||||
- [ ] 优化Spring Security配置
|
||||
- [ ] 实现新的认证过滤器
|
||||
|
||||
- [ ] **AI对话系统重构**
|
||||
- [ ] 重构AiChatController
|
||||
- [ ] 实现Coze API客户端
|
||||
- [ ] 优化异步处理机制
|
||||
- [ ] 完善错误处理
|
||||
|
||||
- [ ] **用户管理系统重构**
|
||||
- [ ] 重构UserController
|
||||
- [ ] 优化用户信息管理
|
||||
- [ ] 实现用户权限控制
|
||||
- [ ] 完善用户统计功能
|
||||
|
||||
- [ ] **日记系统重构**
|
||||
- [ ] 重构DiaryPostController
|
||||
- [ ] 优化日记CRUD操作
|
||||
- [ ] 实现AI点评功能
|
||||
- [ ] 完善社交分享功能
|
||||
|
||||
#### 4.2.3 验收标准
|
||||
- [ ] 用户注册登录功能正常
|
||||
- [ ] AI对话功能正常
|
||||
- [ ] 日记发布管理功能正常
|
||||
- [ ] 用户信息管理功能正常
|
||||
|
||||
#### 4.2.4 风险控制
|
||||
- **风险**: 业务逻辑变更导致功能异常
|
||||
- **应对**: 保持业务逻辑不变,只升级技术实现
|
||||
- **测试**: 每个模块完成后进行功能测试
|
||||
|
||||
### 第三阶段:高级功能重构 (3-4周)
|
||||
|
||||
#### 4.3.1 目标
|
||||
重构高级功能模块,包括WebSocket、社区互动、数据分析等。
|
||||
|
||||
#### 4.3.2 具体任务
|
||||
- [ ] **WebSocket系统重构**
|
||||
- [ ] 重构WebSocket配置
|
||||
- [ ] 优化实时通信机制
|
||||
- [ ] 实现消息推送功能
|
||||
- [ ] 完善连接管理
|
||||
|
||||
- [ ] **社区系统重构**
|
||||
- [ ] 重构CommunityPostController
|
||||
- [ ] 重构CommentController
|
||||
- [ ] 优化点赞分享功能
|
||||
- [ ] 实现内容审核机制
|
||||
|
||||
- [ ] **数据分析系统重构**
|
||||
- [ ] 重构EmotionAnalysisController
|
||||
- [ ] 优化数据统计功能
|
||||
- [ ] 实现实时数据分析
|
||||
- [ ] 完善报表生成功能
|
||||
|
||||
- [ ] **消息系统重构**
|
||||
- [ ] 重构MessageController
|
||||
- [ ] 重构ConversationController
|
||||
- [ ] 优化消息存储机制
|
||||
- [ ] 实现消息推送功能
|
||||
|
||||
#### 4.3.3 验收标准
|
||||
- [ ] WebSocket连接稳定
|
||||
- [ ] 社区互动功能正常
|
||||
- [ ] 数据分析功能正常
|
||||
- [ ] 消息系统功能正常
|
||||
|
||||
#### 4.3.4 风险控制
|
||||
- **风险**: 实时通信功能异常
|
||||
- **应对**: 充分测试WebSocket连接
|
||||
- **监控**: 实时监控连接状态
|
||||
|
||||
### 第四阶段:性能优化和测试 (2-3周)
|
||||
|
||||
#### 4.4.1 目标
|
||||
进行性能优化,完善测试覆盖,确保系统稳定性和性能。
|
||||
|
||||
#### 4.4.2 具体任务
|
||||
- [ ] **性能优化**
|
||||
- [ ] 优化数据库查询
|
||||
- [ ] 实现多级缓存
|
||||
- [ ] 优化异步处理
|
||||
- [ ] 配置连接池参数
|
||||
|
||||
- [ ] **测试完善**
|
||||
- [ ] 编写单元测试
|
||||
- [ ] 编写集成测试
|
||||
- [ ] 进行性能测试
|
||||
- [ ] 进行安全测试
|
||||
|
||||
- [ ] **监控告警**
|
||||
- [ ] 集成Prometheus监控
|
||||
- [ ] 配置Grafana仪表板
|
||||
- [ ] 设置告警规则
|
||||
- [ ] 完善日志系统
|
||||
|
||||
- [ ] **文档完善**
|
||||
- [ ] 更新API文档
|
||||
- [ ] 编写部署文档
|
||||
- [ ] 完善运维文档
|
||||
- [ ] 更新开发文档
|
||||
|
||||
#### 4.4.3 验收标准
|
||||
- [ ] 性能指标达到预期
|
||||
- [ ] 测试覆盖率>80%
|
||||
- [ ] 监控告警正常
|
||||
- [ ] 文档完整准确
|
||||
|
||||
#### 4.4.4 风险控制
|
||||
- **风险**: 性能优化引入新问题
|
||||
- **应对**: 逐步优化,充分测试
|
||||
- **监控**: 实时监控系统性能
|
||||
|
||||
## 5. 详细实施计划
|
||||
|
||||
### 5.1 第一阶段详细计划
|
||||
|
||||
#### 5.1.1 第1周:环境准备
|
||||
**Day 1-2: 环境搭建**
|
||||
- 安装JDK 21
|
||||
- 配置Maven环境
|
||||
- 创建新的代码分支
|
||||
- 配置IDE开发环境
|
||||
|
||||
**Day 3-4: 依赖升级**
|
||||
- 升级Spring Boot到3.4.8
|
||||
- 升级Spring Security到6.x
|
||||
- 解决依赖冲突
|
||||
- 验证基础功能
|
||||
|
||||
**Day 5: 配置迁移**
|
||||
- 迁移application.yml
|
||||
- 配置数据库连接
|
||||
- 配置Redis连接
|
||||
- 测试基础连接
|
||||
|
||||
#### 5.1.2 第2周:基础配置
|
||||
**Day 1-2: 安全配置**
|
||||
- 配置Spring Security 6.x
|
||||
- 实现JWT认证
|
||||
- 配置CORS策略
|
||||
- 测试认证功能
|
||||
|
||||
**Day 3-4: 数据访问配置**
|
||||
- 配置MyBatis-Plus 3.5.5
|
||||
- 优化数据库连接池
|
||||
- 配置Redis缓存
|
||||
- 测试数据访问
|
||||
|
||||
**Day 5: API文档配置**
|
||||
- 集成SpringDoc OpenAPI 3
|
||||
- 配置API文档
|
||||
- 编写基础API文档
|
||||
- 测试文档访问
|
||||
|
||||
#### 5.1.3 第3周:基础功能验证
|
||||
**Day 1-2: 启动类重构**
|
||||
- 重构EmotionSimpleApplication
|
||||
- 配置组件扫描
|
||||
- 配置自动配置
|
||||
- 测试应用启动
|
||||
|
||||
**Day 3-4: 基础控制器测试**
|
||||
- 测试健康检查接口
|
||||
- 测试基础API接口
|
||||
- 验证配置加载
|
||||
- 修复发现的问题
|
||||
|
||||
**Day 5: 第一阶段验收**
|
||||
- 进行第一阶段验收测试
|
||||
- 编写验收报告
|
||||
- 准备第二阶段工作
|
||||
- 团队评审和总结
|
||||
|
||||
### 5.2 第二阶段详细计划
|
||||
|
||||
#### 5.2.1 第4周:认证系统重构
|
||||
**Day 1-2: AuthController重构**
|
||||
- 重构登录接口
|
||||
- 重构注册接口
|
||||
- 重构Token刷新接口
|
||||
- 测试认证功能
|
||||
|
||||
**Day 3-4: 安全机制升级**
|
||||
- 升级JWT实现
|
||||
- 优化认证过滤器
|
||||
- 实现权限控制
|
||||
- 测试安全功能
|
||||
|
||||
**Day 5: 用户管理基础**
|
||||
- 重构UserController基础功能
|
||||
- 实现用户信息查询
|
||||
- 实现用户信息更新
|
||||
- 测试用户管理功能
|
||||
|
||||
#### 5.2.2 第5周:AI对话系统重构
|
||||
**Day 1-2: Coze API客户端**
|
||||
- 实现CozeClient
|
||||
- 实现CozeRestTemplateClient
|
||||
- 配置Coze API参数
|
||||
- 测试API调用
|
||||
|
||||
**Day 3-4: AiChatController重构**
|
||||
- 重构聊天接口
|
||||
- 重构总结接口
|
||||
- 实现异步处理
|
||||
- 测试AI功能
|
||||
|
||||
**Day 5: 消息管理**
|
||||
- 重构MessageController
|
||||
- 实现消息存储
|
||||
- 实现消息查询
|
||||
- 测试消息功能
|
||||
|
||||
#### 5.2.3 第6周:日记系统重构
|
||||
**Day 1-2: DiaryPostController重构**
|
||||
- 重构日记CRUD接口
|
||||
- 实现分页查询
|
||||
- 实现搜索功能
|
||||
- 测试日记功能
|
||||
|
||||
**Day 3-4: 社交功能**
|
||||
- 实现点赞功能
|
||||
- 实现分享功能
|
||||
- 实现评论功能
|
||||
- 测试社交功能
|
||||
|
||||
**Day 5: AI点评功能**
|
||||
- 实现AI点评接口
|
||||
- 集成Coze API
|
||||
- 优化点评逻辑
|
||||
- 测试点评功能
|
||||
|
||||
#### 5.2.4 第7周:会话管理重构
|
||||
**Day 1-2: ConversationController重构**
|
||||
- 重构会话创建
|
||||
- 重构会话查询
|
||||
- 实现会话管理
|
||||
- 测试会话功能
|
||||
|
||||
**Day 3-4: 数据统计**
|
||||
- 实现用户统计
|
||||
- 实现对话统计
|
||||
- 实现日记统计
|
||||
- 测试统计功能
|
||||
|
||||
**Day 5: 第二阶段验收**
|
||||
- 进行第二阶段验收测试
|
||||
- 编写验收报告
|
||||
- 准备第三阶段工作
|
||||
- 团队评审和总结
|
||||
|
||||
### 5.3 第三阶段详细计划
|
||||
|
||||
#### 5.3.1 第8周:WebSocket系统重构
|
||||
**Day 1-2: WebSocket配置**
|
||||
- 重构WebSocketConfig
|
||||
- 配置STOMP协议
|
||||
- 实现连接管理
|
||||
- 测试WebSocket连接
|
||||
|
||||
**Day 3-4: 实时通信**
|
||||
- 实现消息推送
|
||||
- 实现在线状态
|
||||
- 实现群聊功能
|
||||
- 测试实时通信
|
||||
|
||||
**Day 5: 消息处理**
|
||||
- 实现消息处理器
|
||||
- 实现消息路由
|
||||
- 优化消息格式
|
||||
- 测试消息处理
|
||||
|
||||
#### 5.3.2 第9周:社区系统重构
|
||||
**Day 1-2: CommunityPostController重构**
|
||||
- 重构社区帖子管理
|
||||
- 实现帖子发布
|
||||
- 实现帖子查询
|
||||
- 测试社区功能
|
||||
|
||||
**Day 3-4: 评论系统**
|
||||
- 重构CommentController
|
||||
- 实现评论功能
|
||||
- 实现回复功能
|
||||
- 测试评论功能
|
||||
|
||||
**Day 5: 互动功能**
|
||||
- 实现点赞功能
|
||||
- 实现收藏功能
|
||||
- 实现分享功能
|
||||
- 测试互动功能
|
||||
|
||||
#### 5.3.3 第10周:数据分析系统重构
|
||||
**Day 1-2: EmotionAnalysisController重构**
|
||||
- 重构情绪分析
|
||||
- 实现数据分析
|
||||
- 实现趋势分析
|
||||
- 测试分析功能
|
||||
|
||||
**Day 3-4: 统计报表**
|
||||
- 实现用户统计
|
||||
- 实现内容统计
|
||||
- 实现行为统计
|
||||
- 测试统计功能
|
||||
|
||||
**Day 5: 第三阶段验收**
|
||||
- 进行第三阶段验收测试
|
||||
- 编写验收报告
|
||||
- 准备第四阶段工作
|
||||
- 团队评审和总结
|
||||
|
||||
### 5.4 第四阶段详细计划
|
||||
|
||||
#### 5.4.1 第11周:性能优化
|
||||
**Day 1-2: 数据库优化**
|
||||
- 优化SQL查询
|
||||
- 添加数据库索引
|
||||
- 优化连接池配置
|
||||
- 测试数据库性能
|
||||
|
||||
**Day 3-4: 缓存优化**
|
||||
- 实现多级缓存
|
||||
- 优化缓存策略
|
||||
- 配置缓存参数
|
||||
- 测试缓存效果
|
||||
|
||||
**Day 5: 异步优化**
|
||||
- 优化异步处理
|
||||
- 配置线程池
|
||||
- 实现任务队列
|
||||
- 测试异步性能
|
||||
|
||||
#### 5.4.2 第12周:测试完善
|
||||
**Day 1-2: 单元测试**
|
||||
- 编写Controller测试
|
||||
- 编写Service测试
|
||||
- 编写Mapper测试
|
||||
- 提高测试覆盖率
|
||||
|
||||
**Day 3-4: 集成测试**
|
||||
- 编写API集成测试
|
||||
- 编写数据库集成测试
|
||||
- 编写缓存集成测试
|
||||
- 测试系统集成
|
||||
|
||||
**Day 5: 性能测试**
|
||||
- 进行压力测试
|
||||
- 进行并发测试
|
||||
- 进行稳定性测试
|
||||
- 分析测试结果
|
||||
|
||||
#### 5.4.3 第13周:监控和文档
|
||||
**Day 1-2: 监控系统**
|
||||
- 集成Prometheus
|
||||
- 配置Grafana仪表板
|
||||
- 设置告警规则
|
||||
- 测试监控功能
|
||||
|
||||
**Day 3-4: 日志系统**
|
||||
- 配置结构化日志
|
||||
- 实现日志聚合
|
||||
- 配置日志分析
|
||||
- 测试日志功能
|
||||
|
||||
**Day 5: 文档完善**
|
||||
- 更新API文档
|
||||
- 编写部署文档
|
||||
- 完善运维文档
|
||||
- 最终验收测试
|
||||
|
||||
## 6. 风险管理
|
||||
|
||||
### 6.1 技术风险
|
||||
|
||||
#### 6.1.1 依赖升级风险
|
||||
- **风险描述**: Spring Boot 3.x与现有依赖可能存在兼容性问题
|
||||
- **影响程度**: 高
|
||||
- **应对措施**:
|
||||
- 逐步升级依赖,及时解决冲突
|
||||
- 保留原版本代码分支,确保可回滚
|
||||
- 建立完善的测试机制
|
||||
|
||||
#### 6.1.2 数据库兼容性风险
|
||||
- **风险描述**: 新版本框架可能影响数据库操作
|
||||
- **影响程度**: 中
|
||||
- **应对措施**:
|
||||
- 充分测试数据库操作
|
||||
- 准备数据库迁移脚本
|
||||
- 建立数据备份机制
|
||||
|
||||
#### 6.1.3 性能风险
|
||||
- **风险描述**: 新框架可能影响系统性能
|
||||
- **影响程度**: 中
|
||||
- **应对措施**:
|
||||
- 进行充分的性能测试
|
||||
- 建立性能基准
|
||||
- 实时监控系统性能
|
||||
|
||||
### 6.2 业务风险
|
||||
|
||||
#### 6.2.1 功能异常风险
|
||||
- **风险描述**: 重构过程中可能影响业务功能
|
||||
- **影响程度**: 高
|
||||
- **应对措施**:
|
||||
- 保持业务逻辑不变
|
||||
- 分阶段重构,及时验证
|
||||
- 建立完善的测试机制
|
||||
|
||||
#### 6.2.2 数据安全风险
|
||||
- **风险描述**: 重构过程中可能影响数据安全
|
||||
- **影响程度**: 高
|
||||
- **应对措施**:
|
||||
- 建立数据备份机制
|
||||
- 加强安全测试
|
||||
- 实施访问控制
|
||||
|
||||
### 6.3 项目风险
|
||||
|
||||
#### 6.3.1 进度风险
|
||||
- **风险描述**: 重构进度可能延期
|
||||
- **影响程度**: 中
|
||||
- **应对措施**:
|
||||
- 制定详细的时间计划
|
||||
- 建立里程碑检查点
|
||||
- 准备应急预案
|
||||
|
||||
#### 6.3.2 人员风险
|
||||
- **风险描述**: 团队成员可能缺乏新技术的经验
|
||||
- **影响程度**: 中
|
||||
- **应对措施**:
|
||||
- 进行技术培训
|
||||
- 建立知识分享机制
|
||||
- 引入外部技术支持
|
||||
|
||||
## 7. 质量保证
|
||||
|
||||
### 7.1 测试策略
|
||||
|
||||
#### 7.1.1 单元测试
|
||||
- **覆盖率要求**: >80%
|
||||
- **测试范围**: Controller、Service、Mapper层
|
||||
- **测试工具**: JUnit 5 + Mockito
|
||||
- **执行频率**: 每次代码提交
|
||||
|
||||
#### 7.1.2 集成测试
|
||||
- **测试范围**: API接口、数据库操作、缓存操作
|
||||
- **测试工具**: Spring Boot Test
|
||||
- **执行频率**: 每个阶段完成后
|
||||
|
||||
#### 7.1.3 性能测试
|
||||
- **测试范围**: 响应时间、并发处理、资源使用
|
||||
- **测试工具**: JMeter + Prometheus
|
||||
- **执行频率**: 每个阶段完成后
|
||||
|
||||
#### 7.1.4 安全测试
|
||||
- **测试范围**: 认证授权、数据安全、接口安全
|
||||
- **测试工具**: OWASP ZAP
|
||||
- **执行频率**: 每个阶段完成后
|
||||
|
||||
### 7.2 代码质量
|
||||
|
||||
#### 7.2.1 代码规范
|
||||
- **编码规范**: 遵循阿里巴巴Java开发手册
|
||||
- **代码审查**: 每个PR必须经过代码审查
|
||||
- **静态分析**: 使用SonarQube进行代码质量分析
|
||||
|
||||
#### 7.2.2 文档要求
|
||||
- **API文档**: 使用SpringDoc自动生成
|
||||
- **代码注释**: 关键业务逻辑必须有注释
|
||||
- **架构文档**: 更新系统架构文档
|
||||
|
||||
### 7.3 部署质量
|
||||
|
||||
#### 7.3.1 部署流程
|
||||
- **环境隔离**: 开发、测试、生产环境分离
|
||||
- **自动化部署**: 使用CI/CD流水线
|
||||
- **回滚机制**: 支持快速回滚
|
||||
|
||||
#### 7.3.2 监控告警
|
||||
- **系统监控**: 使用Prometheus + Grafana
|
||||
- **日志监控**: 使用ELK Stack
|
||||
- **告警机制**: 设置合理的告警阈值
|
||||
|
||||
## 8. 验收标准
|
||||
|
||||
### 8.1 功能验收标准
|
||||
|
||||
#### 8.1.1 基础功能
|
||||
- [ ] 用户注册登录功能正常
|
||||
- [ ] JWT认证机制正常工作
|
||||
- [ ] 用户信息管理功能正常
|
||||
- [ ] 基础API接口响应正常
|
||||
|
||||
#### 8.1.2 核心功能
|
||||
- [ ] AI对话功能正常
|
||||
- [ ] 日记发布管理功能正常
|
||||
- [ ] 社区互动功能正常
|
||||
- [ ] 消息系统功能正常
|
||||
|
||||
#### 8.1.3 高级功能
|
||||
- [ ] WebSocket实时通信正常
|
||||
- [ ] 数据分析功能正常
|
||||
- [ ] 文件上传功能正常
|
||||
- [ ] 搜索功能正常
|
||||
|
||||
### 8.2 性能验收标准
|
||||
|
||||
#### 8.2.1 响应时间
|
||||
- [ ] API接口平均响应时间 < 200ms
|
||||
- [ ] 数据库查询平均响应时间 < 50ms
|
||||
- [ ] 缓存命中率 > 90%
|
||||
|
||||
#### 8.2.2 并发处理
|
||||
- [ ] 支持1000并发用户
|
||||
- [ ] 系统稳定性测试通过
|
||||
- [ ] 内存使用率 < 80%
|
||||
|
||||
#### 8.2.3 可用性
|
||||
- [ ] 系统可用性 > 99.9%
|
||||
- [ ] 故障恢复时间 < 5分钟
|
||||
- [ ] 数据备份恢复正常
|
||||
|
||||
### 8.3 安全验收标准
|
||||
|
||||
#### 8.3.1 认证授权
|
||||
- [ ] JWT认证机制安全
|
||||
- [ ] 权限控制正确
|
||||
- [ ] 会话管理安全
|
||||
|
||||
#### 8.3.2 数据安全
|
||||
- [ ] 敏感数据加密存储
|
||||
- [ ] 数据传输安全
|
||||
- [ ] SQL注入防护
|
||||
|
||||
#### 8.3.3 接口安全
|
||||
- [ ] API接口安全测试通过
|
||||
- [ ] CORS配置正确
|
||||
- [ ] 请求频率限制
|
||||
|
||||
### 8.4 技术验收标准
|
||||
|
||||
#### 8.4.1 代码质量
|
||||
- [ ] 代码覆盖率 > 80%
|
||||
- [ ] SonarQube质量门禁通过
|
||||
- [ ] 代码审查通过
|
||||
|
||||
#### 8.4.2 文档完整性
|
||||
- [ ] API文档完整准确
|
||||
- [ ] 部署文档完整
|
||||
- [ ] 运维文档完整
|
||||
|
||||
#### 8.4.3 监控告警
|
||||
- [ ] 监控系统正常工作
|
||||
- [ ] 告警机制正常
|
||||
- [ ] 日志系统正常
|
||||
|
||||
## 9. 团队组织
|
||||
|
||||
### 9.1 团队结构
|
||||
- **项目经理**: 负责整体项目管理和协调
|
||||
- **技术负责人**: 负责技术方案设计和架构决策
|
||||
- **后端开发工程师**: 负责具体功能开发
|
||||
- **测试工程师**: 负责测试用例设计和执行
|
||||
- **运维工程师**: 负责部署和运维支持
|
||||
|
||||
### 9.2 职责分工
|
||||
- **项目经理**: 进度管理、风险控制、资源协调
|
||||
- **技术负责人**: 技术方案、架构设计、代码审查
|
||||
- **后端开发工程师**: 功能开发、单元测试、文档编写
|
||||
- **测试工程师**: 测试计划、测试执行、质量保证
|
||||
- **运维工程师**: 环境搭建、部署支持、监控配置
|
||||
|
||||
### 9.3 沟通机制
|
||||
- **日常沟通**: 每日站会,同步进度和问题
|
||||
- **周例会**: 每周总结会议,评审进度和计划
|
||||
- **里程碑会议**: 每个阶段结束后的评审会议
|
||||
- **技术分享**: 定期技术分享,提升团队能力
|
||||
|
||||
## 10. 总结
|
||||
|
||||
### 10.1 重构价值
|
||||
通过本次重构,情绪博物馆后端系统将获得以下价值:
|
||||
|
||||
1. **技术现代化**: 采用最新的Spring Boot 3.4.8和JDK 21
|
||||
2. **性能提升**: 预期性能提升20%以上
|
||||
3. **安全增强**: 采用最新的安全特性和标准
|
||||
4. **可维护性**: 更好的代码结构和文档
|
||||
5. **扩展性**: 为未来功能扩展奠定基础
|
||||
|
||||
### 10.2 成功关键因素
|
||||
1. **充分的准备**: 详细的技术方案和计划
|
||||
2. **渐进式重构**: 分阶段进行,降低风险
|
||||
3. **质量保证**: 完善的测试和监控机制
|
||||
4. **团队协作**: 良好的沟通和协作机制
|
||||
5. **持续改进**: 根据实际情况调整计划
|
||||
|
||||
### 10.3 后续规划
|
||||
重构完成后,将进行以下后续工作:
|
||||
|
||||
1. **性能优化**: 持续的性能监控和优化
|
||||
2. **功能扩展**: 基于新架构的功能扩展
|
||||
3. **微服务化**: 为未来的微服务化做准备
|
||||
4. **技术升级**: 持续关注新技术,及时升级
|
||||
5. **团队建设**: 提升团队技术能力
|
||||
|
||||
这个重构计划将为情绪博物馆后端系统带来显著的技术提升和业务价值,为项目的长期发展奠定坚实的基础。
|
||||
@@ -1,247 +0,0 @@
|
||||
# 情绪博物馆后端重构完成总结
|
||||
|
||||
## 项目概述
|
||||
|
||||
本项目成功完成了情绪博物馆后端服务从Spring Boot 2.7.18到Spring Boot 3.4.8的全面升级重构,采用了最新的技术栈和最佳实践。
|
||||
|
||||
## 重构成果
|
||||
|
||||
### 第一阶段:基础环境升级 ✅
|
||||
|
||||
#### 技术栈升级
|
||||
- **Spring Boot**: 2.7.18 → 3.4.8
|
||||
- **Java版本**: JDK 17 (计划升级到JDK 21)
|
||||
- **Spring Security**: 5.x → 6.x
|
||||
- **MyBatis-Plus**: 3.5.3.1 → 3.5.5
|
||||
- **JWT**: 0.11.5 → 0.12.3
|
||||
- **API文档**: Swagger → SpringDoc OpenAPI 3
|
||||
|
||||
#### 基础配置完成
|
||||
- ✅ Maven项目配置 (pom.xml)
|
||||
- ✅ 主配置文件 (application.yml, application-local.yml)
|
||||
- ✅ 主启动类 (EmotionMuseumApplication.java)
|
||||
- ✅ 基础配置类 (SecurityConfig, MybatisPlusConfig, RedisConfig等)
|
||||
|
||||
### 第二阶段:核心功能重构 ✅
|
||||
|
||||
#### 1. 认证系统重构 ✅
|
||||
- **AuthService**: 用户认证服务接口
|
||||
- **AuthServiceImpl**: 用户认证服务实现
|
||||
- **AuthController**: 认证控制器
|
||||
- **JwtUtil**: JWT工具类 (适配JWT 0.12.3)
|
||||
- **功能**: 用户注册、登录、登出、令牌刷新、令牌验证
|
||||
|
||||
#### 2. 用户管理系统重构 ✅
|
||||
- **UserService**: 用户服务接口
|
||||
- **UserServiceImpl**: 用户服务实现
|
||||
- **UserController**: 用户控制器
|
||||
- **功能**: 用户信息管理、密码修改、用户列表、用户状态管理
|
||||
|
||||
#### 3. AI对话系统重构 ✅
|
||||
- **CozeApiService**: Coze API服务接口
|
||||
- **CozeApiServiceImpl**: Coze API服务实现
|
||||
- **AiChatService**: AI聊天服务接口
|
||||
- **AiChatServiceImpl**: AI聊天服务实现
|
||||
- **AiChatController**: AI聊天控制器
|
||||
- **功能**: 与Coze Bot对话、会话管理、消息历史、AI状态检查
|
||||
|
||||
#### 4. 日记系统重构 ✅
|
||||
- **DiaryPostRequest**: 日记请求DTO
|
||||
- **DiaryPostService**: 日记服务接口
|
||||
- **DiaryPostServiceImpl**: 日记服务实现
|
||||
- **DiaryPostController**: 日记控制器
|
||||
- **功能**: 日记CRUD、AI点评、点赞、情绪标签、公开/私密设置
|
||||
|
||||
#### 5. WebSocket系统重构 ✅
|
||||
- **WebSocketConfig**: WebSocket配置
|
||||
- **ChatMessage**: WebSocket消息DTO
|
||||
- **WebSocketController**: WebSocket控制器
|
||||
- **功能**: 实时聊天、AI对话、消息推送、用户状态同步
|
||||
|
||||
## 技术亮点
|
||||
|
||||
### 1. 现代化技术栈
|
||||
- 采用Spring Boot 3.4.8最新版本
|
||||
- 使用Spring Security 6.x最新安全框架
|
||||
- 集成SpringDoc OpenAPI 3现代化API文档
|
||||
- 支持WebSocket实时通信
|
||||
|
||||
### 2. 完善的认证体系
|
||||
- JWT 0.12.3最新版本适配
|
||||
- Redis令牌存储和验证
|
||||
- 完整的用户认证流程
|
||||
- 安全的密码加密存储
|
||||
|
||||
### 3. AI集成能力
|
||||
- 直接集成Coze API
|
||||
- 支持上下文对话
|
||||
- 异步AI回复处理
|
||||
- 智能错误处理机制
|
||||
|
||||
### 4. 实时通信支持
|
||||
- WebSocket + STOMP协议
|
||||
- 支持SockJS和原生WebSocket
|
||||
- 实时消息推送
|
||||
- 用户状态同步
|
||||
|
||||
### 5. 数据访问优化
|
||||
- MyBatis-Plus 3.5.5最新版本
|
||||
- 分页查询支持
|
||||
- 逻辑删除
|
||||
- 乐观锁机制
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
server/
|
||||
├── src/main/java/com/emotionmuseum/
|
||||
│ ├── config/ # 配置类
|
||||
│ │ ├── SecurityConfig.java
|
||||
│ │ ├── MybatisPlusConfig.java
|
||||
│ │ ├── RedisConfig.java
|
||||
│ │ ├── WebSocketConfig.java
|
||||
│ │ └── ...
|
||||
│ ├── controller/ # 控制器层
|
||||
│ │ ├── AuthController.java
|
||||
│ │ ├── UserController.java
|
||||
│ │ ├── AiChatController.java
|
||||
│ │ ├── DiaryPostController.java
|
||||
│ │ └── WebSocketController.java
|
||||
│ ├── service/ # 服务层
|
||||
│ │ ├── AuthService.java
|
||||
│ │ ├── UserService.java
|
||||
│ │ ├── AiChatService.java
|
||||
│ │ ├── DiaryPostService.java
|
||||
│ │ ├── CozeApiService.java
|
||||
│ │ └── impl/ # 服务实现
|
||||
│ ├── mapper/ # 数据访问层
|
||||
│ │ ├── UserMapper.java
|
||||
│ │ ├── DiaryPostMapper.java
|
||||
│ │ ├── MessageMapper.java
|
||||
│ │ └── ConversationMapper.java
|
||||
│ ├── entity/ # 实体类
|
||||
│ │ ├── User.java
|
||||
│ │ ├── DiaryPost.java
|
||||
│ │ ├── Message.java
|
||||
│ │ └── Conversation.java
|
||||
│ ├── dto/ # 数据传输对象
|
||||
│ │ ├── Result.java
|
||||
│ │ ├── auth/ # 认证相关DTO
|
||||
│ │ ├── diary/ # 日记相关DTO
|
||||
│ │ └── websocket/ # WebSocket相关DTO
|
||||
│ ├── util/ # 工具类
|
||||
│ │ └── JwtUtil.java
|
||||
│ └── EmotionMuseumApplication.java
|
||||
├── src/main/resources/
|
||||
│ ├── application.yml
|
||||
│ ├── application-local.yml
|
||||
│ └── mapper/ # MyBatis映射文件
|
||||
├── pom.xml # Maven配置
|
||||
└── README.md # 项目文档
|
||||
```
|
||||
|
||||
## API接口概览
|
||||
|
||||
### 认证接口
|
||||
- `POST /api/auth/login` - 用户登录
|
||||
- `POST /api/auth/register` - 用户注册
|
||||
- `POST /api/auth/logout` - 用户登出
|
||||
- `POST /api/auth/refresh` - 刷新令牌
|
||||
- `GET /api/auth/validate` - 验证令牌
|
||||
|
||||
### 用户接口
|
||||
- `GET /api/user/profile` - 获取用户信息
|
||||
- `PUT /api/user/profile` - 更新用户信息
|
||||
- `POST /api/user/change-password` - 修改密码
|
||||
- `GET /api/user/list` - 获取用户列表
|
||||
|
||||
### AI聊天接口
|
||||
- `POST /api/ai/chat/send` - 发送消息
|
||||
- `POST /api/ai/conversation/create` - 创建会话
|
||||
- `GET /api/ai/conversation/list` - 获取会话列表
|
||||
- `GET /api/ai/conversation/{id}/messages` - 获取会话消息
|
||||
- `DELETE /api/ai/conversation/{id}` - 删除会话
|
||||
- `POST /api/ai/conversation/{id}/clear` - 清空会话
|
||||
- `GET /api/ai/status` - 检查AI状态
|
||||
|
||||
### 日记接口
|
||||
- `POST /api/diary/create` - 创建日记
|
||||
- `PUT /api/diary/{id}` - 更新日记
|
||||
- `GET /api/diary/{id}` - 获取日记详情
|
||||
- `GET /api/diary/user/list` - 获取用户日记列表
|
||||
- `GET /api/diary/public/list` - 获取公开日记列表
|
||||
- `GET /api/diary/emotion/{tag}` - 根据情绪标签查询
|
||||
- `DELETE /api/diary/{id}` - 删除日记
|
||||
- `POST /api/diary/{id}/like` - 点赞日记
|
||||
- `POST /api/diary/{id}/unlike` - 取消点赞
|
||||
- `GET /api/diary/{id}/ai-comment` - 获取AI点评
|
||||
|
||||
### WebSocket接口
|
||||
- `/ws` - WebSocket连接端点
|
||||
- `/app/chat.sendMessage` - 发送聊天消息
|
||||
- `/app/chat.addUser` - 用户加入聊天
|
||||
- `/app/ai.chat` - AI聊天消息
|
||||
- `/app/chat.typing` - 用户输入状态
|
||||
- `/topic/public` - 公共消息主题
|
||||
- `/queue/ai.response` - AI回复队列
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 环境要求
|
||||
- JDK 17+
|
||||
- Maven 3.6+
|
||||
- MySQL 8.0+
|
||||
- Redis 7.0+
|
||||
|
||||
### 启动步骤
|
||||
1. 配置环境变量或修改application-local.yml
|
||||
2. 启动MySQL和Redis服务
|
||||
3. 执行编译:`mvn clean compile`
|
||||
4. 启动应用:`mvn spring-boot:run`
|
||||
|
||||
### 访问地址
|
||||
- 应用地址:http://localhost:19089
|
||||
- API文档:http://localhost:19089/api/swagger-ui.html
|
||||
- 健康检查:http://localhost:19089/api/health
|
||||
|
||||
## 下一步计划
|
||||
|
||||
### 第三阶段:高级功能重构
|
||||
1. **社区系统重构**
|
||||
- 评论功能
|
||||
- 用户关注
|
||||
- 内容推荐
|
||||
|
||||
2. **统计分析系统**
|
||||
- 用户行为分析
|
||||
- 情绪趋势分析
|
||||
- 数据可视化
|
||||
|
||||
3. **通知系统**
|
||||
- 消息推送
|
||||
- 邮件通知
|
||||
- 系统公告
|
||||
|
||||
### 第四阶段:性能优化和测试
|
||||
1. **性能优化**
|
||||
- 缓存优化
|
||||
- 数据库优化
|
||||
- 并发处理优化
|
||||
|
||||
2. **测试完善**
|
||||
- 单元测试
|
||||
- 集成测试
|
||||
- 性能测试
|
||||
|
||||
## 总结
|
||||
|
||||
本次重构成功完成了情绪博物馆后端服务的全面升级,实现了:
|
||||
|
||||
1. **技术栈现代化**: 升级到Spring Boot 3.4.8等最新技术
|
||||
2. **功能完整性**: 覆盖认证、用户管理、AI对话、日记、WebSocket等核心功能
|
||||
3. **架构优化**: 采用分层架构,代码结构清晰,易于维护
|
||||
4. **安全性提升**: 使用Spring Security 6.x和JWT 0.12.3
|
||||
5. **实时通信**: 支持WebSocket实时消息推送
|
||||
6. **AI集成**: 直接集成Coze API,支持智能对话
|
||||
|
||||
项目已经具备了完整的后端服务能力,为前端应用提供了稳定、安全、高效的API支持。
|
||||
@@ -1,254 +0,0 @@
|
||||
# 情绪博物馆后端重构进度总结
|
||||
|
||||
## 重构概述
|
||||
|
||||
本次重构成功完成了情绪博物馆后端服务从Spring Boot 2.7.18到Spring Boot 3.4.8的全面升级,采用了最新的技术栈和最佳实践。
|
||||
|
||||
## 已完成的重构工作
|
||||
|
||||
### 第一阶段:基础环境升级 ✅
|
||||
|
||||
#### 技术栈升级
|
||||
- **Spring Boot**: 2.7.18 → 3.4.8
|
||||
- **Java版本**: JDK 17 (计划升级到JDK 21)
|
||||
- **Spring Security**: 5.x → 6.x
|
||||
- **MyBatis-Plus**: 3.5.3.1 → 3.5.5
|
||||
- **JWT**: 0.11.5 → 0.12.3
|
||||
- **API文档**: Swagger → SpringDoc OpenAPI 3
|
||||
|
||||
#### 基础配置完成
|
||||
- ✅ Maven项目配置 (pom.xml)
|
||||
- ✅ 主配置文件 (application.yml, application-local.yml)
|
||||
- ✅ 主启动类 (EmotionMuseumApplication.java)
|
||||
- ✅ 基础配置类 (SecurityConfig, MybatisPlusConfig, RedisConfig等)
|
||||
|
||||
### 第二阶段:核心功能重构 ✅
|
||||
|
||||
#### 1. 认证系统重构 ✅
|
||||
- **AuthService**: 用户认证服务接口
|
||||
- **AuthServiceImpl**: 用户认证服务实现
|
||||
- **AuthController**: 认证控制器
|
||||
- **JwtUtil**: JWT工具类 (适配JWT 0.12.3)
|
||||
- **功能**: 用户注册、登录、登出、令牌刷新、令牌验证
|
||||
|
||||
#### 2. 用户管理系统重构 ✅
|
||||
- **UserService**: 用户服务接口
|
||||
- **UserServiceImpl**: 用户服务实现
|
||||
- **UserController**: 用户控制器
|
||||
- **功能**: 用户信息管理、密码修改、用户列表、用户状态管理
|
||||
|
||||
#### 3. AI对话系统重构 ✅
|
||||
- **CozeApiService**: Coze API服务接口
|
||||
- **CozeApiServiceImpl**: Coze API服务实现
|
||||
- **AiChatService**: AI聊天服务接口
|
||||
- **AiChatServiceImpl**: AI聊天服务实现
|
||||
- **AiChatController**: AI聊天控制器
|
||||
- **功能**: 与Coze Bot对话、会话管理、消息历史、AI状态检查
|
||||
|
||||
#### 4. 日记系统重构 ✅
|
||||
- **DiaryPostRequest**: 日记请求DTO
|
||||
- **DiaryPostService**: 日记服务接口
|
||||
- **DiaryPostServiceImpl**: 日记服务实现
|
||||
- **DiaryPostController**: 日记控制器
|
||||
- **功能**: 日记CRUD、AI点评、点赞、情绪标签、公开/私密设置
|
||||
|
||||
#### 5. WebSocket系统重构 ✅
|
||||
- **WebSocketConfig**: WebSocket配置
|
||||
- **ChatMessage**: WebSocket消息DTO
|
||||
- **WebSocketController**: WebSocket控制器
|
||||
- **功能**: 实时聊天、AI对话、消息推送、用户状态同步
|
||||
|
||||
#### 6. 社区系统重构 🔄
|
||||
- **Comment**: 评论实体
|
||||
- **UserFollow**: 用户关注实体
|
||||
- **CommentRequest/CommentResponse**: 评论DTO
|
||||
- **CommentMapper/UserFollowMapper**: 数据访问层
|
||||
- **CommentService/UserFollowService**: 服务接口
|
||||
- **CommentServiceImpl**: 评论服务实现
|
||||
- **功能**: 评论、回复、点赞、用户关注
|
||||
|
||||
## 技术亮点
|
||||
|
||||
### 1. 现代化技术栈
|
||||
- 采用Spring Boot 3.4.8最新版本
|
||||
- 使用Spring Security 6.x最新安全框架
|
||||
- 集成SpringDoc OpenAPI 3现代化API文档
|
||||
- 支持WebSocket实时通信
|
||||
|
||||
### 2. 完善的认证体系
|
||||
- JWT 0.12.3最新版本适配
|
||||
- Redis令牌存储和验证
|
||||
- 完整的用户认证流程
|
||||
- 安全的密码加密存储
|
||||
|
||||
### 3. AI集成能力
|
||||
- 直接集成Coze API
|
||||
- 支持上下文对话
|
||||
- 异步AI回复处理
|
||||
- 智能错误处理机制
|
||||
|
||||
### 4. 实时通信支持
|
||||
- WebSocket + STOMP协议
|
||||
- 支持SockJS和原生WebSocket
|
||||
- 实时消息推送
|
||||
- 用户状态同步
|
||||
|
||||
### 5. 数据访问优化
|
||||
- MyBatis-Plus 3.5.5最新版本
|
||||
- 分页查询支持
|
||||
- 逻辑删除
|
||||
- 乐观锁机制
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
server/
|
||||
├── src/main/java/com/emotionmuseum/
|
||||
│ ├── config/ # 配置类
|
||||
│ │ ├── SecurityConfig.java
|
||||
│ │ ├── MybatisPlusConfig.java
|
||||
│ │ ├── RedisConfig.java
|
||||
│ │ ├── WebSocketConfig.java
|
||||
│ │ └── ...
|
||||
│ ├── controller/ # 控制器层
|
||||
│ │ ├── AuthController.java
|
||||
│ │ ├── UserController.java
|
||||
│ │ ├── AiChatController.java
|
||||
│ │ ├── DiaryPostController.java
|
||||
│ │ └── WebSocketController.java
|
||||
│ ├── service/ # 服务层
|
||||
│ │ ├── AuthService.java
|
||||
│ │ ├── UserService.java
|
||||
│ │ ├── AiChatService.java
|
||||
│ │ ├── DiaryPostService.java
|
||||
│ │ ├── CozeApiService.java
|
||||
│ │ ├── CommentService.java
|
||||
│ │ ├── UserFollowService.java
|
||||
│ │ └── impl/ # 服务实现
|
||||
│ ├── mapper/ # 数据访问层
|
||||
│ │ ├── UserMapper.java
|
||||
│ │ ├── DiaryPostMapper.java
|
||||
│ │ ├── MessageMapper.java
|
||||
│ │ ├── ConversationMapper.java
|
||||
│ │ ├── CommentMapper.java
|
||||
│ │ └── UserFollowMapper.java
|
||||
│ ├── entity/ # 实体类
|
||||
│ │ ├── User.java
|
||||
│ │ ├── DiaryPost.java
|
||||
│ │ ├── Message.java
|
||||
│ │ ├── Conversation.java
|
||||
│ │ ├── Comment.java
|
||||
│ │ └── UserFollow.java
|
||||
│ ├── dto/ # 数据传输对象
|
||||
│ │ ├── Result.java
|
||||
│ │ ├── auth/ # 认证相关DTO
|
||||
│ │ ├── diary/ # 日记相关DTO
|
||||
│ │ ├── comment/ # 评论相关DTO
|
||||
│ │ └── websocket/ # WebSocket相关DTO
|
||||
│ ├── util/ # 工具类
|
||||
│ │ └── JwtUtil.java
|
||||
│ └── EmotionMuseumApplication.java
|
||||
├── src/main/resources/
|
||||
│ ├── application.yml
|
||||
│ ├── application-local.yml
|
||||
│ └── mapper/ # MyBatis映射文件
|
||||
├── pom.xml # Maven配置
|
||||
└── README.md # 项目文档
|
||||
```
|
||||
|
||||
## API接口概览
|
||||
|
||||
### 认证接口
|
||||
- `POST /api/auth/login` - 用户登录
|
||||
- `POST /api/auth/register` - 用户注册
|
||||
- `POST /api/auth/logout` - 用户登出
|
||||
- `POST /api/auth/refresh` - 刷新令牌
|
||||
- `GET /api/auth/validate` - 验证令牌
|
||||
|
||||
### 用户接口
|
||||
- `GET /api/user/profile` - 获取用户信息
|
||||
- `PUT /api/user/profile` - 更新用户信息
|
||||
- `POST /api/user/change-password` - 修改密码
|
||||
- `GET /api/user/list` - 获取用户列表
|
||||
|
||||
### AI聊天接口
|
||||
- `POST /api/ai/chat/send` - 发送消息
|
||||
- `POST /api/ai/conversation/create` - 创建会话
|
||||
- `GET /api/ai/conversation/list` - 获取会话列表
|
||||
- `GET /api/ai/conversation/{id}/messages` - 获取会话消息
|
||||
- `DELETE /api/ai/conversation/{id}` - 删除会话
|
||||
- `POST /api/ai/conversation/{id}/clear` - 清空会话
|
||||
- `GET /api/ai/status` - 检查AI状态
|
||||
|
||||
### 日记接口
|
||||
- `POST /api/diary/create` - 创建日记
|
||||
- `PUT /api/diary/{id}` - 更新日记
|
||||
- `GET /api/diary/{id}` - 获取日记详情
|
||||
- `GET /api/diary/user/list` - 获取用户日记列表
|
||||
- `GET /api/diary/public/list` - 获取公开日记列表
|
||||
- `GET /api/diary/emotion/{tag}` - 根据情绪标签查询
|
||||
- `DELETE /api/diary/{id}` - 删除日记
|
||||
- `POST /api/diary/{id}/like` - 点赞日记
|
||||
- `POST /api/diary/{id}/unlike` - 取消点赞
|
||||
- `GET /api/diary/{id}/ai-comment` - 获取AI点评
|
||||
|
||||
### 评论接口
|
||||
- `POST /api/comment/create` - 创建评论
|
||||
- `GET /api/comment/content/{contentType}/{contentId}` - 获取内容评论
|
||||
- `GET /api/comment/{id}` - 获取评论详情
|
||||
- `DELETE /api/comment/{id}` - 删除评论
|
||||
- `POST /api/comment/{id}/like` - 点赞评论
|
||||
- `POST /api/comment/{id}/unlike` - 取消点赞评论
|
||||
- `GET /api/comment/user/{userId}` - 获取用户评论
|
||||
- `GET /api/comment/{id}/replies` - 获取评论回复
|
||||
|
||||
### WebSocket接口
|
||||
- `/ws` - WebSocket连接端点
|
||||
- `/app/chat.sendMessage` - 发送聊天消息
|
||||
- `/app/chat.addUser` - 用户加入聊天
|
||||
- `/app/ai.chat` - AI聊天消息
|
||||
- `/app/chat.typing` - 用户输入状态
|
||||
- `/topic/public` - 公共消息主题
|
||||
- `/queue/ai.response` - AI回复队列
|
||||
|
||||
## 下一步计划
|
||||
|
||||
### 第三阶段:高级功能重构
|
||||
1. **社区系统完善**
|
||||
- 用户关注功能实现
|
||||
- 评论控制器
|
||||
- 社区内容推荐
|
||||
|
||||
2. **统计分析系统**
|
||||
- 用户行为分析
|
||||
- 情绪趋势分析
|
||||
- 数据可视化
|
||||
|
||||
3. **通知系统**
|
||||
- 消息推送
|
||||
- 邮件通知
|
||||
- 系统公告
|
||||
|
||||
### 第四阶段:性能优化和测试
|
||||
1. **性能优化**
|
||||
- 缓存优化
|
||||
- 数据库优化
|
||||
- 并发处理优化
|
||||
|
||||
2. **测试完善**
|
||||
- 单元测试
|
||||
- 集成测试
|
||||
- 性能测试
|
||||
|
||||
## 总结
|
||||
|
||||
本次重构已经完成了情绪博物馆后端服务的核心功能升级,包括:
|
||||
|
||||
1. **技术栈现代化**: 升级到Spring Boot 3.4.8等最新技术
|
||||
2. **功能完整性**: 覆盖认证、用户管理、AI对话、日记、WebSocket、社区等核心功能
|
||||
3. **架构优化**: 采用分层架构,代码结构清晰,易于维护
|
||||
4. **安全性提升**: 使用Spring Security 6.x和JWT 0.12.3
|
||||
5. **实时通信**: 支持WebSocket实时消息推送
|
||||
6. **AI集成**: 直接集成Coze API,支持智能对话
|
||||
|
||||
项目已经具备了完整的后端服务能力,为前端应用提供了稳定、安全、高效的API支持。后续将继续完善高级功能和性能优化。
|
||||
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
start /b "xxl-job-admin" java -jar F:\ProgramData\xxl-job\xxl-job-admin\target\xxl-job-admin-3.1.2-SNAPSHOT.jar --server.port=18996 --spring.datasource.url="jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai" --spring.datasource.username=root --spring.datasource.password=123456
|
||||
Reference in New Issue
Block a user