From d42d689bd74a424585b7a4aea8d84a3f7c9bcbd4 Mon Sep 17 00:00:00 2001 From: huazhongmin Date: Mon, 8 Sep 2025 17:54:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .augment/rules/rules.md | 212 ++++- .cursor/rules/rules.mdc | 216 ++++- .qoder/rules/rules.md | 218 ++++- .../java/com/emotion/common/PageResult.java | 10 +- .../controller/AchievementController.java | 163 ++-- .../emotion/controller/AiChatController.java | 214 ++--- .../emotion/controller/AuthController.java | 47 +- .../controller/ChatWebSocketController.java | 121 +-- .../emotion/controller/CommentController.java | 230 +---- .../controller/CommunityPostController.java | 260 +----- .../controller/ConversationController.java | 183 +--- .../controller/CozeApiCallController.java | 308 ++----- .../controller/DiaryCommentController.java | 250 ++---- .../controller/DiaryPostController.java | 268 +----- .../controller/EmotionAnalysisController.java | 224 ++--- .../controller/EmotionRecordController.java | 220 ++--- .../emotion/dto/request/ChatStatsRequest.java | 2 +- .../request/ConversationCreateRequest.java | 5 + .../dto/request/ConversationPageRequest.java | 30 + .../request/DiaryCommentCreateRequest.java | 8 +- .../dto/request/DiaryCommentPageRequest.java | 38 + .../dto/request/DiaryPostPageRequest.java | 41 + .../dto/request/DiaryPostUpdateRequest.java | 9 +- .../request/EmotionAnalysisCreateRequest.java | 51 ++ .../request/EmotionAnalysisPageRequest.java | 36 + .../request/EmotionAnalysisUpdateRequest.java | 51 ++ .../request/EmotionRecordCreateRequest.java | 83 ++ .../dto/request/EmotionRecordPageRequest.java | 31 + .../request/EmotionRecordUpdateRequest.java | 79 ++ .../dto/request/GuestUserInfoRequest.java | 20 + .../emotion/dto/request/WebSocketRequest.java | 40 + .../achievement/AchievementCreateRequest.java | 65 ++ .../achievement/AchievementPageRequest.java | 40 + .../AchievementProgressUpdateRequest.java | 28 + .../achievement/AchievementUnlockRequest.java | 21 + .../achievement/AchievementUpdateRequest.java | 68 ++ .../request/comment/CommentCreateRequest.java | 40 + .../request/comment/CommentPageRequest.java | 30 + .../request/comment/CommentQueryRequest.java | 41 + .../request/comment/CommentUpdateRequest.java | 30 + .../community/CommunityPostCreateRequest.java | 56 ++ .../community/CommunityPostPageRequest.java | 41 + .../community/CommunityPostUpdateRequest.java | 56 ++ .../coze/CozeApiCallCreateRequest.java | 215 +++++ .../request/coze/CozeApiCallPageRequest.java | 54 ++ .../coze/CozeApiCallUpdateRequest.java | 220 +++++ .../dto/response/EmotionAnalysisResponse.java | 50 ++ .../dto/response/EmotionRecordResponse.java | 80 ++ .../dto/response/WebSocketResponse.java | 62 ++ .../achievement/AchievementResponse.java | 73 ++ .../dto/response/comment/CommentResponse.java | 41 + .../community/CommunityPostResponse.java | 71 ++ .../response/coze/CozeApiCallResponse.java | 212 +++++ .../emotion/dto/websocket/ChatRequest.java | 49 +- .../dto/websocket/WebSocketMessage.java | 70 +- .../emotion/service/AchievementService.java | 73 +- .../com/emotion/service/AiChatService.java | 124 +-- .../java/com/emotion/service/AuthService.java | 18 +- .../com/emotion/service/CommentService.java | 127 +-- .../emotion/service/CommunityPostService.java | 158 +--- .../emotion/service/ConversationService.java | 66 +- .../emotion/service/CozeApiCallService.java | 81 +- .../emotion/service/DiaryCommentService.java | 122 +-- .../com/emotion/service/DiaryPostService.java | 111 +-- .../service/EmotionAnalysisService.java | 70 +- .../emotion/service/EmotionRecordService.java | 50 +- .../com/emotion/service/TokenService.java | 16 +- .../com/emotion/service/WebSocketService.java | 24 +- .../service/impl/AchievementServiceImpl.java | 189 +++- .../service/impl/AiChatServiceImpl.java | 841 +++++++++++------- .../emotion/service/impl/AuthServiceImpl.java | 24 +- .../service/impl/CommentServiceImpl.java | 291 +++--- .../impl/CommunityPostServiceImpl.java | 399 +++------ .../service/impl/ConversationServiceImpl.java | 196 +++- .../service/impl/CozeApiCallServiceImpl.java | 405 ++++++--- .../service/impl/DiaryCommentServiceImpl.java | 396 +++++---- .../service/impl/DiaryPostServiceImpl.java | 465 +++++----- .../impl/EmotionAnalysisServiceImpl.java | 174 ++++ .../impl/EmotionRecordServiceImpl.java | 288 +++++- .../service/impl/TokenServiceImpl.java | 22 +- .../service/impl/WebSocketServiceImpl.java | 213 +++-- .../main/java/com/emotion/util/TokenUtil.java | 47 + .../com/emotion/util/UserContextUtils.java | 240 ++--- .../ai/controller/GuestChatController.java | 102 +-- 84 files changed, 6403 insertions(+), 4310 deletions(-) create mode 100644 backend-single/src/main/java/com/emotion/dto/request/ConversationPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/DiaryCommentPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/DiaryPostPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisCreateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisUpdateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/EmotionRecordCreateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/EmotionRecordPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/EmotionRecordUpdateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/GuestUserInfoRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/WebSocketRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementCreateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementProgressUpdateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementUnlockRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementUpdateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/comment/CommentCreateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/comment/CommentPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/comment/CommentQueryRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/comment/CommentUpdateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostCreateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostUpdateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallCreateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallPageRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallUpdateRequest.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/EmotionAnalysisResponse.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/EmotionRecordResponse.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/WebSocketResponse.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/achievement/AchievementResponse.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/comment/CommentResponse.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/community/CommunityPostResponse.java create mode 100644 backend-single/src/main/java/com/emotion/dto/response/coze/CozeApiCallResponse.java create mode 100644 backend-single/src/main/java/com/emotion/util/TokenUtil.java diff --git a/.augment/rules/rules.md b/.augment/rules/rules.md index 56ccdd9..1bea7c3 100644 --- a/.augment/rules/rules.md +++ b/.augment/rules/rules.md @@ -6,15 +6,15 @@ type: "always_apply" ## 基础设置 -1. 保持对话语言为中文/英文 -2. 系统环境:Mac +1. 保持对话语言为中文 +2. 不允许在未经允许的情况下删除代码和文件,不允许破坏已经正常的业务代码 3. 执行终端命令时要关注执行情况,避免无效等待 ## 代码规范 4. 生成代码时必须添加类级和函数级注释 5. 使用import导包,禁止使用全限定名称引用类 -6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型 +6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型,禁止以枚举类作为方法的入参,确保接口的通用性和可扩展性 7. 新增数据的id使用已存在的雪花算法生成器生成 ## 架构规范 @@ -34,46 +34,198 @@ type: "always_apply" - 使用项目已有的Result做接口返回 13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装 14. Controller层路由禁止添加/api前缀 -15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空 +15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空,必须明确指定value属性值且使用驼峰结构命名,避免使用下划线分隔符。所有接口注解必须显式指定value属性,如@GetMapping(value = "/page")、@PostMapping(value = "/create")等,禁止使用@GetMapping()或@PostMapping等空注解形式 16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息 -17. Controller层接口的Mapping注解(PostMapping、GetMapping、PutMapping、DeleteMapping等)的value属性值要简洁明了,与接口作用相关,名称不宜过长,使用驼峰结构命名 -18. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误 -19. 所有接口注解必须明确指定value属性,不允许使用空注解 -20. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性 -21. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等 -22. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List -23. 接口路径应避免层级过深,建议不超过3级路径结构 -24. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中 +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层根据字段类型和业务需求判断查询方式 ## 环境配置 -25. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 -26. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 +27. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 +28. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 ## 数据库规范 -27. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 -28. 删除操作优先使用逻辑删除,添加deleted字段标识 -29. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 -30. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 -31. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 -32. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 -33. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 +29. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 +30. 删除操作优先使用逻辑删除,添加deleted字段标识 +31. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 +32. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 +33. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 +34. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 +35. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 ## 安全规范 -34. 所有外部输入必须进行参数校验 -35. 敏感信息不得在日志中输出 -36. 数据库操作必须使用参数化查询,防止SQL注入 +36. 所有外部输入必须进行参数校验 +37. 敏感信息不得在日志中输出 +38. 数据库操作必须使用参数化查询,防止SQL注入 ## 性能规范 -37. 避免N+1查询问题,合理使用批量查询 -38. 大数据量查询必须分页处理 -39. 缓存策略要考虑数据一致性问题 +39. 避免N+1查询问题,合理使用批量查询 +40. 大数据量查询必须分页处理 +41. 缓存策略要考虑数据一致性问题 ## 日志规范 -40. 关键业务操作必须记录操作日志 -41. 异常信息要包含足够的上下文信息 -42. 生产环境禁止输出debug级别日志 +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` 返回。 + - 禁止:业务逻辑、事务控制、数据访问、实体与请求/响应的相互转换。 + +- 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 与 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 静态扫描无阻断级问题。 + +- 回滚准备 + - 是否具备配置开关/灰度发布策略;是否提供回滚脚本;数据库迁移是否可逆(或给出补偿方案)。 diff --git a/.cursor/rules/rules.mdc b/.cursor/rules/rules.mdc index f5801d1..cef0288 100644 --- a/.cursor/rules/rules.mdc +++ b/.cursor/rules/rules.mdc @@ -4,23 +4,27 @@ alwaysApply: true # 项目开发规则 ## 基础设置 -1. 保持对话语言为中文/英文 -2. 系统环境:Mac + +1. 保持对话语言为中文 +2. 不允许在未经允许的情况下删除代码和文件,不允许破坏已经正常的业务代码 3. 执行终端命令时要关注执行情况,避免无效等待 ## 代码规范 + 4. 生成代码时必须添加类级和函数级注释 5. 使用import导包,禁止使用全限定名称引用类 -6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型 +6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型,禁止以枚举类作为方法的入参,确保接口的通用性和可扩展性 7. 新增数据的id使用已存在的雪花算法生成器生成 ## 架构规范 + 8. 所有开发必须遵循当前项目规范 9. Controller层禁止添加业务逻辑 10. 使用全局异常处理,禁止使用try-catch 11. 前端接口访问尽可能走网关调用 ## 接口设计规范 + 12. Controller层接口定义要完整: - 入参使用request封装传递到service层 - service层的方法命名与controller层定义的接口的方法名称保持一致 @@ -29,46 +33,198 @@ alwaysApply: true - 使用项目已有的Result做接口返回 13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装 14. Controller层路由禁止添加/api前缀 -15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空 +15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空,必须明确指定value属性值且使用驼峰结构命名,避免使用下划线分隔符。所有接口注解必须显式指定value属性,如@GetMapping(value = "/page")、@PostMapping(value = "/create")等,禁止使用@GetMapping()或@PostMapping等空注解形式 16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息 -17. Controller层接口的Mapping注解(PostMapping、GetMapping、PutMapping、DeleteMapping等)的value属性值要简洁明了,与接口作用相关,名称不宜过长,使用驼峰结构命名 -18. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误 -19. 所有接口注解必须明确指定value属性,不允许使用空注解 -20. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性 -21. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等 -22. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List -23. 接口路径应避免层级过深,建议不超过3级路径结构 -24. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中 +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层根据字段类型和业务需求判断查询方式 ## 环境配置 -25. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 -26. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 +27. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 +28. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 ## 数据库规范 -27. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 -28. 删除操作优先使用逻辑删除,添加deleted字段标识 -29. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 -30. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 -31. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 -32. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 -33. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 +29. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 +30. 删除操作优先使用逻辑删除,添加deleted字段标识 +31. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 +32. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 +33. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 +34. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 +35. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 ## 安全规范 -34. 所有外部输入必须进行参数校验 -35. 敏感信息不得在日志中输出 -36. 数据库操作必须使用参数化查询,防止SQL注入 +36. 所有外部输入必须进行参数校验 +37. 敏感信息不得在日志中输出 +38. 数据库操作必须使用参数化查询,防止SQL注入 ## 性能规范 -37. 避免N+1查询问题,合理使用批量查询 -38. 大数据量查询必须分页处理 -39. 缓存策略要考虑数据一致性问题 +39. 避免N+1查询问题,合理使用批量查询 +40. 大数据量查询必须分页处理 +41. 缓存策略要考虑数据一致性问题 ## 日志规范 -40. 关键业务操作必须记录操作日志 -41. 异常信息要包含足够的上下文信息 -42. 生产环境禁止输出debug级别日志 +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` 返回。 + - 禁止:业务逻辑、事务控制、数据访问、实体与请求/响应的相互转换。 + +- 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 与 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 静态扫描无阻断级问题。 + +- 回滚准备 + - 是否具备配置开关/灰度发布策略;是否提供回滚脚本;数据库迁移是否可逆(或给出补偿方案)。 diff --git a/.qoder/rules/rules.md b/.qoder/rules/rules.md index 37fff8a..347f543 100644 --- a/.qoder/rules/rules.md +++ b/.qoder/rules/rules.md @@ -2,26 +2,31 @@ trigger: always_on alwaysApply: true --- + # 项目开发规则 ## 基础设置 -1. 保持对话语言为中文/英文 -2. 系统环境:Mac + +1. 保持对话语言为中文 +2. 不允许在未经允许的情况下删除代码和文件,不允许破坏已经正常的业务代码 3. 执行终端命令时要关注执行情况,避免无效等待 ## 代码规范 + 4. 生成代码时必须添加类级和函数级注释 5. 使用import导包,禁止使用全限定名称引用类 -6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型 +6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型,禁止以枚举类作为方法的入参,确保接口的通用性和可扩展性 7. 新增数据的id使用已存在的雪花算法生成器生成 ## 架构规范 + 8. 所有开发必须遵循当前项目规范 9. Controller层禁止添加业务逻辑 10. 使用全局异常处理,禁止使用try-catch 11. 前端接口访问尽可能走网关调用 ## 接口设计规范 + 12. Controller层接口定义要完整: - 入参使用request封装传递到service层 - service层的方法命名与controller层定义的接口的方法名称保持一致 @@ -30,46 +35,199 @@ alwaysApply: true - 使用项目已有的Result做接口返回 13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装 14. Controller层路由禁止添加/api前缀 -15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空 +15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空,必须明确指定value属性值且使用驼峰结构命名,避免使用下划线分隔符。所有接口注解必须显式指定value属性,如@GetMapping(value = "/page")、@PostMapping(value = "/create")等,禁止使用@GetMapping()或@PostMapping等空注解形式 16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息 -17. Controller层接口的Mapping注解(PostMapping、GetMapping、PutMapping、DeleteMapping等)的value属性值要简洁明了,与接口作用相关,名称不宜过长,使用驼峰结构命名 -18. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误 -19. 所有接口注解必须明确指定value属性,不允许使用空注解 -20. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性 -21. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等 -22. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List -23. 接口路径应避免层级过深,建议不超过3级路径结构 -24. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中 +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层根据字段类型和业务需求判断查询方式 ## 环境配置 -25. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 -26. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 +27. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 +28. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 ## 数据库规范 -27. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 -28. 删除操作优先使用逻辑删除,添加deleted字段标识 -29. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 -30. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 -31. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 -32. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 -33. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 +29. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 +30. 删除操作优先使用逻辑删除,添加deleted字段标识 +31. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 +32. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 +33. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 +34. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 +35. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 ## 安全规范 -34. 所有外部输入必须进行参数校验 -35. 敏感信息不得在日志中输出 -36. 数据库操作必须使用参数化查询,防止SQL注入 +36. 所有外部输入必须进行参数校验 +37. 敏感信息不得在日志中输出 +38. 数据库操作必须使用参数化查询,防止SQL注入 ## 性能规范 -37. 避免N+1查询问题,合理使用批量查询 -38. 大数据量查询必须分页处理 -39. 缓存策略要考虑数据一致性问题 +39. 避免N+1查询问题,合理使用批量查询 +40. 大数据量查询必须分页处理 +41. 缓存策略要考虑数据一致性问题 ## 日志规范 -40. 关键业务操作必须记录操作日志 -41. 异常信息要包含足够的上下文信息 -42. 生产环境禁止输出debug级别日志 +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` 返回。 + - 禁止:业务逻辑、事务控制、数据访问、实体与请求/响应的相互转换。 + +- 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 与 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 静态扫描无阻断级问题。 + +- 回滚准备 + - 是否具备配置开关/灰度发布策略;是否提供回滚脚本;数据库迁移是否可逆(或给出补偿方案)。 + diff --git a/backend-single/src/main/java/com/emotion/common/PageResult.java b/backend-single/src/main/java/com/emotion/common/PageResult.java index 632ebeb..198d996 100644 --- a/backend-single/src/main/java/com/emotion/common/PageResult.java +++ b/backend-single/src/main/java/com/emotion/common/PageResult.java @@ -52,4 +52,12 @@ public class PageResult { public static PageResult of(IPage page) { return new PageResult<>(page); } -} + + /** + * 设置数据列表 + */ + public PageResult setRecords(List records) { + this.records = records; + return this; + } +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/AchievementController.java b/backend-single/src/main/java/com/emotion/controller/AchievementController.java index 56ed052..6e2813d 100644 --- a/backend-single/src/main/java/com/emotion/controller/AchievementController.java +++ b/backend-single/src/main/java/com/emotion/controller/AchievementController.java @@ -1,99 +1,88 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.emotion.common.PageResult; import com.emotion.common.Result; -import com.emotion.dto.request.PageRequest; -import com.emotion.dto.response.BaseResponse; -import com.emotion.entity.Achievement; +import com.emotion.dto.request.achievement.*; +import com.emotion.dto.response.achievement.AchievementResponse; import com.emotion.service.AchievementService; -import org.springframework.beans.BeanUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.time.format.DateTimeFormatter; +import java.time.LocalDateTime; import java.util.List; -import java.util.stream.Collectors; /** * 成就控制器 * * @author emotion-museum - * @date 2025-07-23 + * @date 2025-09-08 */ @RestController @RequestMapping("/achievement") +@Tag(name = "成就管理", description = "成就的增删改查功能") public class AchievementController { @Autowired private AchievementService achievementService; - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 分页查询成就 */ + @Operation(summary = "分页查询成就", description = "分页查询成就列表") @GetMapping("/page") - public Result> getPage(@Validated PageRequest request) { - IPage page = achievementService.getPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - + public Result> getPage(@Validated AchievementPageRequest request) { + PageResult pageResult = achievementService.getPageWithResponse(request); return Result.success(pageResult); } /** * 根据ID获取成就 */ - @GetMapping("/{id}") - public Result getById(@PathVariable String id) { - Achievement achievement = achievementService.getById(id); - if (achievement == null) { + @Operation(summary = "根据ID获取成就", description = "根据ID获取成就详情") + @GetMapping("/detail") + public Result getById(@RequestParam String id) { + AchievementResponse response = achievementService.getAchievementResponseById(id); + if (response == null) { return Result.notFound("成就不存在"); } - return Result.success(convertToResponse(achievement)); + return Result.success(response); } /** * 创建成就 */ - @PostMapping - public Result create(@RequestBody Achievement achievement) { - boolean saved = achievementService.save(achievement); - if (!saved) { + @Operation(summary = "创建成就", description = "创建新的成就") + @PostMapping("/create") + public Result create(@RequestBody @Validated AchievementCreateRequest request) { + AchievementResponse response = achievementService.createAchievementWithResponse(request); + if (response == null) { return Result.error("创建失败"); } - return Result.success(convertToResponse(achievement)); + return Result.success("创建成功", response); } /** * 更新成就 */ - @PutMapping("/{id}") - public Result update(@PathVariable String id, @RequestBody Achievement achievement) { - achievement.setId(id); - boolean updated = achievementService.updateById(achievement); - if (!updated) { + @Operation(summary = "更新成就", description = "更新指定成就") + @PutMapping("/update") + public Result update(@RequestBody @Validated AchievementUpdateRequest request) { + AchievementResponse response = achievementService.updateAchievementWithResponse(request); + if (response == null) { return Result.error("更新失败"); } - Achievement updatedAchievement = achievementService.getById(id); - return Result.success(convertToResponse(updatedAchievement)); + return Result.success("更新成功", response); } /** * 删除成就 */ - @DeleteMapping("/{id}") - public Result delete(@PathVariable String id) { + @Operation(summary = "删除成就", description = "删除指定成就") + @DeleteMapping("/delete") + public Result delete(@RequestParam String id) { boolean deleted = achievementService.removeById(id); if (!deleted) { return Result.error("删除失败"); @@ -104,55 +93,48 @@ public class AchievementController { /** * 根据分类查询成就 */ - @GetMapping("/category/{category}") - public Result> getByCategory(@PathVariable String category) { - List achievements = achievementService.getByCategory(category); - List responses = achievements.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + @Operation(summary = "根据分类查询成就", description = "根据分类查询成就列表") + @GetMapping("/byCategory") + public Result> getByCategory(@RequestParam String category) { + List responses = achievementService.getByCategoryWithResponse(category); return Result.success(responses); } /** * 根据稀有度查询成就 */ - @GetMapping("/rarity/{rarity}") - public Result> getByRarity(@PathVariable String rarity) { - List achievements = achievementService.getByRarity(rarity); - List responses = achievements.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + @Operation(summary = "根据稀有度查询成就", description = "根据稀有度查询成就列表") + @GetMapping("/byRarity") + public Result> getByRarity(@RequestParam String rarity) { + List responses = achievementService.getByRarityWithResponse(rarity); return Result.success(responses); } /** * 查询已解锁的成就 */ + @Operation(summary = "查询已解锁的成就", description = "查询已解锁的成就列表") @GetMapping("/unlocked") public Result> getUnlockedAchievements() { - List achievements = achievementService.getUnlockedAchievements(); - List responses = achievements.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + List responses = achievementService.getUnlockedAchievementsWithResponse(); return Result.success(responses); } /** * 查询未解锁的成就 */ + @Operation(summary = "查询未解锁的成就", description = "查询未解锁的成就列表") @GetMapping("/locked") public Result> getLockedAchievements() { - List achievements = achievementService.getLockedAchievements(); - List responses = achievements.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + List responses = achievementService.getLockedAchievementsWithResponse(); return Result.success(responses); } /** * 统计已解锁成就数量 */ - @GetMapping("/count/unlocked") + @Operation(summary = "统计已解锁成就数量", description = "统计已解锁成就数量") + @GetMapping("/unlockedCount") public Result countUnlockedAchievements() { Long count = achievementService.countUnlockedAchievements(); return Result.success(count); @@ -161,7 +143,8 @@ public class AchievementController { /** * 统计未解锁成就数量 */ - @GetMapping("/count/locked") + @Operation(summary = "统计未解锁成就数量", description = "统计未解锁成就数量") + @GetMapping("/lockedCount") public Result countLockedAchievements() { Long count = achievementService.countLockedAchievements(); return Result.success(count); @@ -170,9 +153,10 @@ public class AchievementController { /** * 解锁成就 */ - @PutMapping("/{id}/unlock") - public Result unlockAchievement(@PathVariable String id) { - boolean unlocked = achievementService.unlockAchievement(id, java.time.LocalDateTime.now()); + @Operation(summary = "解锁成就", description = "解锁指定成就") + @PutMapping("/unlock") + public Result unlockAchievement(@RequestBody @Validated AchievementUnlockRequest request) { + boolean unlocked = achievementService.unlockAchievement(request.getId(), LocalDateTime.now()); if (!unlocked) { return Result.error("解锁失败"); } @@ -182,9 +166,10 @@ public class AchievementController { /** * 更新成就进度 */ - @PutMapping("/{id}/progress") - public Result updateProgress(@PathVariable String id, @RequestParam Double progress) { - boolean updated = achievementService.updateProgress(id, progress); + @Operation(summary = "更新成就进度", description = "更新指定成就的进度") + @PutMapping("/updateProgress") + public Result updateProgress(@RequestBody @Validated AchievementProgressUpdateRequest request) { + boolean updated = achievementService.updateProgress(request.getId(), request.getProgress()); if (!updated) { return Result.error("更新进度失败"); } @@ -192,36 +177,12 @@ public class AchievementController { } /** - * 转换为响应对象 + * 查询最近解锁的成就 */ - private AchievementResponse convertToResponse(Achievement achievement) { - AchievementResponse response = new AchievementResponse(); - BeanUtils.copyProperties(achievement, response); - response.setId(achievement.getId()); - if (achievement.getCreateTime() != null) { - response.setCreateTime(achievement.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (achievement.getUpdateTime() != null) { - response.setUpdateTime(achievement.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - return response; + @Operation(summary = "查询最近解锁的成就", description = "查询最近解锁的成就列表") + @GetMapping("/recent") + public Result> getRecentlyUnlocked(@RequestParam(defaultValue = "10") Integer limit) { + List responses = achievementService.getRecentlyUnlockedWithResponse(limit); + return Result.success(responses); } - - /** - * 成就响应类 - */ - @lombok.Data - @lombok.EqualsAndHashCode(callSuper = true) - public static class AchievementResponse extends BaseResponse { - private String title; - private String description; - private String category; - private String rarity; - private String conditionType; - private String conditionValue; - private Double progress; - private String unlockedTime; - private Integer isHidden; - private String iconUrl; - } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/AiChatController.java b/backend-single/src/main/java/com/emotion/controller/AiChatController.java index cc987fc..01d98e3 100644 --- a/backend-single/src/main/java/com/emotion/controller/AiChatController.java +++ b/backend-single/src/main/java/com/emotion/controller/AiChatController.java @@ -5,6 +5,8 @@ import com.emotion.dto.request.AiChatRequest; 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; @@ -12,19 +14,14 @@ import com.emotion.dto.response.ChatStatsResponse; import com.emotion.dto.response.GuestChatResponse; import com.emotion.dto.response.GuestUserInfoResponse; import com.emotion.dto.response.ConversationResponse; -import com.emotion.entity.Conversation; import com.emotion.service.AiChatService; -import com.emotion.service.MessageService; -import com.emotion.service.ConversationService; +import com.emotion.util.UserContextUtils; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; -import java.time.format.DateTimeFormatter; -import java.util.Map; /** * AI聊天控制器 @@ -40,34 +37,16 @@ public class AiChatController { @Autowired private AiChatService aiChatService; - @Autowired - private MessageService messageService; - - @Autowired - private ConversationService conversationService; - - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 发送聊天消息 */ @PostMapping("/chat") public Result sendChatMessage(@Valid @RequestBody AiChatRequest request) { - log.info("收到AI聊天请求: conversationId={}, userId={}, message={}", - request.getConversationId(), request.getUserId(), request.getMessage()); - - // 调用AI服务 - String aiReply = aiChatService.sendChatMessage(request.getConversationId(), request.getMessage(), - request.getUserId()); - - // 构建响应 - AiChatResponse response = new AiChatResponse(); - response.setConversationId(request.getConversationId()); - response.setUserMessage(request.getMessage()); - response.setAiReply(aiReply); - response.setUserId(request.getUserId()); - response.setTimestamp(System.currentTimeMillis()); - + log.info("收到AI聊天请求: conversationId={}, userId={}, message={}", + request.getConversationId(), request.getUserId(), request.getMessage()); + + // 调用AI服务 + AiChatResponse response = aiChatService.sendChatMessage(request); return Result.success(response); } @@ -77,18 +56,9 @@ public class AiChatController { @PostMapping("/summary") public Result generateSummary(@Valid @RequestBody AiSummaryRequest request) { log.info("收到对话总结请求: conversationId={}, userId={}", request.getConversationId(), request.getUserId()); - - // 调用AI总结服务 - String summary = aiChatService.generateConversationSummary(request.getConversationId(), - request.getUserId()); - - // 构建响应 - AiSummaryResponse response = new AiSummaryResponse(); - response.setConversationId(request.getConversationId()); - response.setSummary(summary); - response.setUserId(request.getUserId()); - response.setTimestamp(System.currentTimeMillis()); - + + // 调用AI总结服务 + AiSummaryResponse response = aiChatService.generateConversationSummary(request); return Result.success(response); } @@ -99,15 +69,7 @@ public class AiChatController { public Result getServiceStatus() { log.info("获取AI服务状态"); - boolean available = aiChatService.isServiceAvailable(); - String status = aiChatService.getServiceStatus(); - - // 构建响应 - AiStatusResponse response = new AiStatusResponse(); - response.setAvailable(available); - response.setStatus(status); - response.setTimestamp(System.currentTimeMillis()); - + AiStatusResponse response = aiChatService.getServiceStatus(); return Result.success(response); } @@ -115,124 +77,48 @@ public class AiChatController { * 获取聊天记录统计 */ @GetMapping("/stats") - public Result getChatStats(@RequestParam(required = false) String userId, - @RequestParam(required = false) String conversationId) { - log.info("获取聊天统计: userId={}, conversationId={}", userId, conversationId); + public Result getChatStats(@Valid ChatStatsRequest request) { + log.info("获取聊天统计: userId={}, conversationId={}", request.getUserId(), request.getConversationId()); - // 构建响应 - ChatStatsResponse response = new ChatStatsResponse(); - - if (userId != null && !userId.trim().isEmpty()) { - Long userConversationCount = conversationService.countByUserId(userId); - Long activeConversationCount = conversationService.countActiveByUserId(userId); - - response.setUserConversationCount(userConversationCount); - response.setActiveConversationCount(activeConversationCount); - } - - if (conversationId != null && !conversationId.trim().isEmpty()) { - Long conversationMessageCount = messageService.countByConversationId(conversationId); - response.setConversationMessageCount(conversationMessageCount); - } - - response.setTimestamp(System.currentTimeMillis()); - - return Result.success(response); - } - - /** - * 访客聊天(不需要登录) - */ - @PostMapping("/guest/chat") - public Result guestChat(@Valid @RequestBody GuestChatRequest request, - HttpServletRequest httpRequest) { - String clientIp = getClientIp(httpRequest); - log.info("访客聊天请求: {}, IP: {}", request.getMessage(), clientIp); - - // 调用AI服务 - Map aiResponse = aiChatService.guestChat(request.getMessage(), clientIp); - - // 构建响应 - GuestChatResponse response = new GuestChatResponse(); - response.setMessage((String) aiResponse.get("message")); - response.setMessageId((String) aiResponse.get("messageId")); - response.setTimestamp((Long) aiResponse.get("timestamp")); - response.setError((Boolean) aiResponse.get("error")); - - return Result.success("发送成功", response); - } - - /** - * 获取访客用户信息 - */ - @GetMapping("/guest/user/info") - public Result getGuestUserInfo(HttpServletRequest request) { - String clientIp = getClientIp(request); - log.info("获取访客用户信息: IP={}", clientIp); - - // 调用AI服务 - Map userInfo = aiChatService.getGuestUserInfo(clientIp); - - // 构建响应 - GuestUserInfoResponse response = new GuestUserInfoResponse(); - response.setId((String) userInfo.get("id")); - response.setUsername((String) userInfo.get("username")); - response.setNickname((String) userInfo.get("nickname")); - response.setType((String) userInfo.get("type")); - response.setClientIp((String) userInfo.get("clientIp")); - response.setUserCreateTime((Long) userInfo.get("createTime")); - - return Result.success(response); - } - - /** - * 创建对话 - */ - @PostMapping("/conversation/create") - public Result createConversation(@Valid @RequestBody ConversationCreateRequest request, - HttpServletRequest httpRequest) { - log.info("创建对话请求: userId={}, title={}", request.getUserId(), request.getTitle()); - - String clientIp = getClientIp(httpRequest); - - // 调用AI服务创建对话 - Map aiConversation = aiChatService.createConversation(request.getUserId(), - request.getTitle()); - - // 创建数据库对话记录 - Conversation conversation = conversationService.createConversation( - request.getUserId(), - request.getTitle(), - request.getType() != null ? request.getType() : "user"); - - // 构建响应 - ConversationResponse response = new ConversationResponse(); - BeanUtils.copyProperties(conversation, response); - response.setId(conversation.getId()); - if (conversation.getCreateTime() != null) { - response.setCreateTime(conversation.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (conversation.getUpdateTime() != null) { - response.setUpdateTime(conversation.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - - return Result.success("创建成功", response); + ChatStatsResponse response = aiChatService.getChatStats(request); + return Result.success(response); } /** - * 获取客户端IP + * 访客聊天(不需要登录) */ - private String getClientIp(HttpServletRequest request) { - String xForwardedFor = request.getHeader("X-Forwarded-For"); - if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) { - return xForwardedFor.split(",")[0].trim(); - } + @PostMapping("/guestChat") + public Result guestChat(@Valid @RequestBody GuestChatRequest request, + HttpServletRequest httpRequest) { + String clientIp = UserContextUtils.getClientIpAddress(httpRequest); + log.info("访客聊天请求: {}, IP: {}", request.getMessage(), clientIp); - String xRealIp = request.getHeader("X-Real-IP"); - if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) { - return xRealIp; - } - - return request.getRemoteAddr(); + GuestChatResponse response = aiChatService.guestChat(request, clientIp); + return Result.success("发送成功", response); } -} + + /** + * 获取访客用户信息 + */ + @GetMapping("/guestUserInfo") + public Result getGuestUserInfo(HttpServletRequest request) { + String clientIp = UserContextUtils.getClientIpAddress(request); + log.info("获取访客用户信息: IP={}", clientIp); + + GuestUserInfoResponse response = aiChatService.getGuestUserInfo(clientIp); + return Result.success(response); + } + + /** + * 创建对话 + */ + @PostMapping("/createConversation") + public Result createConversation(@Valid @RequestBody ConversationCreateRequest request, + HttpServletRequest httpRequest) { + log.info("创建对话请求: userId={}, title={}", request.getUserId(), request.getTitle()); + + String clientIp = UserContextUtils.getClientIpAddress(httpRequest); + ConversationResponse response = aiChatService.createConversation(request, clientIp); + return Result.success("创建成功", response); + } +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/AuthController.java b/backend-single/src/main/java/com/emotion/controller/AuthController.java index eef1f02..208232b 100644 --- a/backend-single/src/main/java/com/emotion/controller/AuthController.java +++ b/backend-single/src/main/java/com/emotion/controller/AuthController.java @@ -9,6 +9,7 @@ import com.emotion.dto.response.CaptchaResponse; import com.emotion.dto.response.UserInfoResponse; import com.emotion.service.AuthService; import com.emotion.service.TokenService; +import com.emotion.util.UserContextUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -52,10 +53,9 @@ public class AuthController { /** * 获取当前用户信息 */ - @GetMapping("/user/info") + @GetMapping("/userInfo") public Result getCurrentUserInfo(HttpServletRequest request) { - String token = extractToken(request); - UserInfoResponse userInfo = tokenService.getUserInfoByToken(token); + UserInfoResponse userInfo = tokenService.getUserInfoByToken(request); return Result.success(userInfo); } @@ -73,15 +73,14 @@ public class AuthController { */ @PostMapping("/logout") public Result logout(HttpServletRequest request) { - String token = extractToken(request); - authService.logoutByToken(token); + authService.logoutByToken(request); return Result.success(); } /** * 刷新访问令牌 */ - @PostMapping("/refresh") + @PostMapping("/refreshToken") public Result refreshToken(@Valid @RequestBody RefreshTokenRequest request) { AuthResponse response = authService.refreshToken(request.getRefreshToken()); return Result.success("令牌刷新成功", response); @@ -90,14 +89,9 @@ public class AuthController { /** * 验证访问令牌 */ - @GetMapping("/validate") + @GetMapping("/validateToken") public Result validateToken(HttpServletRequest request) { - String token = extractToken(request); - if (token == null) { - return Result.success(false); - } - - boolean isValid = authService.validateToken(token); + boolean isValid = authService.validateToken(request); return Result.success(isValid); } @@ -106,15 +100,14 @@ public class AuthController { */ @GetMapping("/username") public Result getUsernameFromToken(HttpServletRequest request) { - String token = extractToken(request); - String username = tokenService.getUsernameByToken(token); + String username = tokenService.getUsernameByToken(request); return Result.success(username); } /** * 检查账号是否存在 */ - @GetMapping("/check-account") + @GetMapping("/checkAccount") public Result checkAccount(@RequestParam String account) { boolean exists = authService.existsByAccount(account); return Result.success(exists); @@ -123,7 +116,7 @@ public class AuthController { /** * 检查邮箱是否存在 */ - @GetMapping("/check-email") + @GetMapping("/checkEmail") public Result checkEmail(@RequestParam String email) { boolean exists = authService.existsByEmail(email); return Result.success(exists); @@ -132,27 +125,9 @@ public class AuthController { /** * 检查手机号是否存在 */ - @GetMapping("/check-phone") + @GetMapping("/checkPhone") public Result checkPhone(@RequestParam String phone) { boolean exists = authService.existsByPhone(phone); return Result.success(exists); } - - /** - * 从请求中提取访问令牌 - */ - private String extractToken(HttpServletRequest request) { - String authHeader = request.getHeader("Authorization"); - if (authHeader != null && authHeader.startsWith("Bearer ")) { - return authHeader.substring(7); - } - - // 也可以从请求参数中获取 - String tokenParam = request.getParameter("token"); - if (tokenParam != null && !tokenParam.trim().isEmpty()) { - return tokenParam.trim(); - } - - return null; - } } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/ChatWebSocketController.java b/backend-single/src/main/java/com/emotion/controller/ChatWebSocketController.java index 085b1bf..d084c12 100644 --- a/backend-single/src/main/java/com/emotion/controller/ChatWebSocketController.java +++ b/backend-single/src/main/java/com/emotion/controller/ChatWebSocketController.java @@ -1,6 +1,6 @@ package com.emotion.controller; -import com.emotion.dto.websocket.ChatRequest; +import com.emotion.dto.request.WebSocketRequest; import com.emotion.dto.websocket.ConnectRequest; import com.emotion.service.WebSocketService; import lombok.extern.slf4j.Slf4j; @@ -14,14 +14,15 @@ import javax.validation.Valid; import java.security.Principal; /** - * 优化的WebSocket聊天控制器 - * 使用规范的请求对象封装参数 + * WebSocket聊天控制器 + * 使用规范的请求对象封装参数,符合项目开发规则 * * @author emotion-museum - * @date 2025-07-23 + * @date 2025-09-08 */ @Slf4j @Controller +@MessageMapping("/chat") public class ChatWebSocketController { @Autowired @@ -29,111 +30,55 @@ public class ChatWebSocketController { /** * 处理聊天消息 - * @param chatRequest 聊天请求对象 - * @param headerAccessor 消息头访问器 - * @param principal 用户主体 + * + * @param webSocketRequest WebSocket请求对象 + * @param headerAccessor 消息头访问器 + * @param principal 用户主体 */ - @MessageMapping("/chat.send") - public void handleChatMessage(@Valid @Payload ChatRequest chatRequest, - SimpMessageHeaderAccessor headerAccessor, - Principal principal) { - try { - log.info("收到WebSocket聊天消息: {}", chatRequest); - - // 获取会话ID - String sessionId = headerAccessor.getSessionId(); - - // 如果请求中没有发送者ID,尝试从Principal获取 - if (chatRequest.getSenderId() == null && principal != null) { - chatRequest.setSenderId(principal.getName()); - } - - // 如果还是没有发送者ID,使用会话ID作为访客ID - if (chatRequest.getSenderId() == null) { - chatRequest.setSenderId("guest_" + sessionId); - chatRequest.setSenderType(ChatRequest.SenderType.GUEST); - } - - // 设置时间戳 - if (chatRequest.getTimestamp() == null) { - chatRequest.setTimestamp(System.currentTimeMillis()); - } - - // 处理聊天消息 - webSocketService.handleChatMessage(chatRequest, sessionId, principal); - - } catch (Exception e) { - log.error("处理WebSocket聊天消息失败", e); - } + @MessageMapping("/send") + public void handleChatMessage(@Valid @Payload WebSocketRequest webSocketRequest, + SimpMessageHeaderAccessor headerAccessor, + Principal principal) { + String sessionId = headerAccessor.getSessionId(); + webSocketService.handleChatMessage(webSocketRequest, sessionId, principal); } /** * 处理用户连接 + * * @param connectRequest 连接请求对象 * @param headerAccessor 消息头访问器 - * @param principal 用户主体 + * @param principal 用户主体 */ - @MessageMapping("/chat.connect") + @MessageMapping("/connect") public void connectUser(@Payload ConnectRequest connectRequest, - SimpMessageHeaderAccessor headerAccessor, - Principal principal) { - try { - String sessionId = headerAccessor.getSessionId(); - log.info("用户连接WebSocket: connectRequest={}, sessionId={}, principal={}", - connectRequest, sessionId, principal); - - // 如果请求中没有用户ID,尝试从Principal获取 - if (connectRequest.getUserId() == null && principal != null) { - connectRequest.setUserId(principal.getName()); - } - - // 设置连接时间戳 - if (connectRequest.getTimestamp() == null) { - connectRequest.setTimestamp(System.currentTimeMillis()); - } - - // 处理用户连接 - webSocketService.handleUserConnect(connectRequest, sessionId, principal); - - } catch (Exception e) { - log.error("处理用户WebSocket连接失败", e); - } + SimpMessageHeaderAccessor headerAccessor, + Principal principal) { + String sessionId = headerAccessor.getSessionId(); + webSocketService.handleUserConnect(connectRequest, sessionId, principal); } /** * 处理用户断开连接 + * * @param headerAccessor 消息头访问器 - * @param principal 用户主体 + * @param principal 用户主体 */ - @MessageMapping("/chat.disconnect") + @MessageMapping("/disconnect") public void disconnectUser(SimpMessageHeaderAccessor headerAccessor, Principal principal) { - try { - String sessionId = headerAccessor.getSessionId(); - log.info("用户断开WebSocket连接: sessionId={}, principal={}", sessionId, principal); - - // 处理用户断开连接 - webSocketService.handleUserDisconnect(sessionId, principal); - - } catch (Exception e) { - log.error("处理用户WebSocket断开连接失败", e); - } + String sessionId = headerAccessor.getSessionId(); + webSocketService.handleUserDisconnect(sessionId, principal); } /** * 处理心跳消息 + * * @param headerAccessor 消息头访问器 - * @param principal 用户主体 + * @param principal 用户主体 */ - @MessageMapping("/chat.heartbeat") + @MessageMapping("/heartbeat") public void heartbeat(SimpMessageHeaderAccessor headerAccessor, Principal principal) { - try { - String sessionId = headerAccessor.getSessionId(); - - // 处理心跳消息 - webSocketService.handleHeartbeat(sessionId, principal); - - } catch (Exception e) { - log.error("处理WebSocket心跳失败", e); - } + String sessionId = headerAccessor.getSessionId(); + webSocketService.handleHeartbeat(sessionId, principal); } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/CommentController.java b/backend-single/src/main/java/com/emotion/controller/CommentController.java index 34c33ba..6dadb01 100644 --- a/backend-single/src/main/java/com/emotion/controller/CommentController.java +++ b/backend-single/src/main/java/com/emotion/controller/CommentController.java @@ -1,22 +1,17 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.emotion.common.PageResult; import com.emotion.common.Result; -import com.emotion.dto.request.PageRequest; -import com.emotion.dto.response.BaseResponse; -import com.emotion.entity.Comment; +import com.emotion.dto.request.comment.CommentCreateRequest; +import com.emotion.dto.request.comment.CommentUpdateRequest; +import com.emotion.dto.request.comment.CommentPageRequest; +import com.emotion.dto.response.comment.CommentResponse; import com.emotion.service.CommentService; -import org.springframework.beans.BeanUtils; +import com.emotion.util.UserContextUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import javax.validation.constraints.NotBlank; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.stream.Collectors; - /** * 评论控制器 * @@ -30,114 +25,44 @@ public class CommentController { @Autowired private CommentService commentService; - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 分页查询评论 */ - @GetMapping("/page") - public Result> getPage(@Validated PageRequest request) { - IPage page = commentService.getPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - + @GetMapping(value = "/page") + public Result> getCommentPage(@Validated CommentPageRequest request) { + PageResult pageResult = commentService.getPage(request); return Result.success(pageResult); } - /** - * 根据帖子ID分页查询评论 - */ - @GetMapping("/post/{postId}/page") - public Result> getPageByPostId(@PathVariable String postId, @Validated PageRequest request) { - IPage page = commentService.getPageByPostId(request, postId); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据用户ID分页查询评论 - */ - @GetMapping("/user/{userId}/page") - public Result> getPageByUserId(@PathVariable String userId, @Validated PageRequest request) { - IPage page = commentService.getPageByUserId(request, userId); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据ID获取评论 - */ - @GetMapping("/{id}") - public Result getById(@PathVariable String id) { - Comment comment = commentService.getById(id); - if (comment == null) { - return Result.notFound("评论不存在"); - } - return Result.success(convertToResponse(comment)); - } - /** * 创建评论 */ - @PostMapping - public Result create(@RequestBody @Validated CommentCreateRequest request) { - Comment comment = commentService.createComment( - request.getPostId(), - request.getUserId(), - request.getContent(), - request.getReplyToId() - ); - return Result.success(convertToResponse(comment)); + @PostMapping(value = "/create") + public Result createComment(@RequestBody @Validated CommentCreateRequest request) { + // 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID + String currentUserId = UserContextUtils.getCurrentUserId(); + if (currentUserId != null) { + request.setUserId(currentUserId); + } + CommentResponse response = commentService.create(request); + return Result.success(response); } /** * 更新评论 */ - @PutMapping("/{id}") - public Result update(@PathVariable String id, @RequestBody Comment comment) { - comment.setId(id); - boolean updated = commentService.updateById(comment); - if (!updated) { - return Result.error("更新失败"); - } - Comment updatedComment = commentService.getById(id); - return Result.success(convertToResponse(updatedComment)); + @PutMapping(value = "/update") + public Result updateComment(@RequestBody @Validated CommentUpdateRequest request) { + CommentResponse response = commentService.update(request); + return Result.success(response); } /** * 删除评论 */ - @DeleteMapping("/{id}") - public Result delete(@PathVariable String id) { - boolean deleted = commentService.removeById(id); + @DeleteMapping(value = "/delete") + public Result deleteComment(@RequestParam String id) { + boolean deleted = commentService.delete(id); if (!deleted) { return Result.error("删除失败"); } @@ -145,105 +70,14 @@ public class CommentController { } /** - * 根据帖子ID查询顶级评论 + * 根据ID获取评论 */ - @GetMapping("/post/{postId}/top-level") - public Result> getTopLevelCommentsByPostId(@PathVariable String postId) { - List comments = commentService.getTopLevelCommentsByPostId(postId); - List responses = comments.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 根据评论ID查询回复 - */ - @GetMapping("/{commentId}/replies") - public Result> getRepliesByCommentId(@PathVariable String commentId) { - List replies = commentService.getRepliesByCommentId(commentId); - List responses = replies.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 统计帖子的评论数量 - */ - @GetMapping("/post/{postId}/count") - public Result countByPostId(@PathVariable String postId) { - Long count = commentService.countByPostId(postId); - return Result.success(count); - } - - /** - * 点赞评论 - */ - @PutMapping("/{id}/like") - public Result likeComment(@PathVariable String id) { - boolean updated = commentService.updateLikes(id, 1); - if (!updated) { - return Result.error("点赞失败"); + @GetMapping(value = "/detail") + public Result getCommentById(@RequestParam String id) { + CommentResponse response = commentService.getById(id); + if (response == null) { + return Result.notFound("评论不存在"); } - return Result.success(); + return Result.success(response); } - - /** - * 取消点赞评论 - */ - @PutMapping("/{id}/unlike") - public Result unlikeComment(@PathVariable String id) { - boolean updated = commentService.updateLikes(id, -1); - if (!updated) { - return Result.error("取消点赞失败"); - } - return Result.success(); - } - - /** - * 转换为响应对象 - */ - private CommentResponse convertToResponse(Comment comment) { - CommentResponse response = new CommentResponse(); - BeanUtils.copyProperties(comment, response); - response.setId(comment.getId()); - if (comment.getCreateTime() != null) { - response.setCreateTime(comment.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (comment.getUpdateTime() != null) { - response.setUpdateTime(comment.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - return response; - } - - /** - * 评论创建请求 - */ - @lombok.Data - public static class CommentCreateRequest { - @NotBlank(message = "帖子ID不能为空") - private String postId; - - @NotBlank(message = "用户ID不能为空") - private String userId; - - @NotBlank(message = "评论内容不能为空") - private String content; - - private String replyToId; - } - - /** - * 评论响应类 - */ - @lombok.Data - @lombok.EqualsAndHashCode(callSuper = true) - public static class CommentResponse extends BaseResponse { - private String postId; - private String userId; - private String content; - private String replyToId; - private Integer likes; - } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/CommunityPostController.java b/backend-single/src/main/java/com/emotion/controller/CommunityPostController.java index 00110b0..130ac5c 100644 --- a/backend-single/src/main/java/com/emotion/controller/CommunityPostController.java +++ b/backend-single/src/main/java/com/emotion/controller/CommunityPostController.java @@ -1,22 +1,16 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.emotion.common.PageResult; import com.emotion.common.Result; -import com.emotion.dto.request.PageRequest; -import com.emotion.dto.response.BaseResponse; -import com.emotion.entity.CommunityPost; +import com.emotion.dto.request.community.CommunityPostCreateRequest; +import com.emotion.dto.request.community.CommunityPostUpdateRequest; +import com.emotion.dto.request.community.CommunityPostPageRequest; +import com.emotion.dto.response.community.CommunityPostResponse; import com.emotion.service.CommunityPostService; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import javax.validation.constraints.NotBlank; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.stream.Collectors; - /** * 社区帖子控制器 * @@ -24,266 +18,60 @@ import java.util.stream.Collectors; * @date 2025-07-23 */ @RestController -@RequestMapping("/community-post") +@RequestMapping("/communityPost") public class CommunityPostController { @Autowired private CommunityPostService communityPostService; - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 分页查询帖子 */ - @GetMapping("/page") - public Result> getPage(@Validated PageRequest request) { - IPage page = communityPostService.getPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 分页查询公开帖子 - */ - @GetMapping("/public/page") - public Result> getPublicPostsPage(@Validated PageRequest request) { - IPage page = communityPostService.getPublicPostsPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据用户ID分页查询帖子 - */ - @GetMapping("/user/{userId}/page") - public Result> getPageByUserId(@PathVariable String userId, @Validated PageRequest request) { - IPage page = communityPostService.getPageByUserId(request, userId); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - + @GetMapping(value = "/page") + public Result> getPage(@Validated CommunityPostPageRequest request) { + PageResult pageResult = communityPostService.getPage(request); return Result.success(pageResult); } /** * 根据ID获取帖子 */ - @GetMapping("/{id}") - public Result getById(@PathVariable String id) { - CommunityPost post = communityPostService.getById(id); - if (post == null) { + @GetMapping(value = "/detail") + public Result getById(@RequestParam String id) { + CommunityPostResponse response = communityPostService.getById(id); + if (response == null) { return Result.notFound("帖子不存在"); } - // 增加浏览数 - communityPostService.incrementViewCount(id); - return Result.success(convertToResponse(post)); + return Result.success(response); } /** * 创建帖子 */ - @PostMapping + @PostMapping(value = "/create") public Result create(@RequestBody @Validated CommunityPostCreateRequest request) { - CommunityPost post = communityPostService.createPost( - request.getUserId(), - request.getTitle(), - request.getContent(), - request.getType(), - request.getLocationId(), - request.getTags(), - request.getIsPrivate() - ); - return Result.success(convertToResponse(post)); + CommunityPostResponse response = communityPostService.create(request); + return Result.success(response); } /** * 更新帖子 */ - @PutMapping("/{id}") - public Result update(@PathVariable String id, @RequestBody CommunityPost post) { - post.setId(id); - boolean updated = communityPostService.updateById(post); - if (!updated) { - return Result.error("更新失败"); - } - CommunityPost updatedPost = communityPostService.getById(id); - return Result.success(convertToResponse(updatedPost)); + @PutMapping(value = "/update") + public Result update(@RequestBody @Validated CommunityPostUpdateRequest request) { + CommunityPostResponse response = communityPostService.update(request); + return Result.success(response); } /** * 删除帖子 */ - @DeleteMapping("/{id}") - public Result delete(@PathVariable String id) { - boolean deleted = communityPostService.removeById(id); + @DeleteMapping(value = "/delete") + public Result delete(@RequestParam String id) { + boolean deleted = communityPostService.delete(id); if (!deleted) { return Result.error("删除失败"); } return Result.success(); } - - /** - * 根据类型查询帖子 - */ - @GetMapping("/type/{type}") - public Result> getByType(@PathVariable String type) { - List posts = communityPostService.getByType(type); - List responses = posts.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 根据地点ID查询帖子 - */ - @GetMapping("/location/{locationId}") - public Result> getByLocationId(@PathVariable String locationId) { - List posts = communityPostService.getByLocationId(locationId); - List responses = posts.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 查询最受欢迎的帖子 - */ - @GetMapping("/popular") - public Result> getMostLikedPosts(@RequestParam(defaultValue = "10") Integer limit) { - List posts = communityPostService.getMostLikedPosts(limit); - List responses = posts.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 查询最新的帖子 - */ - @GetMapping("/latest") - public Result> getLatestPosts(@RequestParam(defaultValue = "10") Integer limit) { - List posts = communityPostService.getLatestPosts(limit); - List responses = posts.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 点赞帖子 - */ - @PutMapping("/{id}/like") - public Result likePost(@PathVariable String id) { - boolean updated = communityPostService.updateLikes(id, 1); - if (!updated) { - return Result.error("点赞失败"); - } - return Result.success(); - } - - /** - * 取消点赞帖子 - */ - @PutMapping("/{id}/unlike") - public Result unlikePost(@PathVariable String id) { - boolean updated = communityPostService.updateLikes(id, -1); - if (!updated) { - return Result.error("取消点赞失败"); - } - return Result.success(); - } - - /** - * 根据标签搜索帖子 - */ - @GetMapping("/search/tag") - public Result> getByTag(@RequestParam String tag) { - List posts = communityPostService.getByTag(tag); - List responses = posts.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 转换为响应对象 - */ - private CommunityPostResponse convertToResponse(CommunityPost post) { - CommunityPostResponse response = new CommunityPostResponse(); - BeanUtils.copyProperties(post, response); - response.setId(post.getId()); - if (post.getCreateTime() != null) { - response.setCreateTime(post.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (post.getUpdateTime() != null) { - response.setUpdateTime(post.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - return response; - } - - /** - * 帖子创建请求 - */ - @lombok.Data - public static class CommunityPostCreateRequest { - @NotBlank(message = "用户ID不能为空") - private String userId; - - @NotBlank(message = "标题不能为空") - private String title; - - @NotBlank(message = "内容不能为空") - private String content; - - private String type; - private String locationId; - private String tags; - private Integer isPrivate; - } - - /** - * 帖子响应类 - */ - @lombok.Data - @lombok.EqualsAndHashCode(callSuper = true) - public static class CommunityPostResponse extends BaseResponse { - private String userId; - private String title; - private String content; - private String type; - private String locationId; - private String tags; - private Integer likes; - private Integer viewCount; - private Integer commentCount; - private Integer isPrivate; - } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/ConversationController.java b/backend-single/src/main/java/com/emotion/controller/ConversationController.java index ed2e736..fccb24d 100644 --- a/backend-single/src/main/java/com/emotion/controller/ConversationController.java +++ b/backend-single/src/main/java/com/emotion/controller/ConversationController.java @@ -1,22 +1,17 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.emotion.common.PageResult; import com.emotion.common.Result; -import com.emotion.dto.request.PageRequest; import com.emotion.dto.request.ConversationCreateRequest; +import com.emotion.dto.request.ConversationPageRequest; import com.emotion.dto.response.ConversationResponse; -import com.emotion.entity.Conversation; import com.emotion.service.ConversationService; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; -import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.stream.Collectors; /** * 对话控制器 @@ -31,95 +26,51 @@ public class ConversationController { @Autowired private ConversationService conversationService; - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 分页查询对话 */ - @GetMapping("/page") - public Result> getPage(@Valid PageRequest request) { - IPage page = conversationService.getPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据用户ID分页查询对话 - */ - @GetMapping("/user/{userId}/page") - public Result> getPageByUserId(@PathVariable String userId, - @Valid PageRequest request) { - IPage page = conversationService.getPageByUserId(request, userId); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - + @GetMapping(value = "/page") + public Result> getPage(@Valid ConversationPageRequest request) { + PageResult pageResult = conversationService.getPageWithResponse(request); return Result.success(pageResult); } /** * 根据ID获取对话 */ - @GetMapping("/{id}") - public Result getById(@PathVariable String id) { - Conversation conversation = conversationService.getById(id); - if (conversation == null) { + @GetMapping(value = "/detail") + public Result getById(@RequestParam String id) { + ConversationResponse response = conversationService.getConversationResponseById(id); + if (response == null) { return Result.notFound("对话不存在"); } - return Result.success(convertToResponse(conversation)); + return Result.success(response); } /** * 创建对话 */ - @PostMapping + @PostMapping(value = "/create") public Result create(@Valid @RequestBody ConversationCreateRequest request, HttpServletRequest httpRequest) { - String clientIp = getClientIp(httpRequest); - Conversation conversation = conversationService.createConversation( - request.getUserId(), - request.getTitle(), - null // cozeConversationId - ); - return Result.success(convertToResponse(conversation)); + ConversationResponse response = conversationService.createConversationWithResponse(request, httpRequest); + return Result.success(response); } /** * 更新对话 */ - @PutMapping("/{id}") - public Result update(@PathVariable String id, @RequestBody Conversation conversation) { - conversation.setId(id); - boolean updated = conversationService.updateById(conversation); - if (!updated) { - return Result.error("更新失败"); - } - Conversation updatedConversation = conversationService.getById(id); - return Result.success(convertToResponse(updatedConversation)); + @PutMapping(value = "/update") + public Result update(@RequestBody ConversationCreateRequest request) { + ConversationResponse response = conversationService.updateConversationWithResponse(request); + return Result.success(response); } /** * 删除对话 */ - @DeleteMapping("/{id}") - public Result delete(@PathVariable String id) { + @DeleteMapping(value = "/delete") + public Result delete(@RequestParam String id) { boolean deleted = conversationService.removeById(id); if (!deleted) { return Result.error("删除失败"); @@ -127,93 +78,35 @@ public class ConversationController { return Result.success(); } - /** - * 根据用户ID查询对话 - */ - @GetMapping("/user/{userId}") - public Result> getByUserId(@PathVariable String userId) { - List conversations = conversationService.getByUserId(userId); - List responses = conversations.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - /** * 获取活跃对话 */ - @GetMapping("/active") + @GetMapping(value = "/active") public Result> getActiveConversations() { - // 暂时返回空列表,因为接口中没有这个方法 - return Result.success(); + List responses = conversationService.getActiveConversationsWithResponse(); + return Result.success(responses); } /** * 获取归档对话 */ - @GetMapping("/archived") + @GetMapping(value = "/archived") public Result> getArchivedConversations() { - // 暂时返回空列表,因为接口中没有这个方法 + List responses = conversationService.getArchivedConversationsWithResponse(); + return Result.success(responses); + } + + /** + * 更新对话状态 + */ + @PutMapping(value = "/status") + public Result updateConversationStatus( + @RequestParam String id, + @RequestParam String status) { + boolean updated = conversationService.updateConversationStatus(id, status); + if (!updated) { + return Result.error("更新状态失败"); + } return Result.success(); } - - /** - * 归档对话 - */ - @PutMapping("/{id}/archive") - public Result archiveConversation(@PathVariable String id) { - // 暂时返回成功,因为接口中没有这个方法 - return Result.success(); - } - - /** - * 激活对话 - */ - @PutMapping("/{id}/activate") - public Result activateConversation(@PathVariable String id) { - // 暂时返回成功,因为接口中没有这个方法 - return Result.success(); - } - - /** - * 统计用户对话数量 - */ - @GetMapping("/user/{userId}/count") - public Result countByUserId(@PathVariable String userId) { - Long count = conversationService.countByUserId(userId); - return Result.success(count); - } - - /** - * 获取客户端IP - */ - private String getClientIp(HttpServletRequest request) { - String xForwardedFor = request.getHeader("X-Forwarded-For"); - if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) { - return xForwardedFor.split(",")[0]; - } - - String xRealIp = request.getHeader("X-Real-IP"); - if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) { - return xRealIp; - } - - return request.getRemoteAddr(); - } - - /** - * 转换为响应对象 - */ - private ConversationResponse convertToResponse(Conversation conversation) { - ConversationResponse response = new ConversationResponse(); - BeanUtils.copyProperties(conversation, response); - response.setId(conversation.getId()); - if (conversation.getCreateTime() != null) { - response.setCreateTime(conversation.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (conversation.getUpdateTime() != null) { - response.setUpdateTime(conversation.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - return response; - } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/CozeApiCallController.java b/backend-single/src/main/java/com/emotion/controller/CozeApiCallController.java index bcd5e45..420a5bd 100644 --- a/backend-single/src/main/java/com/emotion/controller/CozeApiCallController.java +++ b/backend-single/src/main/java/com/emotion/controller/CozeApiCallController.java @@ -1,20 +1,17 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.emotion.common.PageResult; import com.emotion.common.Result; -import com.emotion.dto.request.PageRequest; -import com.emotion.dto.response.BaseResponse; -import com.emotion.entity.CozeApiCall; +import com.emotion.dto.request.coze.CozeApiCallPageRequest; +import com.emotion.dto.request.coze.CozeApiCallCreateRequest; +import com.emotion.dto.request.coze.CozeApiCallUpdateRequest; +import com.emotion.dto.response.coze.CozeApiCallResponse; import com.emotion.service.CozeApiCallService; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.stream.Collectors; +import java.math.BigDecimal; /** * Coze API调用记录控制器 @@ -29,229 +26,54 @@ public class CozeApiCallController { @Autowired private CozeApiCallService cozeApiCallService; - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 分页查询API调用记录 */ - @GetMapping("/page") - public Result> getPage(@Validated PageRequest request) { - IPage page = cozeApiCallService.getPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据会话ID分页查询API调用记录 - */ - @GetMapping("/conversation/{conversationId}/page") - public Result> getPageByConversationId(@PathVariable String conversationId, @Validated PageRequest request) { - IPage page = cozeApiCallService.getPageByConversationId(request, conversationId); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据用户ID分页查询API调用记录 - */ - @GetMapping("/user/{userId}/page") - public Result> getPageByUserId(@PathVariable String userId, @Validated PageRequest request) { - IPage page = cozeApiCallService.getPageByUserId(request, userId); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - + @GetMapping(value = "/page") + public Result> getCozeApiCallPage(@Validated CozeApiCallPageRequest request) { + PageResult pageResult = cozeApiCallService.getPage(request); return Result.success(pageResult); } /** * 根据ID获取API调用记录 */ - @GetMapping("/{id}") - public Result getById(@PathVariable String id) { - CozeApiCall apiCall = cozeApiCallService.getById(id); - if (apiCall == null) { + @GetMapping(value = "/detail") + public Result getCozeApiCallById(@RequestParam String id) { + CozeApiCallResponse response = cozeApiCallService.getById(id); + if (response == null) { return Result.notFound("API调用记录不存在"); } - return Result.success(convertToResponse(apiCall)); - } - - /** - * 根据Bot ID查询API调用记录 - */ - @GetMapping("/bot/{botId}") - public Result> getByBotId(@PathVariable String botId) { - List apiCalls = cozeApiCallService.getByBotId(botId); - List responses = apiCalls.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 根据状态查询API调用记录 - */ - @GetMapping("/status/{status}") - public Result> getByStatus(@PathVariable String status) { - List apiCalls = cozeApiCallService.getByStatus(status); - List responses = apiCalls.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 根据请求类型查询API调用记录 - */ - @GetMapping("/request-type/{requestType}") - public Result> getByRequestType(@PathVariable String requestType) { - List apiCalls = cozeApiCallService.getByRequestType(requestType); - List responses = apiCalls.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 统计用户的API调用次数 - */ - @GetMapping("/user/{userId}/count") - public Result countByUserId(@PathVariable String userId) { - Long count = cozeApiCallService.countByUserId(userId); - return Result.success(count); - } - - /** - * 统计Bot的API调用次数 - */ - @GetMapping("/bot/{botId}/count") - public Result countByBotId(@PathVariable String botId) { - Long count = cozeApiCallService.countByBotId(botId); - return Result.success(count); - } - - /** - * 统计指定状态的API调用次数 - */ - @GetMapping("/status/{status}/count") - public Result countByStatus(@PathVariable String status) { - Long count = cozeApiCallService.countByStatus(status); - return Result.success(count); - } - - /** - * 统计用户的Token使用量 - */ - @GetMapping("/user/{userId}/tokens") - public Result sumTokensByUserId(@PathVariable String userId) { - Long totalTokens = cozeApiCallService.sumTokensByUserId(userId); - return Result.success(totalTokens); - } - - /** - * 统计用户的API调用费用 - */ - @GetMapping("/user/{userId}/cost") - public Result sumCostByUserId(@PathVariable String userId) { - java.math.BigDecimal totalCost = cozeApiCallService.sumCostByUserId(userId); - return Result.success(totalCost); - } - - /** - * 查询失败的API调用记录 - */ - @GetMapping("/failed") - public Result> getFailedCalls() { - List apiCalls = cozeApiCallService.getFailedCalls(); - List responses = apiCalls.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 查询超时的API调用记录 - */ - @GetMapping("/timeout") - public Result> getTimeoutCalls() { - List apiCalls = cozeApiCallService.getTimeoutCalls(); - List responses = apiCalls.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - return Result.success(responses); - } - - /** - * 根据追踪ID查询API调用记录 - */ - @GetMapping("/trace/{traceId}") - public Result getByTraceId(@PathVariable String traceId) { - CozeApiCall apiCall = cozeApiCallService.getByTraceId(traceId); - if (apiCall == null) { - return Result.notFound("API调用记录不存在"); - } - return Result.success(convertToResponse(apiCall)); + return Result.success(response); } /** * 创建API调用记录 */ - @PostMapping - public Result create(@RequestBody CozeApiCall apiCall) { - boolean saved = cozeApiCallService.save(apiCall); - if (!saved) { - return Result.error("创建失败"); - } - return Result.success(convertToResponse(apiCall)); + @PostMapping(value = "/create") + public Result createCozeApiCall(@RequestBody @Validated CozeApiCallCreateRequest request) { + CozeApiCallResponse response = cozeApiCallService.create(request); + return Result.success(response); } /** * 更新API调用记录 */ - @PutMapping("/{id}") - public Result update(@PathVariable String id, @RequestBody CozeApiCall apiCall) { - apiCall.setId(id); - boolean updated = cozeApiCallService.updateById(apiCall); - if (!updated) { - return Result.error("更新失败"); + @PutMapping(value = "/update") + public Result updateCozeApiCall(@RequestBody @Validated CozeApiCallUpdateRequest request) { + CozeApiCallResponse response = cozeApiCallService.update(request); + if (response == null) { + return Result.error("更新失败,记录不存在"); } - CozeApiCall updatedApiCall = cozeApiCallService.getById(id); - return Result.success(convertToResponse(updatedApiCall)); + return Result.success(response); } /** * 删除API调用记录 */ - @DeleteMapping("/{id}") - public Result delete(@PathVariable String id) { - boolean deleted = cozeApiCallService.removeById(id); + @DeleteMapping(value = "/delete") + public Result deleteCozeApiCall(@RequestParam String id) { + boolean deleted = cozeApiCallService.delete(id); if (!deleted) { return Result.error("删除失败"); } @@ -259,49 +81,47 @@ public class CozeApiCallController { } /** - * 转换为响应对象 + * 统计用户的API调用次数 */ - private CozeApiCallResponse convertToResponse(CozeApiCall apiCall) { - CozeApiCallResponse response = new CozeApiCallResponse(); - BeanUtils.copyProperties(apiCall, response); - response.setId(apiCall.getId()); - if (apiCall.getCreateTime() != null) { - response.setCreateTime(apiCall.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (apiCall.getUpdateTime() != null) { - response.setUpdateTime(apiCall.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - if (apiCall.getStartTime() != null) { - response.setStartTime(apiCall.getStartTime().format(DATE_TIME_FORMATTER)); - } - if (apiCall.getEndTime() != null) { - response.setEndTime(apiCall.getEndTime().format(DATE_TIME_FORMATTER)); - } - return response; + @GetMapping(value = "/countByUser") + public Result countByUserId(@RequestParam String userId) { + Long count = cozeApiCallService.countByUserId(userId); + return Result.success(count); } /** - * API调用记录响应类 + * 统计Bot的API调用次数 */ - @lombok.Data - @lombok.EqualsAndHashCode(callSuper = true) - public static class CozeApiCallResponse extends BaseResponse { - private String conversationId; - private String messageId; - private String userId; - private String botId; - private String requestType; - private String requestUrl; - private String requestBody; - private Integer responseStatus; - private String responseBody; - private String aiReply; - private Integer totalTokens; - private java.math.BigDecimal cost; - private String status; - private String finalStatus; - private String startTime; - private String endTime; - private String traceId; + @GetMapping(value = "/countByBot") + public Result countByBotId(@RequestParam String botId) { + Long count = cozeApiCallService.countByBotId(botId); + return Result.success(count); } -} + + /** + * 统计指定状态的API调用次数 + */ + @GetMapping(value = "/countByStatus") + public Result countByStatus(@RequestParam String status) { + Long count = cozeApiCallService.countByStatus(status); + return Result.success(count); + } + + /** + * 统计用户的Token使用量 + */ + @GetMapping(value = "/tokensByUser") + public Result sumTokensByUserId(@RequestParam String userId) { + Long totalTokens = cozeApiCallService.sumTokensByUserId(userId); + return Result.success(totalTokens); + } + + /** + * 统计用户的API调用费用 + */ + @GetMapping(value = "/costByUser") + public Result sumCostByUserId(@RequestParam String userId) { + BigDecimal totalCost = cozeApiCallService.sumCostByUserId(userId); + return Result.success(totalCost); + } +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/DiaryCommentController.java b/backend-single/src/main/java/com/emotion/controller/DiaryCommentController.java index c6f783b..123818a 100644 --- a/backend-single/src/main/java/com/emotion/controller/DiaryCommentController.java +++ b/backend-single/src/main/java/com/emotion/controller/DiaryCommentController.java @@ -1,27 +1,19 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.emotion.common.BasePageRequest; import com.emotion.common.PageResult; import com.emotion.common.Result; import com.emotion.dto.request.DiaryCommentCreateRequest; +import com.emotion.dto.request.DiaryCommentPageRequest; import com.emotion.dto.response.DiaryCommentResponse; -import com.emotion.entity.DiaryComment; import com.emotion.service.DiaryCommentService; import com.emotion.service.DiaryPostService; -import com.emotion.service.UserService; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.beans.BeanUtils; +import com.emotion.util.UserContextUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; -import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.stream.Collectors; /** * 日记评论控制器 @@ -30,7 +22,7 @@ import java.util.stream.Collectors; * @date 2025-07-23 */ @RestController -@RequestMapping("/diary-comment") +@RequestMapping("/diaryComment") public class DiaryCommentController { @Autowired @@ -39,152 +31,79 @@ public class DiaryCommentController { @Autowired private DiaryPostService diaryPostService; - @Autowired - private UserService userService; - - @Autowired - private ObjectMapper objectMapper; - - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 分页查询评论 */ - @GetMapping("/page") - public Result> getPage(@Validated BasePageRequest request) { - IPage page = diaryCommentService.getPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据日记ID分页查询评论 - */ - @GetMapping("/diary/{diaryId}/page") - public Result> getPageByDiaryId(@PathVariable String diaryId, - @Validated BasePageRequest request) { - IPage page = diaryCommentService.getPageByDiaryId(diaryId, request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据用户ID分页查询评论 - */ - @GetMapping("/user/{userId}/page") - public Result> getPageByUserId(@PathVariable String userId, - @Validated BasePageRequest request) { - IPage page = diaryCommentService.getPageByUserId(userId, request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - + @GetMapping(value = "/page") + public Result> getPage(@Validated DiaryCommentPageRequest request) { + PageResult pageResult = diaryCommentService.getPageWithResponse(request); return Result.success(pageResult); } /** * 获取评论树结构 */ - @GetMapping("/diary/{diaryId}/tree") - public Result> getCommentTree(@PathVariable String diaryId) { - List comments = diaryCommentService.getCommentTree(diaryId); - List responses = comments.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + @GetMapping(value = "/commentTree") + public Result> getCommentTree(@RequestParam String diaryId) { + List responses = diaryCommentService.getCommentTreeWithResponse(diaryId); return Result.success(responses); } /** * 根据ID获取评论详情 */ - @GetMapping("/{id}") - public Result getById(@PathVariable String id) { - DiaryComment comment = diaryCommentService.getById(id); - if (comment == null) { + @GetMapping(value = "/detail") + public Result getById(@RequestParam String id) { + DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id); + if (response == null) { return Result.notFound("评论不存在"); } - return Result.success(convertToResponse(comment)); + return Result.success(response); } /** * 创建评论 */ - @PostMapping + @PostMapping(value = "/create") public Result create(@Valid @RequestBody DiaryCommentCreateRequest request) { - DiaryComment comment = diaryCommentService.createComment( - request.getDiaryId(), - request.getUserId(), - request.getContent(), - request.getImages(), - request.getParentCommentId(), - request.getIsAnonymous() - ); + // 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID + String currentUserId = UserContextUtils.getCurrentUserId(); + if (currentUserId != null) { + request.setUserId(currentUserId); + } + DiaryCommentResponse response = diaryCommentService.createCommentWithResponse(request); // 更新日记的评论数 diaryPostService.incrementCommentCount(request.getDiaryId()); diaryPostService.updateLastCommentTime(request.getDiaryId()); - return Result.success(convertToResponse(comment)); + return Result.success(response); } /** * 更新评论 */ - @PutMapping("/{id}") - public Result update(@PathVariable String id, @Valid @RequestBody DiaryCommentCreateRequest request) { - boolean updated = diaryCommentService.updateComment(id, request.getContent(), request.getImages()); - if (!updated) { - return Result.error("更新失败"); - } - - DiaryComment updatedComment = diaryCommentService.getById(id); - return Result.success(convertToResponse(updatedComment)); + @PutMapping(value = "/update") + public Result update(@Valid @RequestBody DiaryCommentCreateRequest request) { + DiaryCommentResponse response = diaryCommentService.updateCommentWithResponse(request); + return Result.success(response); } /** * 删除评论 */ - @DeleteMapping("/{id}") - public Result delete(@PathVariable String id) { - DiaryComment comment = diaryCommentService.getById(id); - if (comment == null) { - return Result.error("评论不存在"); - } - + @DeleteMapping(value = "/delete") + public Result delete(@RequestParam String id) { boolean deleted = diaryCommentService.deleteComment(id); if (!deleted) { return Result.error("删除失败"); } // 更新日记的评论数 - diaryPostService.decrementCommentCount(comment.getDiaryId()); + DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id); + if (response != null) { + diaryPostService.decrementCommentCount(response.getDiaryId()); + } return Result.success(); } @@ -192,20 +111,18 @@ public class DiaryCommentController { /** * 软删除评论 */ - @DeleteMapping("/{id}/soft") - public Result softDelete(@PathVariable String id) { - DiaryComment comment = diaryCommentService.getById(id); - if (comment == null) { - return Result.error("评论不存在"); - } - + @DeleteMapping(value = "/softDelete") + public Result softDelete(@RequestParam String id) { boolean deleted = diaryCommentService.softDeleteComment(id); if (!deleted) { return Result.error("删除失败"); } // 更新日记的评论数 - diaryPostService.decrementCommentCount(comment.getDiaryId()); + DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id); + if (response != null) { + diaryPostService.decrementCommentCount(response.getDiaryId()); + } return Result.success(); } @@ -213,20 +130,18 @@ public class DiaryCommentController { /** * 恢复评论 */ - @PutMapping("/{id}/restore") - public Result restore(@PathVariable String id) { - DiaryComment comment = diaryCommentService.getById(id); - if (comment == null) { - return Result.error("评论不存在"); - } - + @PutMapping(value = "/restore") + public Result restore(@RequestParam String id) { boolean restored = diaryCommentService.restoreComment(id); if (!restored) { return Result.error("恢复失败"); } // 更新日记的评论数 - diaryPostService.incrementCommentCount(comment.getDiaryId()); + DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id); + if (response != null) { + diaryPostService.incrementCommentCount(response.getDiaryId()); + } return Result.success(); } @@ -234,8 +149,8 @@ public class DiaryCommentController { /** * 点赞评论 */ - @PostMapping("/{id}/like") - public Result like(@PathVariable String id) { + @PostMapping(value = "/like") + public Result like(@RequestParam String id) { boolean liked = diaryCommentService.incrementLikeCount(id); if (!liked) { return Result.error("点赞失败"); @@ -246,8 +161,8 @@ public class DiaryCommentController { /** * 取消点赞评论 */ - @DeleteMapping("/{id}/like") - public Result unlike(@PathVariable String id) { + @DeleteMapping(value = "/unlike") + public Result unlike(@RequestParam String id) { boolean unliked = diaryCommentService.decrementLikeCount(id); if (!unliked) { return Result.error("取消点赞失败"); @@ -258,75 +173,12 @@ public class DiaryCommentController { /** * 设置置顶状态 */ - @PutMapping("/{id}/top/{isTop}") - public Result setTop(@PathVariable String id, @PathVariable Integer isTop) { + @PutMapping(value = "/setTop") + public Result setTop(@RequestParam String id, @RequestParam Integer isTop) { boolean set = diaryCommentService.setTop(id, isTop); if (!set) { return Result.error("设置置顶状态失败"); } return Result.success(); } - - /** - * 统计日记评论数量 - */ - @GetMapping("/diary/{diaryId}/count") - public Result countByDiaryId(@PathVariable String diaryId) { - Long count = diaryCommentService.countByDiaryId(diaryId); - return Result.success(count); - } - - /** - * 统计用户评论数量 - */ - @GetMapping("/user/{userId}/count") - public Result countByUserId(@PathVariable String userId) { - Long count = diaryCommentService.countByUserId(userId); - return Result.success(count); - } - - /** - * 统计回复数量 - */ - @GetMapping("/parent/{parentCommentId}/replies/count") - public Result countReplies(@PathVariable String parentCommentId) { - Long count = diaryCommentService.countReplies(parentCommentId); - return Result.success(count); - } - - /** - * 转换实体为响应DTO - */ - private DiaryCommentResponse convertToResponse(DiaryComment comment) { - DiaryCommentResponse response = new DiaryCommentResponse(); - BeanUtils.copyProperties(comment, response); - - // 转换时间格式 - if (comment.getPublishTime() != null) { - response.setPublishTime(comment.getPublishTime().format(DATE_TIME_FORMATTER)); - } - if (comment.getLastReplyTime() != null) { - response.setLastReplyTime(comment.getLastReplyTime().format(DATE_TIME_FORMATTER)); - } - if (comment.getCreateTime() != null) { - response.setCreateTime(comment.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (comment.getUpdateTime() != null) { - response.setUpdateTime(comment.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - - // 转换JSON字段 - try { - if (comment.getImages() != null) { - response.setImages(objectMapper.readValue(comment.getImages(), new TypeReference>() {})); - } - if (comment.getMetadata() != null) { - response.setMetadata(objectMapper.readValue(comment.getMetadata(), Object.class)); - } - } catch (JsonProcessingException e) { - // 忽略JSON解析错误 - } - - return response; - } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/DiaryPostController.java b/backend-single/src/main/java/com/emotion/controller/DiaryPostController.java index 2256465..452d3c5 100644 --- a/backend-single/src/main/java/com/emotion/controller/DiaryPostController.java +++ b/backend-single/src/main/java/com/emotion/controller/DiaryPostController.java @@ -1,30 +1,18 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; -import com.emotion.common.BasePageRequest; import com.emotion.common.PageResult; import com.emotion.common.Result; import com.emotion.dto.request.DiaryPostCreateRequest; +import com.emotion.dto.request.DiaryPostPageRequest; import com.emotion.dto.request.DiaryPostUpdateRequest; import com.emotion.dto.response.DiaryPostResponse; -import com.emotion.entity.DiaryPost; import com.emotion.service.DiaryPostService; -import com.emotion.service.DiaryCommentService; -import com.emotion.service.UserService; -import com.emotion.service.AiChatService; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.beans.BeanUtils; +import com.emotion.util.UserContextUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.stream.Collectors; -import com.emotion.entity.DiaryComment; /** * 用户日记控制器 @@ -33,161 +21,75 @@ import com.emotion.entity.DiaryComment; * @date 2025-07-23 */ @RestController -@RequestMapping("/diary-post") +@RequestMapping("/diaryPost") public class DiaryPostController { @Autowired private DiaryPostService diaryPostService; - @Autowired - private DiaryCommentService diaryCommentService; - - @Autowired - private UserService userService; - - @Autowired - private AiChatService aiChatService; - - @Autowired - private ObjectMapper objectMapper; - - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 分页查询日记 */ - @GetMapping("/page") - public Result> getPage(@Validated BasePageRequest request) { - IPage page = diaryPostService.getPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据用户ID分页查询日记 - */ - @GetMapping("/user/{userId}/page") - public Result> getPageByUserId(@PathVariable String userId, - @Validated BasePageRequest request) { - IPage page = diaryPostService.getPageByUserId(userId, request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 根据用户ID查询公开日记 - */ - @GetMapping("/user/{userId}/public/page") - public Result> getPublicPageByUserId(@PathVariable String userId, - @Validated BasePageRequest request) { - IPage page = diaryPostService.getPublicPageByUserId(userId, request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); - } - - /** - * 查询精选日记 - */ - @GetMapping("/featured/page") - public Result> getFeaturedPage(@Validated BasePageRequest request) { - IPage page = diaryPostService.getFeaturedPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); + @GetMapping(value = "/page") + public Result> getPage(@Validated DiaryPostPageRequest request) { + return Result.success(diaryPostService.getPageWithResponse(request)); } /** * 根据ID获取日记详情 */ - @GetMapping("/{id}") - public Result getById(@PathVariable String id) { - DiaryPost diaryPost = diaryPostService.getById(id); - if (diaryPost == null) { + @GetMapping(value = "/detail") + public Result getById(@RequestParam String id) { + DiaryPostResponse response = diaryPostService.getDiaryPostResponseById(id); + if (response == null) { return Result.notFound("日记不存在"); } - - // 增加浏览数 - diaryPostService.incrementViewCount(id); - - return Result.success(convertToResponse(diaryPost)); + return Result.success(response); } /** * 创建日记 */ - @PostMapping + @PostMapping(value = "/create") public Result create(@Valid @RequestBody DiaryPostCreateRequest request) { - DiaryPost diaryPost = diaryPostService.createDiaryPost(request); - return Result.success(convertToResponse(diaryPost)); + // 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID + String currentUserId = UserContextUtils.getCurrentUserId(); + if (currentUserId != null) { + request.setUserId(currentUserId); + } + return Result.success(diaryPostService.createDiaryPostWithResponse(request)); } /** * 发表日记并生成AI评论 */ - @PostMapping("/publish") + @PostMapping(value = "/publish") public Result publish(@Valid @RequestBody DiaryPostCreateRequest request) { + // 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID + String currentUserId = UserContextUtils.getCurrentUserId(); + if (currentUserId != null) { + request.setUserId(currentUserId); + } return Result.success(diaryPostService.publishDiaryWithAiComment(request)); } /** * 更新日记 */ - @PutMapping("/{id}") - public Result update(@PathVariable String id, @Valid @RequestBody DiaryPostUpdateRequest request) { - boolean updated = diaryPostService.updateDiaryPost(id, request); - - if (!updated) { + @PutMapping(value = "/update") + public Result update(@Valid @RequestBody DiaryPostUpdateRequest request) { + DiaryPostResponse response = diaryPostService.updateDiaryPostWithResponse(request); + if (response == null) { return Result.error("更新失败"); } - - DiaryPost updatedDiaryPost = diaryPostService.getById(id); - return Result.success(convertToResponse(updatedDiaryPost)); + return Result.success(response); } /** * 删除日记 */ - @DeleteMapping("/{id}") - public Result delete(@PathVariable String id) { + @DeleteMapping(value = "/delete") + public Result delete(@RequestParam String id) { boolean deleted = diaryPostService.deleteDiaryPost(id); if (!deleted) { return Result.error("删除失败"); @@ -198,8 +100,8 @@ public class DiaryPostController { /** * 软删除日记 */ - @DeleteMapping("/{id}/soft") - public Result softDelete(@PathVariable String id) { + @DeleteMapping(value = "/softDelete") + public Result softDelete(@RequestParam String id) { boolean deleted = diaryPostService.softDeleteDiaryPost(id); if (!deleted) { return Result.error("删除失败"); @@ -210,8 +112,8 @@ public class DiaryPostController { /** * 恢复日记 */ - @PutMapping("/{id}/restore") - public Result restore(@PathVariable String id) { + @PutMapping(value = "/restore") + public Result restore(@RequestParam String id) { boolean restored = diaryPostService.restoreDiaryPost(id); if (!restored) { return Result.error("恢复失败"); @@ -222,8 +124,8 @@ public class DiaryPostController { /** * 点赞日记 */ - @PostMapping("/{id}/like") - public Result like(@PathVariable String id) { + @PostMapping(value = "/like") + public Result like(@RequestParam String id) { boolean liked = diaryPostService.incrementLikeCount(id); if (!liked) { return Result.error("点赞失败"); @@ -234,8 +136,8 @@ public class DiaryPostController { /** * 取消点赞日记 */ - @DeleteMapping("/{id}/like") - public Result unlike(@PathVariable String id) { + @DeleteMapping(value = "/unlike") + public Result unlike(@RequestParam String id) { boolean unliked = diaryPostService.decrementLikeCount(id); if (!unliked) { return Result.error("取消点赞失败"); @@ -246,8 +148,8 @@ public class DiaryPostController { /** * 分享日记 */ - @PostMapping("/{id}/share") - public Result share(@PathVariable String id) { + @PostMapping(value = "/share") + public Result share(@RequestParam String id) { boolean shared = diaryPostService.incrementShareCount(id); if (!shared) { return Result.error("分享失败"); @@ -258,8 +160,8 @@ public class DiaryPostController { /** * 设置精选状态 */ - @PutMapping("/{id}/featured/{featured}") - public Result setFeatured(@PathVariable String id, @PathVariable Integer featured) { + @PutMapping(value = "/setFeatured") + public Result setFeatured(@RequestParam String id, @RequestParam Integer featured) { boolean set = diaryPostService.setFeatured(id, featured); if (!set) { return Result.error("设置精选状态失败"); @@ -270,90 +172,12 @@ public class DiaryPostController { /** * 设置置顶优先级 */ - @PutMapping("/{id}/priority/{priority}") - public Result setPriority(@PathVariable String id, @PathVariable Integer priority) { + @PutMapping(value = "/setPriority") + public Result setPriority(@RequestParam String id, @RequestParam Integer priority) { boolean set = diaryPostService.setPriority(id, priority); if (!set) { return Result.error("设置优先级失败"); } return Result.success(); } - - /** - * 统计用户日记数量 - */ - @GetMapping("/user/{userId}/count") - public Result countByUserId(@PathVariable String userId) { - Long count = diaryPostService.countByUserId(userId); - return Result.success(count); - } - - /** - * 统计用户公开日记数量 - */ - @GetMapping("/user/{userId}/public/count") - public Result countPublicByUserId(@PathVariable String userId) { - Long count = diaryPostService.countPublicByUserId(userId); - return Result.success(count); - } - - /** - * 统计精选日记数量 - */ - @GetMapping("/featured/count") - public Result countFeatured() { - Long count = diaryPostService.countFeatured(); - return Result.success(count); - } - - /** - * 转换实体为响应DTO - */ - private DiaryPostResponse convertToResponse(DiaryPost diaryPost) { - DiaryPostResponse response = new DiaryPostResponse(); - BeanUtils.copyProperties(diaryPost, response); - - // 转换时间格式 - if (diaryPost.getPublishTime() != null) { - response.setPublishTime(diaryPost.getPublishTime().format(DATE_TIME_FORMATTER)); - } - if (diaryPost.getLastCommentTime() != null) { - response.setLastCommentTime(diaryPost.getLastCommentTime().format(DATE_TIME_FORMATTER)); - } - if (diaryPost.getAiCommentTime() != null) { - response.setAiCommentTime(diaryPost.getAiCommentTime().format(DATE_TIME_FORMATTER)); - } - if (diaryPost.getCreateTime() != null) { - response.setCreateTime(diaryPost.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (diaryPost.getUpdateTime() != null) { - response.setUpdateTime(diaryPost.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - - // 转换JSON字段 - try { - if (diaryPost.getImages() != null) { - response.setImages(objectMapper.readValue(diaryPost.getImages(), new TypeReference>() {})); - } - if (diaryPost.getVideos() != null) { - response.setVideos(objectMapper.readValue(diaryPost.getVideos(), new TypeReference>() {})); - } - if (diaryPost.getTags() != null) { - response.setTags(objectMapper.readValue(diaryPost.getTags(), new TypeReference>() {})); - } - if (diaryPost.getAiKeywords() != null) { - response.setAiKeywords(objectMapper.readValue(diaryPost.getAiKeywords(), new TypeReference>() {})); - } - if (diaryPost.getAiEmotionAnalysis() != null) { - response.setAiEmotionAnalysis(objectMapper.readValue(diaryPost.getAiEmotionAnalysis(), Object.class)); - } - if (diaryPost.getMetadata() != null) { - response.setMetadata(objectMapper.readValue(diaryPost.getMetadata(), Object.class)); - } - } catch (JsonProcessingException e) { - // 忽略JSON解析错误 - } - - return response; - } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/EmotionAnalysisController.java b/backend-single/src/main/java/com/emotion/controller/EmotionAnalysisController.java index a0be461..bd08d4d 100644 --- a/backend-single/src/main/java/com/emotion/controller/EmotionAnalysisController.java +++ b/backend-single/src/main/java/com/emotion/controller/EmotionAnalysisController.java @@ -1,24 +1,22 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.emotion.common.PageResult; import com.emotion.common.Result; -import com.emotion.dto.request.PageRequest; -import com.emotion.dto.response.BaseResponse; -import com.emotion.entity.EmotionAnalysis; +import com.emotion.dto.request.EmotionAnalysisCreateRequest; +import com.emotion.dto.request.EmotionAnalysisPageRequest; +import com.emotion.dto.request.EmotionAnalysisUpdateRequest; +import com.emotion.dto.response.EmotionAnalysisResponse; import com.emotion.service.EmotionAnalysisService; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.stream.Collectors; /** * 情绪分析控制器 @@ -33,108 +31,74 @@ public class EmotionAnalysisController { @Autowired private EmotionAnalysisService emotionAnalysisService; - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - /** * 分页查询情绪分析记录 */ @GetMapping("/page") - public Result> getPage(@Validated PageRequest request) { - IPage page = emotionAnalysisService.getPage(request); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); + public Result> getPage(@Validated EmotionAnalysisPageRequest request) { + return Result.success(emotionAnalysisService.getPageWithResponse(request)); } /** * 根据用户ID分页查询情绪分析记录 */ - @GetMapping("/user/{userId}/page") - public Result> getPageByUserId(@PathVariable String userId, @Validated PageRequest request) { - IPage page = emotionAnalysisService.getPageByUserId(request, userId); - List responses = page.getRecords().stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); - - PageResult pageResult = new PageResult<>(); - pageResult.setCurrent(page.getCurrent()); - pageResult.setSize(page.getSize()); - pageResult.setTotal(page.getTotal()); - pageResult.setPages(page.getPages()); - pageResult.setRecords(responses); - - return Result.success(pageResult); + @GetMapping("/user/page") + public Result> getPageByUserId( + @RequestParam String userId, + @Validated EmotionAnalysisPageRequest request) { + return Result.success(emotionAnalysisService.getPageByUserIdWithResponse(userId, request)); } /** * 根据ID获取情绪分析记录 */ - @GetMapping("/{id}") - public Result getById(@PathVariable String id) { - EmotionAnalysis analysis = emotionAnalysisService.getById(id); - if (analysis == null) { + @GetMapping + public Result getById(@RequestParam String id) { + EmotionAnalysisResponse response = emotionAnalysisService.getEmotionAnalysisResponseById(id); + if (response == null) { return Result.notFound("情绪分析记录不存在"); } - return Result.success(convertToResponse(analysis)); + return Result.success(response); } /** * 根据消息ID获取情绪分析记录 */ - @GetMapping("/message/{messageId}") - public Result getByMessageId(@PathVariable String messageId) { - EmotionAnalysis analysis = emotionAnalysisService.getByMessageId(messageId); - if (analysis == null) { + @GetMapping("/message") + public Result getByMessageId(@RequestParam String messageId) { + EmotionAnalysisResponse response = emotionAnalysisService.getEmotionAnalysisResponseByMessageId(messageId); + if (response == null) { return Result.notFound("情绪分析记录不存在"); } - return Result.success(convertToResponse(analysis)); + return Result.success(response); } /** * 创建情绪分析记录 */ @PostMapping - public Result create(@RequestBody @Validated EmotionAnalysisCreateRequest request) { - EmotionAnalysis analysis = emotionAnalysisService.createEmotionAnalysis( - request.getMessageId(), - request.getUserId(), - request.getPrimaryEmotion(), - request.getPolarity(), - request.getIntensity(), - request.getConfidence() - ); - return Result.success(convertToResponse(analysis)); + public Result create(@RequestBody @Valid EmotionAnalysisCreateRequest request) { + return Result.success(emotionAnalysisService.createEmotionAnalysisWithResponse(request)); } /** * 更新情绪分析记录 */ - @PutMapping("/{id}") - public Result update(@PathVariable String id, @RequestBody EmotionAnalysis analysis) { - analysis.setId(id); - boolean updated = emotionAnalysisService.updateById(analysis); - if (!updated) { + @PutMapping + public Result update(@RequestBody @Valid EmotionAnalysisUpdateRequest request) { + EmotionAnalysisResponse response = emotionAnalysisService.updateEmotionAnalysisWithResponse(request); + if (response == null) { return Result.error("更新失败"); } - EmotionAnalysis updatedAnalysis = emotionAnalysisService.getById(id); - return Result.success(convertToResponse(updatedAnalysis)); + return Result.success(response); } /** * 删除情绪分析记录 */ - @DeleteMapping("/{id}") - public Result delete(@PathVariable String id) { - boolean deleted = emotionAnalysisService.removeById(id); + @DeleteMapping + public Result delete(@RequestParam String id) { + boolean deleted = emotionAnalysisService.deleteEmotionAnalysis(id); if (!deleted) { return Result.error("删除失败"); } @@ -144,59 +108,53 @@ public class EmotionAnalysisController { /** * 根据主要情绪查询分析记录 */ - @GetMapping("/emotion/{primaryEmotion}") - public Result> getByPrimaryEmotion(@PathVariable String primaryEmotion) { - List analyses = emotionAnalysisService.getByPrimaryEmotion(primaryEmotion); - List responses = analyses.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + @GetMapping("/emotion") + public Result> getByPrimaryEmotion(@RequestParam String primaryEmotion) { + List responses = emotionAnalysisService + .getEmotionAnalysisResponsesByPrimaryEmotion(primaryEmotion); return Result.success(responses); } /** * 根据情绪极性查询分析记录 */ - @GetMapping("/polarity/{polarity}") - public Result> getByPolarity(@PathVariable String polarity) { - List analyses = emotionAnalysisService.getByPolarity(polarity); - List responses = analyses.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + @GetMapping("/polarity") + public Result> getByPolarity(@RequestParam String polarity) { + List responses = emotionAnalysisService + .getEmotionAnalysisResponsesByPolarity(polarity); return Result.success(responses); } /** * 根据用户ID和情绪类型查询分析记录 */ - @GetMapping("/user/{userId}/emotion/{primaryEmotion}") - public Result> getByUserIdAndEmotion(@PathVariable String userId, @PathVariable String primaryEmotion) { - List analyses = emotionAnalysisService.getByUserIdAndEmotion(userId, primaryEmotion); - List responses = analyses.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + @GetMapping("/user/emotion") + public Result> getByUserIdAndEmotion( + @RequestParam String userId, + @RequestParam String primaryEmotion) { + List responses = emotionAnalysisService + .getEmotionAnalysisResponsesByUserIdAndEmotion(userId, primaryEmotion); return Result.success(responses); } /** * 根据时间范围查询用户情绪分析记录 */ - @GetMapping("/user/{userId}/time-range") + @GetMapping("/user/time-range") public Result> getByUserIdAndTimeRange( - @PathVariable String userId, - @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, + @RequestParam String userId, + @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) { - List analyses = emotionAnalysisService.getByUserIdAndTimeRange(userId, startTime, endTime); - List responses = analyses.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + List responses = emotionAnalysisService + .getEmotionAnalysisResponsesByUserIdAndTimeRange(userId, startTime, endTime); return Result.success(responses); } /** * 统计用户的情绪分析记录数量 */ - @GetMapping("/user/{userId}/count") - public Result countByUserId(@PathVariable String userId) { + @GetMapping("/user/count") + public Result countByUserId(@RequestParam String userId) { Long count = emotionAnalysisService.countByUserId(userId); return Result.success(count); } @@ -204,20 +162,20 @@ public class EmotionAnalysisController { /** * 查询用户最近的情绪分析记录 */ - @GetMapping("/user/{userId}/recent") - public Result> getRecentByUserId(@PathVariable String userId, @RequestParam(defaultValue = "10") Integer limit) { - List analyses = emotionAnalysisService.getRecentByUserId(userId, limit); - List responses = analyses.stream() - .map(this::convertToResponse) - .collect(Collectors.toList()); + @GetMapping("/user/recent") + public Result> getRecentByUserId( + @RequestParam String userId, + @RequestParam(defaultValue = "10") Integer limit) { + List responses = emotionAnalysisService + .getEmotionAnalysisResponsesRecentByUserId(userId, limit); return Result.success(responses); } /** * 查询用户的平均情绪强度 */ - @GetMapping("/user/{userId}/avg-intensity") - public Result getAvgIntensityByUserId(@PathVariable String userId) { + @GetMapping("/user/avg-intensity") + public Result getAvgIntensityByUserId(@RequestParam String userId) { Double avgIntensity = emotionAnalysisService.getAvgIntensityByUserId(userId); return Result.success(avgIntensity); } @@ -225,63 +183,9 @@ public class EmotionAnalysisController { /** * 查询用户最常见的情绪类型 */ - @GetMapping("/user/{userId}/most-frequent-emotion") - public Result getMostFrequentEmotionByUserId(@PathVariable String userId) { + @GetMapping("/user/most-frequent-emotion") + public Result getMostFrequentEmotionByUserId(@RequestParam String userId) { String emotion = emotionAnalysisService.getMostFrequentEmotionByUserId(userId); return Result.success(emotion); } - - /** - * 转换为响应对象 - */ - private EmotionAnalysisResponse convertToResponse(EmotionAnalysis analysis) { - EmotionAnalysisResponse response = new EmotionAnalysisResponse(); - BeanUtils.copyProperties(analysis, response); - response.setId(analysis.getId()); - if (analysis.getCreateTime() != null) { - response.setCreateTime(analysis.getCreateTime().format(DATE_TIME_FORMATTER)); - } - if (analysis.getUpdateTime() != null) { - response.setUpdateTime(analysis.getUpdateTime().format(DATE_TIME_FORMATTER)); - } - return response; - } - - /** - * 情绪分析创建请求 - */ - @lombok.Data - public static class EmotionAnalysisCreateRequest { - @NotBlank(message = "消息ID不能为空") - private String messageId; - - @NotBlank(message = "用户ID不能为空") - private String userId; - - @NotBlank(message = "主要情绪不能为空") - private String primaryEmotion; - - private String polarity; - - @NotNull(message = "情绪强度不能为空") - private Double intensity; - - @NotNull(message = "置信度不能为空") - private Double confidence; - } - - /** - * 情绪分析响应类 - */ - @lombok.Data - @lombok.EqualsAndHashCode(callSuper = true) - public static class EmotionAnalysisResponse extends BaseResponse { - private String messageId; - private String userId; - private String primaryEmotion; - private String polarity; - private Double intensity; - private Double confidence; - private String emotionDetails; - } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/controller/EmotionRecordController.java b/backend-single/src/main/java/com/emotion/controller/EmotionRecordController.java index 55fd466..d4bcbdd 100644 --- a/backend-single/src/main/java/com/emotion/controller/EmotionRecordController.java +++ b/backend-single/src/main/java/com/emotion/controller/EmotionRecordController.java @@ -1,8 +1,11 @@ package com.emotion.controller; -import com.baomidou.mybatisplus.core.metadata.IPage; +import com.emotion.common.PageResult; import com.emotion.common.Result; -import com.emotion.entity.EmotionRecord; +import com.emotion.dto.request.EmotionRecordCreateRequest; +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 io.swagger.v3.oas.annotations.Operation; @@ -11,10 +14,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; /** * 情绪记录控制器 @@ -36,29 +43,16 @@ public class EmotionRecordController { * 创建情绪记录 */ @PostMapping - public Result> createRecord(@RequestBody Map request) { - log.info("创建情绪记录: {}", request); + @Operation(summary = "创建情绪记录", description = "创建新的情绪记录") + public Result createRecord(@RequestBody @Valid EmotionRecordCreateRequest request) { + log.info("创建情绪记录: userId={}", request.getUserId()); - try { - Map record = new HashMap<>(); - record.put("id", "record-" + System.currentTimeMillis()); - record.put("userId", request.get("userId")); - record.put("recordDate", request.get("recordDate")); - record.put("emotionType", request.get("emotionType")); - record.put("intensity", request.get("intensity")); - record.put("triggers", request.get("triggers")); - record.put("description", request.get("description")); - record.put("tags", request.get("tags")); - record.put("weather", request.get("weather")); - record.put("location", request.get("location")); - record.put("activity", request.get("activity")); - record.put("createTime", LocalDateTime.now()); - - return Result.success("创建成功", record); - } catch (Exception e) { - log.error("创建情绪记录失败: {}", e.getMessage()); - return Result.error("创建失败"); - } + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); + request.setUserId(userId); + + EmotionRecordResponse response = emotionRecordService.createEmotionRecordWithResponse(request); + return Result.success("创建成功", response); } /** @@ -66,30 +60,20 @@ public class EmotionRecordController { */ @Operation(summary = "获取用户情绪记录列表", description = "分页获取当前用户的情绪记录,按创建时间倒序") @GetMapping("/user") - public Result> getRecordList( - @Parameter(description = "页码,从1开始") @RequestParam(defaultValue = "1") Integer current, - @Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer size) { + public Result> getRecordList( + @Validated EmotionRecordPageRequest request) { - try { - // 从上下文中获取当前用户ID - String userId = CurrentUserUtil.requireCurrentUserId(); + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); - log.info("获取用户情绪记录列表: userId={}, current={}, size={}", userId, current, size); + log.info("获取用户情绪记录列表: userId={}, current={}, size={}", userId, request.getCurrent(), request.getSize()); - IPage page = emotionRecordService.getByUserIdWithPage(userId, current, size); + PageResult page = emotionRecordService.getPageByUserIdWithResponse(userId, request); - log.info("获取用户情绪记录成功: userId={}, total={}, records={}", - userId, page.getTotal(), page.getRecords().size()); + log.info("获取用户情绪记录成功: userId={}, total={}, records={}", + userId, page.getTotal(), page.getRecords().size()); - return Result.success(page); - - } catch (IllegalStateException e) { - log.warn("用户认证失败: {}", e.getMessage()); - return Result.error(e.getMessage()); - } catch (Exception e) { - log.error("获取用户情绪记录失败", e); - return Result.error("获取情绪记录失败: " + e.getMessage()); - } + return Result.success(page); } /** @@ -97,138 +81,82 @@ public class EmotionRecordController { */ @Operation(summary = "获取用户最近情绪记录", description = "获取当前用户最近的情绪记录列表") @GetMapping("/user/recent") - public Result> getRecentRecords( + public Result> getRecentRecords( @Parameter(description = "限制数量") @RequestParam(defaultValue = "5") Integer limit) { - try { - // 从上下文中获取当前用户ID - String userId = CurrentUserUtil.requireCurrentUserId(); + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); - log.info("获取用户最近情绪记录: userId={}, limit={}", userId, limit); + log.info("获取用户最近情绪记录: userId={}, limit={}", userId, limit); - List records = emotionRecordService.getRecentByUserId(userId, limit); + List records = emotionRecordService.getRecentByUserIdWithResponse(userId, limit); - log.info("获取用户最近情绪记录成功: userId={}, records={}", userId, records.size()); + log.info("获取用户最近情绪记录成功: userId={}, records={}", userId, records.size()); - return Result.success(records); - - } catch (IllegalStateException e) { - log.warn("用户认证失败: {}", e.getMessage()); - return Result.error(e.getMessage()); - } catch (Exception e) { - log.error("获取用户最近情绪记录失败", e); - return Result.error("获取最近记录失败: " + e.getMessage()); - } + return Result.success(records); } /** * 获取情绪记录详情 */ - @GetMapping("/{recordId}") - public Result> getRecord(@PathVariable String recordId) { - log.info("获取情绪记录详情: {}", recordId); + @Operation(summary = "获取情绪记录详情", description = "根据ID获取情绪记录详情") + @GetMapping + public Result getRecord(@RequestParam String id) { + log.info("获取情绪记录详情: {}", id); - try { - Map record = new HashMap<>(); - record.put("id", recordId); - record.put("userId", "user123"); - record.put("recordDate", LocalDate.now()); - record.put("emotionType", "joy"); - record.put("intensity", 0.8); - record.put("triggers", "完成了重要项目"); - record.put("description", "今天心情很好,完成了一个重要的项目"); - record.put("tags", Arrays.asList("工作", "成就感")); - record.put("weather", "晴天"); - record.put("location", "办公室"); - record.put("activity", "工作"); - record.put("createTime", LocalDateTime.now()); - - return Result.success(record); - } catch (Exception e) { - log.error("获取情绪记录详情失败: {}", e.getMessage()); - return Result.error("获取详情失败"); + EmotionRecordResponse response = emotionRecordService.getEmotionRecordResponseById(id); + if (response == null) { + return Result.notFound("情绪记录不存在"); } + return Result.success(response); } /** * 更新情绪记录 */ - @PutMapping("/{recordId}") - public Result> updateRecord(@PathVariable String recordId, - @RequestBody Map request) { - log.info("更新情绪记录: {}", recordId); + @Operation(summary = "更新情绪记录", description = "更新指定的情绪记录") + @PutMapping + public Result updateRecord(@RequestBody @Valid EmotionRecordUpdateRequest request) { + log.info("更新情绪记录: {}", request.getId()); - try { - Map record = new HashMap<>(); - record.put("id", recordId); - record.put("emotionType", request.get("emotionType")); - record.put("intensity", request.get("intensity")); - record.put("triggers", request.get("triggers")); - record.put("description", request.get("description")); - record.put("tags", request.get("tags")); - record.put("updateTime", LocalDateTime.now()); - - return Result.success("更新成功", record); - } catch (Exception e) { - log.error("更新情绪记录失败: {}", e.getMessage()); - return Result.error("更新失败"); + EmotionRecordResponse response = emotionRecordService.updateEmotionRecordWithResponse(request); + if (response == null) { + return Result.notFound("情绪记录不存在"); } + return Result.success("更新成功", response); } /** * 删除情绪记录 */ - @DeleteMapping("/{recordId}") - public Result deleteRecord(@PathVariable String recordId) { - log.info("删除情绪记录: {}", recordId); + @Operation(summary = "删除情绪记录", description = "删除指定的情绪记录") + @DeleteMapping + public Result deleteRecord(@RequestParam String id) { + log.info("删除情绪记录: {}", id); - try { - return Result.success("删除成功"); - } catch (Exception e) { - log.error("删除情绪记录失败: {}", e.getMessage()); - return Result.error("删除失败"); + boolean deleted = emotionRecordService.deleteEmotionRecord(id); + if (!deleted) { + return Result.notFound("情绪记录不存在"); } + return Result.success("删除成功"); } /** * 获取情绪统计 */ - @GetMapping("/stats/{userId}") - public Result> getEmotionStats(@PathVariable String userId, - @RequestParam(required = false) String startDate, - @RequestParam(required = false) String endDate) { + @Operation(summary = "获取情绪统计", description = "获取当前用户的情绪统计信息") + @GetMapping("/stats") + public Result> getEmotionStats( + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate) { + + // 从上下文中获取当前用户ID + String userId = CurrentUserUtil.requireCurrentUserId(); + log.info("获取情绪统计: userId={}, startDate={}, endDate={}", userId, startDate, endDate); - try { - Map stats = new HashMap<>(); - - // 情绪类型分布 - Map emotionDistribution = new HashMap<>(); - emotionDistribution.put("joy", 15); - emotionDistribution.put("sadness", 5); - emotionDistribution.put("anger", 3); - emotionDistribution.put("fear", 2); - emotionDistribution.put("surprise", 8); - emotionDistribution.put("neutral", 12); - - stats.put("emotionDistribution", emotionDistribution); - stats.put("totalRecords", 45); - stats.put("averageIntensity", 0.72); - stats.put("mostFrequentEmotion", "joy"); - stats.put("emotionTrend", "improving"); - - return Result.success(stats); - } catch (Exception e) { - log.error("获取情绪统计失败: {}", e.getMessage()); - return Result.error("获取统计失败"); - } + Map stats = emotionRecordService.getEmotionStats(userId, startDate, endDate); + + return Result.success(stats); } - - /** - * 获取随机情绪类型 - */ - private String getRandomEmotion() { - String[] emotions = {"joy", "sadness", "anger", "fear", "surprise", "neutral"}; - return emotions[new Random().nextInt(emotions.length)]; - } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/ChatStatsRequest.java b/backend-single/src/main/java/com/emotion/dto/request/ChatStatsRequest.java index 0e65f8d..0498e64 100644 --- a/backend-single/src/main/java/com/emotion/dto/request/ChatStatsRequest.java +++ b/backend-single/src/main/java/com/emotion/dto/request/ChatStatsRequest.java @@ -22,4 +22,4 @@ public class ChatStatsRequest extends BaseRequest { * 会话ID */ private String conversationId; -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/ConversationCreateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/ConversationCreateRequest.java index 79ee2fb..8b9ac42 100644 --- a/backend-single/src/main/java/com/emotion/dto/request/ConversationCreateRequest.java +++ b/backend-single/src/main/java/com/emotion/dto/request/ConversationCreateRequest.java @@ -15,6 +15,11 @@ import javax.validation.constraints.NotBlank; @EqualsAndHashCode(callSuper = true) public class ConversationCreateRequest extends BaseRequest { + /** + * 对话ID(更新时使用) + */ + private String id; + /** * 用户ID */ diff --git a/backend-single/src/main/java/com/emotion/dto/request/ConversationPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/ConversationPageRequest.java new file mode 100644 index 0000000..8bdcdea --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/ConversationPageRequest.java @@ -0,0 +1,30 @@ +package com.emotion.dto.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 对话分页请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ConversationPageRequest extends PageRequest { + + /** + * 用户ID(可选) + */ + private String userId; + + /** + * 对话状态(可选) + */ + private String status; + + /** + * 对话类型(可选) + */ + private String type; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/DiaryCommentCreateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/DiaryCommentCreateRequest.java index 1204ea8..7de6d43 100644 --- a/backend-single/src/main/java/com/emotion/dto/request/DiaryCommentCreateRequest.java +++ b/backend-single/src/main/java/com/emotion/dto/request/DiaryCommentCreateRequest.java @@ -4,7 +4,6 @@ import lombok.Data; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; import java.util.List; /** @@ -16,6 +15,11 @@ import java.util.List; @Data public class DiaryCommentCreateRequest { + /** + * 评论ID (用于更新操作) + */ + private String id; + /** * 日记ID */ @@ -49,4 +53,4 @@ public class DiaryCommentCreateRequest { */ @NotNull(message = "是否匿名不能为空") private Integer isAnonymous; -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/DiaryCommentPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/DiaryCommentPageRequest.java new file mode 100644 index 0000000..f725018 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/DiaryCommentPageRequest.java @@ -0,0 +1,38 @@ +package com.emotion.dto.request; + +import com.emotion.common.BasePageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.Pattern; + +/** + * 日记评论分页请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DiaryCommentPageRequest extends BasePageRequest { + + /** + * 日记ID(可选) + */ + private String diaryId; + + /** + * 用户ID(可选) + */ + private String userId; + + /** + * 评论类型(可选) + */ + private String commentType; + + /** + * 是否只查询顶级评论(可选) + */ + private Boolean topLevelOnly; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/DiaryPostPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/DiaryPostPageRequest.java new file mode 100644 index 0000000..88aa263 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/DiaryPostPageRequest.java @@ -0,0 +1,41 @@ +package com.emotion.dto.request; + +import com.emotion.common.BasePageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 日记分页请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DiaryPostPageRequest extends BasePageRequest { + + /** + * 用户ID(可选) + */ + private String userId; + + /** + * 是否只查询公开日记(可选) + */ + private Boolean publicOnly; + + /** + * 是否只查询精选日记(可选) + */ + private Boolean featuredOnly; + + /** + * 心情状态(可选) + */ + private String mood; + + /** + * 标签(可选) + */ + private String tag; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/DiaryPostUpdateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/DiaryPostUpdateRequest.java index 764f652..f0f4477 100644 --- a/backend-single/src/main/java/com/emotion/dto/request/DiaryPostUpdateRequest.java +++ b/backend-single/src/main/java/com/emotion/dto/request/DiaryPostUpdateRequest.java @@ -2,6 +2,7 @@ package com.emotion.dto.request; import lombok.Data; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import java.math.BigDecimal; import java.util.List; @@ -15,6 +16,12 @@ import java.util.List; @Data public class DiaryPostUpdateRequest { + /** + * 日记ID + */ + @NotBlank(message = "日记ID不能为空") + private String id; + /** * 日记标题 */ @@ -88,4 +95,4 @@ public class DiaryPostUpdateRequest { * 状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除 */ private String status; -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisCreateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisCreateRequest.java new file mode 100644 index 0000000..c0d4ea6 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisCreateRequest.java @@ -0,0 +1,51 @@ +package com.emotion.dto.request; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 情绪分析创建请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +public class EmotionAnalysisCreateRequest { + + /** + * 消息ID + */ + @NotBlank(message = "消息ID不能为空") + private String messageId; + + /** + * 用户ID + */ + @NotBlank(message = "用户ID不能为空") + private String userId; + + /** + * 主要情绪 + */ + @NotBlank(message = "主要情绪不能为空") + private String primaryEmotion; + + /** + * 情绪极性 + */ + private String polarity; + + /** + * 情绪强度 + */ + @NotNull(message = "情绪强度不能为空") + private Double intensity; + + /** + * 置信度 + */ + @NotNull(message = "置信度不能为空") + private Double confidence; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisPageRequest.java new file mode 100644 index 0000000..7e3cfd5 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisPageRequest.java @@ -0,0 +1,36 @@ +package com.emotion.dto.request; + +import com.emotion.common.BasePageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 情绪分析分页请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class EmotionAnalysisPageRequest extends BasePageRequest { + + /** + * 用户ID(可选) + */ + private String userId; + + /** + * 消息ID(可选) + */ + private String messageId; + + /** + * 主要情绪(可选) + */ + private String primaryEmotion; + + /** + * 情绪极性(可选) + */ + private String polarity; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisUpdateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisUpdateRequest.java new file mode 100644 index 0000000..cff03b9 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/EmotionAnalysisUpdateRequest.java @@ -0,0 +1,51 @@ +package com.emotion.dto.request; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 情绪分析更新请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +public class EmotionAnalysisUpdateRequest { + + /** + * 情绪分析ID + */ + @NotBlank(message = "情绪分析ID不能为空") + private String id; + + /** + * 消息ID + */ + private String messageId; + + /** + * 用户ID + */ + private String userId; + + /** + * 主要情绪 + */ + private String primaryEmotion; + + /** + * 情绪极性 + */ + private String polarity; + + /** + * 情绪强度 + */ + private Double intensity; + + /** + * 置信度 + */ + private Double confidence; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordCreateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordCreateRequest.java new file mode 100644 index 0000000..68c1264 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordCreateRequest.java @@ -0,0 +1,83 @@ +package com.emotion.dto.request; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +/** + * 情绪记录创建请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +public class EmotionRecordCreateRequest { + + /** + * 用户ID + */ + @NotBlank(message = "用户ID不能为空") + private String userId; + + /** + * 记录日期 + */ + @NotNull(message = "记录日期不能为空") + private LocalDate recordDate; + + /** + * 情绪类型 + */ + @NotBlank(message = "情绪类型不能为空") + private String emotionType; + + /** + * 情绪强度 + */ + @NotNull(message = "情绪强度不能为空") + private BigDecimal intensity; + + /** + * 触发因素 + */ + private String triggers; + + /** + * 描述 + */ + private String description; + + /** + * 标签 + */ + private List tags; + + /** + * 天气 + */ + private String weather; + + /** + * 地点 + */ + private String location; + + /** + * 活动 + */ + private String activity; + + /** + * 相关人物 + */ + private String people; + + /** + * 备注 + */ + private String notes; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordPageRequest.java new file mode 100644 index 0000000..eaa622e --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordPageRequest.java @@ -0,0 +1,31 @@ +package com.emotion.dto.request; + +import com.emotion.common.BasePageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 情绪记录分页请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class EmotionRecordPageRequest extends BasePageRequest { + + /** + * 用户ID(可选) + */ + private String userId; + + /** + * 情绪类型(可选) + */ + private String emotionType; + + /** + * 地点(可选) + */ + private String location; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordUpdateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordUpdateRequest.java new file mode 100644 index 0000000..97c632e --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/EmotionRecordUpdateRequest.java @@ -0,0 +1,79 @@ +package com.emotion.dto.request; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +/** + * 情绪记录更新请求类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +public class EmotionRecordUpdateRequest { + + /** + * 情绪记录ID + */ + @NotBlank(message = "情绪记录ID不能为空") + private String id; + + /** + * 记录日期 + */ + private LocalDate recordDate; + + /** + * 情绪类型 + */ + private String emotionType; + + /** + * 情绪强度 + */ + private BigDecimal intensity; + + /** + * 触发因素 + */ + private String triggers; + + /** + * 描述 + */ + private String description; + + /** + * 标签 + */ + private List tags; + + /** + * 天气 + */ + private String weather; + + /** + * 地点 + */ + private String location; + + /** + * 活动 + */ + private String activity; + + /** + * 相关人物 + */ + private String people; + + /** + * 备注 + */ + private String notes; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/GuestUserInfoRequest.java b/backend-single/src/main/java/com/emotion/dto/request/GuestUserInfoRequest.java new file mode 100644 index 0000000..0f489e3 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/GuestUserInfoRequest.java @@ -0,0 +1,20 @@ +package com.emotion.dto.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 访客用户信息请求类 + * + * @author emotion-museum + * @date 2025-07-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GuestUserInfoRequest extends BaseRequest { + + /** + * 客户端IP + */ + private String clientIp; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/WebSocketRequest.java b/backend-single/src/main/java/com/emotion/dto/request/WebSocketRequest.java new file mode 100644 index 0000000..257ba2d --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/WebSocketRequest.java @@ -0,0 +1,40 @@ +package com.emotion.dto.request; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * WebSocket请求对象 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WebSocketRequest extends BaseRequest { + + /** + * 消息内容 + */ + private String content; + + /** + * 发送者ID + */ + private String senderId; + + /** + * 发送者类型 + */ + private String senderType; + + /** + * 消息类型 + */ + private String messageType; + + /** + * 会话ID(可选) + */ + private String conversationId; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementCreateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementCreateRequest.java new file mode 100644 index 0000000..ac2983e --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementCreateRequest.java @@ -0,0 +1,65 @@ +package com.emotion.dto.request.achievement; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * 创建成就请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +public class AchievementCreateRequest { + + /** + * 成就标题 + */ + @NotBlank(message = "成就标题不能为空") + private String title; + + /** + * 描述 + */ + private String description; + + /** + * 分类 + */ + @NotBlank(message = "分类不能为空") + private String category; + + /** + * 图标 + */ + private String icon; + + /** + * 稀有度 + */ + @NotBlank(message = "稀有度不能为空") + private String rarity; + + /** + * 条件类型 + */ + private String conditionType; + + /** + * 条件值 + */ + private String conditionValue; + + /** + * 奖励 + */ + private String rewards; + + /** + * 是否隐藏 + */ + private Integer isHidden; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementPageRequest.java new file mode 100644 index 0000000..7bd6402 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementPageRequest.java @@ -0,0 +1,40 @@ +package com.emotion.dto.request.achievement; + +import com.emotion.common.BasePageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * 成就分页查询请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AchievementPageRequest extends BasePageRequest { + + /** + * 成就分类 + */ + private String category; + + /** + * 成就稀有度 + */ + private String rarity; + + /** + * 是否已解锁 + */ + private Boolean unlocked; + + /** + * 是否隐藏 + */ + private Boolean hidden; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementProgressUpdateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementProgressUpdateRequest.java new file mode 100644 index 0000000..74db457 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementProgressUpdateRequest.java @@ -0,0 +1,28 @@ +package com.emotion.dto.request.achievement; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 更新成就进度请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +public class AchievementProgressUpdateRequest { + + /** + * 成就ID + */ + @NotBlank(message = "成就ID不能为空") + private String id; + + /** + * 进度值 + */ + @NotNull(message = "进度值不能为空") + private Double progress; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementUnlockRequest.java b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementUnlockRequest.java new file mode 100644 index 0000000..2cae3e2 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementUnlockRequest.java @@ -0,0 +1,21 @@ +package com.emotion.dto.request.achievement; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 解锁成就请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +public class AchievementUnlockRequest { + + /** + * 成就ID + */ + @NotBlank(message = "成就ID不能为空") + private String id; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementUpdateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementUpdateRequest.java new file mode 100644 index 0000000..ba2c421 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/achievement/AchievementUpdateRequest.java @@ -0,0 +1,68 @@ +package com.emotion.dto.request.achievement; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * 更新成就请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +public class AchievementUpdateRequest { + + /** + * 成就ID + */ + @NotBlank(message = "成就ID不能为空") + private String id; + + /** + * 成就标题 + */ + private String title; + + /** + * 描述 + */ + private String description; + + /** + * 分类 + */ + private String category; + + /** + * 图标 + */ + private String icon; + + /** + * 稀有度 + */ + private String rarity; + + /** + * 条件类型 + */ + private String conditionType; + + /** + * 条件值 + */ + private String conditionValue; + + /** + * 奖励 + */ + private String rewards; + + /** + * 是否隐藏 + */ + private Integer isHidden; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/comment/CommentCreateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/comment/CommentCreateRequest.java new file mode 100644 index 0000000..1654f9c --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/comment/CommentCreateRequest.java @@ -0,0 +1,40 @@ +package com.emotion.dto.request.comment; + +import com.emotion.dto.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 评论创建请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommentCreateRequest extends BaseRequest { + + /** + * 帖子ID + */ + @NotBlank(message = "帖子ID不能为空") + private String postId; + + /** + * 用户ID + */ + private String userId; // 不再使用@NotBlank注解,因为将从上下文中获取 + + /** + * 评论内容 + */ + @NotBlank(message = "评论内容不能为空") + private String content; + + /** + * 回复的评论ID + */ + private String replyToId; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/comment/CommentPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/comment/CommentPageRequest.java new file mode 100644 index 0000000..e0c7bc3 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/comment/CommentPageRequest.java @@ -0,0 +1,30 @@ +package com.emotion.dto.request.comment; + +import com.emotion.dto.request.PageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.Pattern; + +/** + * 评论分页请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommentPageRequest extends PageRequest { + + /** + * 帖子ID(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,50}$", message = "帖子ID格式不正确") + private String postId; + + /** + * 用户ID(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,50}$", message = "用户ID格式不正确") + private String userId; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/comment/CommentQueryRequest.java b/backend-single/src/main/java/com/emotion/dto/request/comment/CommentQueryRequest.java new file mode 100644 index 0000000..7d44141 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/comment/CommentQueryRequest.java @@ -0,0 +1,41 @@ +package com.emotion.dto.request.comment; + +import com.emotion.dto.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 评论查询请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommentQueryRequest extends BaseRequest { + + /** + * 帖子ID + */ + private String postId; + + /** + * 用户ID + */ + private String userId; + + /** + * 回复的评论ID + */ + private String replyToId; + + /** + * 最小点赞数 + */ + private Integer minLikes; + + /** + * 最大点赞数 + */ + private Integer maxLikes; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/comment/CommentUpdateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/comment/CommentUpdateRequest.java new file mode 100644 index 0000000..016d3d3 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/comment/CommentUpdateRequest.java @@ -0,0 +1,30 @@ +package com.emotion.dto.request.comment; + +import com.emotion.dto.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 评论更新请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommentUpdateRequest extends BaseRequest { + + /** + * 评论ID + */ + @NotBlank(message = "评论ID不能为空") + private String id; + + /** + * 评论内容 + */ + @NotBlank(message = "评论内容不能为空") + private String content; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostCreateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostCreateRequest.java new file mode 100644 index 0000000..c0681f3 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostCreateRequest.java @@ -0,0 +1,56 @@ +package com.emotion.dto.request.community; + +import com.emotion.dto.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 社区帖子创建请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommunityPostCreateRequest extends BaseRequest { + + /** + * 用户ID + */ + @NotBlank(message = "用户ID不能为空") + private String userId; + + /** + * 标题 + */ + @NotBlank(message = "标题不能为空") + private String title; + + /** + * 内容 + */ + @NotBlank(message = "内容不能为空") + private String content; + + /** + * 帖子类型 + */ + private String type; + + /** + * 地点ID + */ + private String locationId; + + /** + * 标签 + */ + private String tags; + + /** + * 是否私密 + */ + private Integer isPrivate; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostPageRequest.java new file mode 100644 index 0000000..63976bd --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostPageRequest.java @@ -0,0 +1,41 @@ +package com.emotion.dto.request.community; + +import com.emotion.dto.request.PageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.Pattern; + +/** + * 社区帖子分页请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommunityPostPageRequest extends PageRequest { + + /** + * 用户ID(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,50}$", message = "用户ID格式不正确") + private String userId; + + /** + * 帖子类型(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\u4e00-\\u9fa5]{1,20}$", message = "帖子类型格式不正确") + private String type; + + /** + * 地点ID(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,50}$", message = "地点ID格式不正确") + private String locationId; + + /** + * 是否只查询公开帖子 + */ + private Boolean publicOnly; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostUpdateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostUpdateRequest.java new file mode 100644 index 0000000..5db1669 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/community/CommunityPostUpdateRequest.java @@ -0,0 +1,56 @@ +package com.emotion.dto.request.community; + +import com.emotion.dto.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 社区帖子更新请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommunityPostUpdateRequest extends BaseRequest { + + /** + * 帖子ID + */ + @NotBlank(message = "帖子ID不能为空") + private String id; + + /** + * 标题 + */ + @NotBlank(message = "标题不能为空") + private String title; + + /** + * 内容 + */ + @NotBlank(message = "内容不能为空") + private String content; + + /** + * 帖子类型 + */ + private String type; + + /** + * 地点ID + */ + private String locationId; + + /** + * 标签 + */ + private String tags; + + /** + * 是否私密 + */ + private Integer isPrivate; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallCreateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallCreateRequest.java new file mode 100644 index 0000000..2946db1 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallCreateRequest.java @@ -0,0 +1,215 @@ +package com.emotion.dto.request.coze; + +import com.emotion.dto.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import java.math.BigDecimal; + +/** + * Coze API调用记录创建请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CozeApiCallCreateRequest extends BaseRequest { + + /** + * 对话ID + */ + @NotBlank(message = "对话ID不能为空") + private String conversationId; + + /** + * 消息ID + */ + private String messageId; + + /** + * Coze聊天ID + */ + private String cozeChatId; + + /** + * Coze对话ID + */ + private String cozeConversationId; + + /** + * Bot ID + */ + private String botId; + + /** + * Workflow ID + */ + private String workflowId; + + /** + * 用户ID + */ + private String userId; + + /** + * 请求类型: chat/stream/retrieve/messages + */ + private String requestType; + + /** + * 请求URL + */ + private String requestUrl; + + /** + * 请求体 + */ + private String requestBody; + + /** + * 请求头 + */ + private String requestHeaders; + + /** + * 用户输入的消息内容 + */ + private String userMessage; + + /** + * 用户消息类型: text/image/file + */ + private String userMessageType; + + /** + * AI回复的消息内容 + */ + private String aiReply; + + /** + * AI回复类型: text/image/file + */ + private String aiReplyType; + + /** + * HTTP状态码 + */ + private Integer responseStatus; + + /** + * 响应体 + */ + private String responseBody; + + /** + * 响应头 + */ + private String responseHeaders; + + /** + * 轮询次数 + */ + private Integer pollCount; + + /** + * 轮询开始时间 + */ + private String pollStartTime; + + /** + * 轮询结束时间 + */ + private String pollEndTime; + + /** + * 最终状态: completed/failed/timeout + */ + private String finalStatus; + + /** + * 调用状态: pending/success/failed/timeout + */ + private String status; + + /** + * 开始时间 + */ + private String startTime; + + /** + * 结束时间 + */ + private String endTime; + + /** + * 耗时(毫秒) + */ + private Integer durationMs; + + /** + * 输入Token数 + */ + private Integer promptTokens; + + /** + * 输出Token数 + */ + private Integer completionTokens; + + /** + * 总Token数 + */ + private Integer totalTokens; + + /** + * 费用 + */ + private BigDecimal cost; + + /** + * 函数调用记录 + */ + private String functionCalls; + + /** + * 函数调用结果 + */ + private String functionResults; + + /** + * 错误代码 + */ + private String errorCode; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 客户端IP + */ + private String clientIp; + + /** + * 用户代理 + */ + private String userAgent; + + /** + * 会话ID + */ + private String sessionId; + + /** + * 追踪ID + */ + private String traceId; + + /** + * 扩展元数据 + */ + private String metadata; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallPageRequest.java b/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallPageRequest.java new file mode 100644 index 0000000..e210844 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallPageRequest.java @@ -0,0 +1,54 @@ +package com.emotion.dto.request.coze; + +import com.emotion.dto.request.PageRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.Pattern; + +/** + * Coze API调用记录分页请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CozeApiCallPageRequest extends PageRequest { + + /** + * 会话ID(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,50}$", message = "会话ID格式不正确") + private String conversationId; + + /** + * 用户ID(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,50}$", message = "用户ID格式不正确") + private String userId; + + /** + * Bot ID(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,50}$", message = "Bot ID格式不正确") + private String botId; + + /** + * 状态(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\u4e00-\\u9fa5]{1,20}$", message = "状态格式不正确") + private String status; + + /** + * 请求类型(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,20}$", message = "请求类型格式不正确") + private String requestType; + + /** + * 追踪ID(可选) + */ + @Pattern(regexp = "^[a-zA-Z0-9_\\-]{1,50}$", message = "追踪ID格式不正确") + private String traceId; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallUpdateRequest.java b/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallUpdateRequest.java new file mode 100644 index 0000000..212ed39 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/request/coze/CozeApiCallUpdateRequest.java @@ -0,0 +1,220 @@ +package com.emotion.dto.request.coze; + +import com.emotion.dto.request.BaseRequest; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; +import java.math.BigDecimal; + +/** + * Coze API调用记录更新请求 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CozeApiCallUpdateRequest extends BaseRequest { + + /** + * ID + */ + @NotBlank(message = "ID不能为空") + private String id; + + /** + * 对话ID + */ + private String conversationId; + + /** + * 消息ID + */ + private String messageId; + + /** + * Coze聊天ID + */ + private String cozeChatId; + + /** + * Coze对话ID + */ + private String cozeConversationId; + + /** + * Bot ID + */ + private String botId; + + /** + * Workflow ID + */ + private String workflowId; + + /** + * 用户ID + */ + private String userId; + + /** + * 请求类型: chat/stream/retrieve/messages + */ + private String requestType; + + /** + * 请求URL + */ + private String requestUrl; + + /** + * 请求体 + */ + private String requestBody; + + /** + * 请求头 + */ + private String requestHeaders; + + /** + * 用户输入的消息内容 + */ + private String userMessage; + + /** + * 用户消息类型: text/image/file + */ + private String userMessageType; + + /** + * AI回复的消息内容 + */ + private String aiReply; + + /** + * AI回复类型: text/image/file + */ + private String aiReplyType; + + /** + * HTTP状态码 + */ + private Integer responseStatus; + + /** + * 响应体 + */ + private String responseBody; + + /** + * 响应头 + */ + private String responseHeaders; + + /** + * 轮询次数 + */ + private Integer pollCount; + + /** + * 轮询开始时间 + */ + private String pollStartTime; + + /** + * 轮询结束时间 + */ + private String pollEndTime; + + /** + * 最终状态: completed/failed/timeout + */ + private String finalStatus; + + /** + * 调用状态: pending/success/failed/timeout + */ + private String status; + + /** + * 开始时间 + */ + private String startTime; + + /** + * 结束时间 + */ + private String endTime; + + /** + * 耗时(毫秒) + */ + private Integer durationMs; + + /** + * 输入Token数 + */ + private Integer promptTokens; + + /** + * 输出Token数 + */ + private Integer completionTokens; + + /** + * 总Token数 + */ + private Integer totalTokens; + + /** + * 费用 + */ + private BigDecimal cost; + + /** + * 函数调用记录 + */ + private String functionCalls; + + /** + * 函数调用结果 + */ + private String functionResults; + + /** + * 错误代码 + */ + private String errorCode; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 客户端IP + */ + private String clientIp; + + /** + * 用户代理 + */ + private String userAgent; + + /** + * 会话ID + */ + private String sessionId; + + /** + * 追踪ID + */ + private String traceId; + + /** + * 扩展元数据 + */ + private String metadata; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/response/EmotionAnalysisResponse.java b/backend-single/src/main/java/com/emotion/dto/response/EmotionAnalysisResponse.java new file mode 100644 index 0000000..764f34e --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/EmotionAnalysisResponse.java @@ -0,0 +1,50 @@ +package com.emotion.dto.response; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 情绪分析响应类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class EmotionAnalysisResponse extends BaseResponse { + + /** + * 消息ID + */ + private String messageId; + + /** + * 用户ID + */ + private String userId; + + /** + * 主要情绪 + */ + private String primaryEmotion; + + /** + * 情绪极性 + */ + private String polarity; + + /** + * 情绪强度 + */ + private Double intensity; + + /** + * 置信度 + */ + private Double confidence; + + /** + * 情绪详情 + */ + private String emotionDetails; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/response/EmotionRecordResponse.java b/backend-single/src/main/java/com/emotion/dto/response/EmotionRecordResponse.java new file mode 100644 index 0000000..19d3108 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/EmotionRecordResponse.java @@ -0,0 +1,80 @@ +package com.emotion.dto.response; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 情绪记录响应类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class EmotionRecordResponse extends BaseResponse { + + /** + * 用户ID + */ + private String userId; + + /** + * 记录日期 + */ + private LocalDate recordDate; + + /** + * 情绪类型 + */ + private String emotionType; + + /** + * 情绪强度 + */ + private BigDecimal intensity; + + /** + * 触发因素 + */ + private String triggers; + + /** + * 描述 + */ + private String description; + + /** + * 标签 + */ + private List tags; + + /** + * 天气 + */ + private String weather; + + /** + * 地点 + */ + private String location; + + /** + * 活动 + */ + private String activity; + + /** + * 相关人物 + */ + private String people; + + /** + * 备注 + */ + private String notes; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/response/WebSocketResponse.java b/backend-single/src/main/java/com/emotion/dto/response/WebSocketResponse.java new file mode 100644 index 0000000..ba408ce --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/WebSocketResponse.java @@ -0,0 +1,62 @@ +package com.emotion.dto.response; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * WebSocket响应对象 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WebSocketResponse extends BaseResponse { + + /** + * 消息ID + */ + private String messageId; + + /** + * 会话ID + */ + private String conversationId; + + /** + * 消息类型 + */ + private String type; + + /** + * 消息内容 + */ + private String content; + + /** + * 发送者ID + */ + private String senderId; + + /** + * 发送者类型 + */ + private String senderType; + + /** + * 消息状态 + */ + private String status; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 扩展数据 + */ + private Object data; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/response/achievement/AchievementResponse.java b/backend-single/src/main/java/com/emotion/dto/response/achievement/AchievementResponse.java new file mode 100644 index 0000000..d8ae9db --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/achievement/AchievementResponse.java @@ -0,0 +1,73 @@ +package com.emotion.dto.response.achievement; + +import com.emotion.dto.response.BaseResponse; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 成就响应类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AchievementResponse extends BaseResponse { + + /** + * 成就标题 + */ + private String title; + + /** + * 描述 + */ + private String description; + + /** + * 分类 + */ + private String category; + + /** + * 图标 + */ + private String icon; + + /** + * 稀有度 + */ + private String rarity; + + /** + * 条件类型 + */ + private String conditionType; + + /** + * 条件值 + */ + private String conditionValue; + + /** + * 奖励 + */ + private String rewards; + + /** + * 解锁时间 + */ + private String unlockedTime; + + /** + * 进度 + */ + private Double progress; + + /** + * 是否隐藏 + */ + private Integer isHidden; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/response/comment/CommentResponse.java b/backend-single/src/main/java/com/emotion/dto/response/comment/CommentResponse.java new file mode 100644 index 0000000..17abb8c --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/comment/CommentResponse.java @@ -0,0 +1,41 @@ +package com.emotion.dto.response.comment; + +import com.emotion.dto.response.BaseResponse; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 评论响应 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommentResponse extends BaseResponse { + + /** + * 帖子ID + */ + private String postId; + + /** + * 用户ID + */ + private String userId; + + /** + * 评论内容 + */ + private String content; + + /** + * 回复的评论ID + */ + private String replyToId; + + /** + * 点赞数 + */ + private Integer likes; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/response/community/CommunityPostResponse.java b/backend-single/src/main/java/com/emotion/dto/response/community/CommunityPostResponse.java new file mode 100644 index 0000000..c4395f7 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/community/CommunityPostResponse.java @@ -0,0 +1,71 @@ +package com.emotion.dto.response.community; + +import com.emotion.dto.response.BaseResponse; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 社区帖子响应 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CommunityPostResponse extends BaseResponse { + + /** + * 用户ID + */ + private String userId; + + /** + * 地点ID + */ + private String locationId; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 帖子类型 + */ + private String type; + + /** + * 图片列表 + */ + private String images; + + /** + * 标签 + */ + private String tags; + + /** + * 点赞数 + */ + private Integer likes; + + /** + * 浏览数 + */ + private Integer viewCount; + + /** + * 评论数 + */ + private Integer commentCount; + + /** + * 是否私密 + */ + private Integer isPrivate; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/response/coze/CozeApiCallResponse.java b/backend-single/src/main/java/com/emotion/dto/response/coze/CozeApiCallResponse.java new file mode 100644 index 0000000..d28a18d --- /dev/null +++ b/backend-single/src/main/java/com/emotion/dto/response/coze/CozeApiCallResponse.java @@ -0,0 +1,212 @@ +package com.emotion.dto.response.coze; + +import com.emotion.dto.response.BaseResponse; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.math.BigDecimal; + +/** + * Coze API调用记录响应 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CozeApiCallResponse extends BaseResponse { + + /** + * 对话ID + */ + private String conversationId; + + /** + * 消息ID + */ + private String messageId; + + /** + * Coze聊天ID + */ + private String cozeChatId; + + /** + * Coze对话ID + */ + private String cozeConversationId; + + /** + * Bot ID + */ + private String botId; + + /** + * Workflow ID + */ + private String workflowId; + + /** + * 用户ID + */ + private String userId; + + /** + * 请求类型: chat/stream/retrieve/messages + */ + private String requestType; + + /** + * 请求URL + */ + private String requestUrl; + + /** + * 请求体 + */ + private String requestBody; + + /** + * 请求头 + */ + private String requestHeaders; + + /** + * 用户输入的消息内容 + */ + private String userMessage; + + /** + * 用户消息类型: text/image/file + */ + private String userMessageType; + + /** + * AI回复的消息内容 + */ + private String aiReply; + + /** + * AI回复类型: text/image/file + */ + private String aiReplyType; + + /** + * HTTP状态码 + */ + private Integer responseStatus; + + /** + * 响应体 + */ + private String responseBody; + + /** + * 响应头 + */ + private String responseHeaders; + + /** + * 轮询次数 + */ + private Integer pollCount; + + /** + * 轮询开始时间 + */ + private String pollStartTime; + + /** + * 轮询结束时间 + */ + private String pollEndTime; + + /** + * 最终状态: completed/failed/timeout + */ + private String finalStatus; + + /** + * 调用状态: pending/success/failed/timeout + */ + private String status; + + /** + * 开始时间 + */ + private String startTime; + + /** + * 结束时间 + */ + private String endTime; + + /** + * 耗时(毫秒) + */ + private Integer durationMs; + + /** + * 输入Token数 + */ + private Integer promptTokens; + + /** + * 输出Token数 + */ + private Integer completionTokens; + + /** + * 总Token数 + */ + private Integer totalTokens; + + /** + * 费用 + */ + private BigDecimal cost; + + /** + * 函数调用记录 + */ + private String functionCalls; + + /** + * 函数调用结果 + */ + private String functionResults; + + /** + * 错误代码 + */ + private String errorCode; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 客户端IP + */ + private String clientIp; + + /** + * 用户代理 + */ + private String userAgent; + + /** + * 会话ID + */ + private String sessionId; + + /** + * 追踪ID + */ + private String traceId; + + /** + * 扩展元数据 + */ + private String metadata; +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/dto/websocket/ChatRequest.java b/backend-single/src/main/java/com/emotion/dto/websocket/ChatRequest.java index 0db758f..10caa39 100644 --- a/backend-single/src/main/java/com/emotion/dto/websocket/ChatRequest.java +++ b/backend-single/src/main/java/com/emotion/dto/websocket/ChatRequest.java @@ -35,14 +35,14 @@ public class ChatRequest { /** * 发送者类型 */ - @NotNull(message = "发送者类型不能为空") - private SenderType senderType; + @NotBlank(message = "发送者类型不能为空") + private String senderType; /** * 消息类型 */ - @NotNull(message = "消息类型不能为空") - private MessageType messageType; + @NotBlank(message = "消息类型不能为空") + private String messageType; /** * 会话ID(可选) @@ -53,45 +53,4 @@ public class ChatRequest { * 发送时间戳 */ private Long timestamp; - - /** - * 发送者类型枚举 - */ - public enum SenderType { - USER("用户"), - GUEST("访客"), - AI("AI助手"), - SYSTEM("系统"); - - private final String description; - - SenderType(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } - } - - /** - * 消息类型枚举 - */ - public enum MessageType { - TEXT("文本消息"), - IMAGE("图片消息"), - FILE("文件消息"), - SYSTEM("系统消息"), - HEARTBEAT("心跳消息"); - - private final String description; - - MessageType(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } - } } diff --git a/backend-single/src/main/java/com/emotion/dto/websocket/WebSocketMessage.java b/backend-single/src/main/java/com/emotion/dto/websocket/WebSocketMessage.java index dbc5c19..5cb6c34 100644 --- a/backend-single/src/main/java/com/emotion/dto/websocket/WebSocketMessage.java +++ b/backend-single/src/main/java/com/emotion/dto/websocket/WebSocketMessage.java @@ -32,7 +32,7 @@ public class WebSocketMessage { /** * 消息类型 */ - private MessageType type; + private String type; /** * 消息内容 @@ -47,12 +47,12 @@ public class WebSocketMessage { /** * 发送者类型 */ - private SenderType senderType; + private String senderType; /** * 消息状态 */ - private MessageStatus status; + private String status; /** * 创建时间 @@ -63,68 +63,4 @@ public class WebSocketMessage { * 扩展数据 */ private Object data; - - /** - * 消息类型枚举 - */ - public enum MessageType { - TEXT("文本消息"), - TYPING("正在输入"), - SYSTEM("系统消息"), - ERROR("错误消息"), - HEARTBEAT("心跳消息"), - CONNECTION("连接消息"), - AI_THINKING("AI思考中"); - - private final String description; - - MessageType(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } - } - - /** - * 发送者类型枚举 - */ - public enum SenderType { - USER("用户"), - GUEST("访客"), - AI("AI助手"), - SYSTEM("系统"); - - private final String description; - - SenderType(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } - } - - /** - * 消息状态枚举 - */ - public enum MessageStatus { - SENDING("发送中"), - SENT("已发送"), - DELIVERED("已送达"), - READ("已读"), - FAILED("发送失败"); - - private final String description; - - MessageStatus(String description) { - this.description = description; - } - - public String getDescription() { - return description; - } - } } diff --git a/backend-single/src/main/java/com/emotion/service/AchievementService.java b/backend-single/src/main/java/com/emotion/service/AchievementService.java index c0bae15..8f4808a 100644 --- a/backend-single/src/main/java/com/emotion/service/AchievementService.java +++ b/backend-single/src/main/java/com/emotion/service/AchievementService.java @@ -2,7 +2,9 @@ package com.emotion.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.achievement.*; +import com.emotion.dto.response.achievement.AchievementResponse; import com.emotion.entity.Achievement; import java.time.LocalDateTime; @@ -12,14 +14,14 @@ import java.util.List; * 成就服务接口 * * @author emotion-museum - * @date 2025-07-23 + * @date 2025-09-08 */ public interface AchievementService extends IService { /** * 分页查询成就 */ - IPage getPage(BasePageRequest request); + IPage getPage(AchievementPageRequest request); /** * 根据分类查询成就 @@ -87,32 +89,32 @@ public interface AchievementService extends IService { Long countByRarity(String rarity); /** - * 查询平均进度 + * 获取平均进度 */ Double getAvgProgress(); /** - * 查询指定分类的平均进度 + * 根据分类获取平均进度 */ Double getAvgProgressByCategory(String category); /** - * 查询最近解锁的成就 + * 获取最近解锁的成就 */ List getRecentlyUnlocked(Integer limit); /** - * 查询即将完成的成就(进度>80%) + * 获取接近完成的成就 */ List getNearCompletion(); /** - * 查询稀有成就(稀有度为legendary或epic) + * 获取稀有成就 */ List getRareAchievements(); /** - * 更新成就解锁状态 + * 解锁成就 */ boolean unlockAchievement(String id, LocalDateTime unlockedTime); @@ -122,12 +124,59 @@ public interface AchievementService extends IService { boolean updateProgress(String id, Double progress); /** - * 更新成就隐藏状态 + * 更新隐藏状态 */ boolean updateHiddenStatus(String id, Integer isHidden); /** - * 查询推荐成就(基于分类和稀有度) + * 获取推荐成就 */ List getRecommendedAchievements(String category, String rarity, Integer limit); -} + + // 新增的Response相关方法 + + /** + * 分页查询成就响应 + */ + PageResult getPageWithResponse(AchievementPageRequest request); + + /** + * 根据ID获取成就响应 + */ + AchievementResponse getAchievementResponseById(String id); + + /** + * 创建成就并返回响应 + */ + AchievementResponse createAchievementWithResponse(AchievementCreateRequest request); + + /** + * 更新成就并返回响应 + */ + AchievementResponse updateAchievementWithResponse(AchievementUpdateRequest request); + + /** + * 根据分类查询成就响应 + */ + List getByCategoryWithResponse(String category); + + /** + * 根据稀有度查询成就响应 + */ + List getByRarityWithResponse(String rarity); + + /** + * 查询已解锁的成就响应 + */ + List getUnlockedAchievementsWithResponse(); + + /** + * 查询未解锁的成就响应 + */ + List getLockedAchievementsWithResponse(); + + /** + * 查询最近解锁的成就响应 + */ + List getRecentlyUnlockedWithResponse(Integer limit); +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/AiChatService.java b/backend-single/src/main/java/com/emotion/service/AiChatService.java index 68d1371..9e82600 100644 --- a/backend-single/src/main/java/com/emotion/service/AiChatService.java +++ b/backend-single/src/main/java/com/emotion/service/AiChatService.java @@ -1,5 +1,7 @@ package com.emotion.service; +import com.emotion.dto.request.*; +import com.emotion.dto.response.*; /** * AI聊天服务接口 @@ -10,85 +12,93 @@ package com.emotion.service; public interface AiChatService { /** - * 发送聊天消息(保存用户消息和AI回复) - * @param conversationId 会话ID - * @param message 用户消息内容 - * @param userId 用户ID - * @return AI回复内容 + * 发送聊天消息 + * + * @param request AI聊天请求 + * @return AI聊天响应 */ - String sendChatMessage(String conversationId, String message, String userId); + AiChatResponse sendChatMessage(AiChatRequest request); + + /** + * 生成对话总结 + * + * @param request AI总结请求 + * @return AI总结响应 + */ + AiSummaryResponse generateConversationSummary(AiSummaryRequest request); + + /** + * 获取AI服务状态 + * + * @return AI状态响应 + */ + AiStatusResponse getServiceStatus(); + + /** + * 获取聊天统计 + * + * @param request 聊天统计请求 + * @return 聊天统计响应 + */ + ChatStatsResponse getChatStats(ChatStatsRequest request); + + /** + * 访客聊天 + * + * @param request 访客聊天请求 + * @param clientIp 客户端IP + * @return 访客聊天响应 + */ + GuestChatResponse guestChat(GuestChatRequest request, String clientIp); + + /** + * 获取访客用户信息 + * + * @param clientIp 客户端IP + * @return 访客用户信息响应 + */ + GuestUserInfoResponse getGuestUserInfo(String clientIp); + + /** + * 创建对话 + * + * @param request 对话创建请求 + * @param clientIp 客户端IP + * @return 对话响应 + */ + ConversationResponse createConversation(ConversationCreateRequest request, String clientIp); /** * WebSocket方式发送聊天消息(只保存AI回复) + * * @param conversationId 会话ID - * @param message 用户消息内容 - * @param userId 用户ID + * @param message 用户消息内容 + * @param userId 用户ID * @return AI回复内容 */ String sendChatMessageForWebSocket(String conversationId, String message, String userId); /** * WebSocket方式发送聊天消息(只保存AI回复,带messageId) + * * @param conversationId 会话ID - * @param messageId 用户消息ID - * @param message 用户消息内容 - * @param userId 用户ID + * @param messageId 用户消息ID + * @param message 用户消息内容 + * @param userId 用户ID * @return AI回复内容 */ String sendChatMessageForWebSocket(String conversationId, String messageId, String message, String userId); - /** - * 生成对话总结 - * @param conversationId 会话ID - * @param userId 用户ID - * @return 总结内容 - */ - String generateConversationSummary(String conversationId, String userId); - - /** - * 检查AI服务是否可用 - * @return 可用返回true,否则false - */ - boolean isServiceAvailable(); - - /** - * 获取AI服务状态 - * @return "available" 或 "unavailable" - */ - String getServiceStatus(); - /** * 发送消息到Coze AI(不保存消息,仅AI交互) + * * @param conversationId 会话ID - * @param userMessage 用户消息内容 - * @param userId 用户ID + * @param userMessage 用户消息内容 + * @param userId 用户ID * @return AI回复内容 */ String sendMessage(String conversationId, String userMessage, String userId); - /** - * 访客聊天(不登录情况下) - * @param message 用户消息内容 - * @param clientIp 客户端IP - * @return 包含AI回复等信息的Map - */ - java.util.Map guestChat(String message, String clientIp); - - /** - * 创建新对话 - * @param userId 用户ID - * @param title 对话标题 - * @return 包含对话信息的Map - */ - java.util.Map createConversation(String userId, String title); - - /** - * 获取访客用户信息 - * @param clientIp 客户端IP - * @return 包含访客信息的Map - */ - java.util.Map getGuestUserInfo(String clientIp); - /** * 流式聊天(暂时降级为普通聊天) * @param conversationId 会话ID @@ -126,4 +136,4 @@ public interface AiChatService { * @return AI评论内容 */ String sendSummaryMessage(String conversationId, String userMessage, String userId); -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/AuthService.java b/backend-single/src/main/java/com/emotion/service/AuthService.java index a1dc9f5..00c574e 100644 --- a/backend-single/src/main/java/com/emotion/service/AuthService.java +++ b/backend-single/src/main/java/com/emotion/service/AuthService.java @@ -6,6 +6,8 @@ import com.emotion.dto.response.AuthResponse; import com.emotion.dto.response.CaptchaResponse; import com.emotion.dto.response.UserInfoResponse; +import javax.servlet.http.HttpServletRequest; + /** * 认证服务接口 * @@ -64,12 +66,12 @@ public interface AuthService { boolean logout(String userId, String token); /** - * 用户登出(通过令牌) + * 用户登出(通过请求) * - * @param token 访问令牌 + * @param request HTTP请求 * @return 是否登出成功 */ - boolean logoutByToken(String token); + boolean logoutByToken(HttpServletRequest request); /** * 刷新访问令牌 @@ -79,6 +81,14 @@ public interface AuthService { */ AuthResponse refreshToken(String refreshToken); + /** + * 验证访问令牌 + * + * @param request HTTP请求 + * @return 是否有效 + */ + boolean validateToken(HttpServletRequest request); + /** * 验证访问令牌 * @@ -126,4 +136,4 @@ public interface AuthService { * @return 是否存在 */ boolean existsByPhone(String phone); -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/CommentService.java b/backend-single/src/main/java/com/emotion/service/CommentService.java index 2dfb773..d8aaee3 100644 --- a/backend-single/src/main/java/com/emotion/service/CommentService.java +++ b/backend-single/src/main/java/com/emotion/service/CommentService.java @@ -1,13 +1,13 @@ package com.emotion.service; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.comment.CommentCreateRequest; +import com.emotion.dto.request.comment.CommentUpdateRequest; +import com.emotion.dto.request.comment.CommentPageRequest; +import com.emotion.dto.response.comment.CommentResponse; import com.emotion.entity.Comment; -import java.time.LocalDateTime; -import java.util.List; - /** * 评论服务接口 * @@ -19,110 +19,25 @@ public interface CommentService extends IService { /** * 分页查询评论 */ - IPage getPage(BasePageRequest request); + PageResult getPage(CommentPageRequest request); /** - * 根据帖子ID分页查询评论 + * 根据ID获取评论响应 */ - IPage getPageByPostId(BasePageRequest request, String postId); - - /** - * 根据用户ID分页查询评论 - */ - IPage getPageByUserId(BasePageRequest request, String userId); - - /** - * 根据帖子ID查询所有评论 - */ - List getByPostId(String postId); - - /** - * 根据用户ID查询所有评论 - */ - List getByUserId(String userId); - - /** - * 根据回复的评论ID查询回复 - */ - List getRepliesByCommentId(String replyToId); - - /** - * 查询顶级评论(非回复的评论) - */ - List getTopLevelCommentsByPostId(String postId); - - /** - * 根据点赞数范围查询评论 - */ - List getByLikesRange(Integer minLikes, Integer maxLikes); - - /** - * 根据时间范围查询评论 - */ - List getByTimeRange(LocalDateTime startTime, LocalDateTime endTime); - - /** - * 统计帖子的评论数量 - */ - Long countByPostId(String postId); - - /** - * 统计用户的评论数量 - */ - Long countByUserId(String userId); - - /** - * 统计评论的回复数量 - */ - Long countRepliesByCommentId(String commentId); - - /** - * 统计帖子的顶级评论数量 - */ - Long countTopLevelCommentsByPostId(String postId); - - /** - * 查询最受欢迎的评论(按点赞数排序) - */ - List getMostLikedCommentsByPostId(String postId, Integer limit); - - /** - * 查询最新的评论 - */ - List getLatestCommentsByPostId(String postId, Integer limit); - - /** - * 查询用户最近的评论 - */ - List getRecentByUserId(String userId, Integer limit); - - /** - * 查询热门评论(按点赞数和回复数综合排序) - */ - List getPopularCommentsByPostId(String postId, Integer limit); - - /** - * 根据关键词搜索评论内容 - */ - List searchByKeyword(String keyword); - - /** - * 根据帖子ID和关键词搜索评论 - */ - List searchByPostIdAndKeyword(String postId, String keyword); - - /** - * 查询用户在指定帖子下的评论 - */ - List getByPostIdAndUserId(String postId, String userId); - - /** - * 更新评论点赞数 - */ - boolean updateLikes(String id, Integer increment); - + CommentResponse getById(String id); + /** * 创建评论 */ - Comment createComment(String postId, String userId, String content, String replyToId); -} + CommentResponse create(CommentCreateRequest request); + + /** + * 更新评论 + */ + CommentResponse update(CommentUpdateRequest request); + + /** + * 删除评论 + */ + boolean delete(String id); +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/CommunityPostService.java b/backend-single/src/main/java/com/emotion/service/CommunityPostService.java index 2ba5d76..e16d07d 100644 --- a/backend-single/src/main/java/com/emotion/service/CommunityPostService.java +++ b/backend-single/src/main/java/com/emotion/service/CommunityPostService.java @@ -1,13 +1,13 @@ package com.emotion.service; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.community.CommunityPostCreateRequest; +import com.emotion.dto.request.community.CommunityPostUpdateRequest; +import com.emotion.dto.request.community.CommunityPostPageRequest; +import com.emotion.dto.response.community.CommunityPostResponse; import com.emotion.entity.CommunityPost; -import java.time.LocalDateTime; -import java.util.List; - /** * 社区帖子服务接口 * @@ -19,141 +19,25 @@ public interface CommunityPostService extends IService { /** * 分页查询帖子 */ - IPage getPage(BasePageRequest request); + PageResult getPage(CommunityPostPageRequest request); /** - * 分页查询公开帖子 + * 根据ID获取帖子响应 */ - IPage getPublicPostsPage(BasePageRequest request); - - /** - * 根据用户ID分页查询帖子 - */ - IPage getPageByUserId(BasePageRequest request, String userId); - - /** - * 根据地点ID查询帖子 - */ - List getByLocationId(String locationId); - - /** - * 根据帖子类型查询帖子 - */ - List getByType(String type); - - /** - * 查询用户的私密帖子 - */ - List getPrivatePostsByUserId(String userId); - - /** - * 根据点赞数范围查询帖子 - */ - List getByLikesRange(Integer minLikes, Integer maxLikes); - - /** - * 根据浏览数范围查询帖子 - */ - List getByViewRange(Integer minViews, Integer maxViews); - - /** - * 根据评论数范围查询帖子 - */ - List getByCommentRange(Integer minComments, Integer maxComments); - - /** - * 根据时间范围查询帖子 - */ - List getByTimeRange(LocalDateTime startTime, LocalDateTime endTime); - - /** - * 统计用户的帖子数量 - */ - Long countByUserId(String userId); - - /** - * 统计用户的公开帖子数量 - */ - Long countPublicPostsByUserId(String userId); - - /** - * 统计用户的私密帖子数量 - */ - Long countPrivatePostsByUserId(String userId); - - /** - * 统计指定类型的帖子数量 - */ - Long countByType(String type); - - /** - * 统计地点的帖子数量 - */ - Long countByLocationId(String locationId); - - /** - * 查询最受欢迎的帖子(按点赞数排序) - */ - List getMostLikedPosts(Integer limit); - - /** - * 查询最热门的帖子(按浏览数排序) - */ - List getMostViewedPosts(Integer limit); - - /** - * 查询最新的帖子 - */ - List getLatestPosts(Integer limit); - - /** - * 查询热门帖子(综合点赞、浏览、评论) - */ - List getPopularPosts(Integer limit); - - /** - * 根据标签搜索帖子 - */ - List getByTag(String tag); - - /** - * 根据关键词搜索帖子 - */ - List searchByKeyword(String keyword); - - /** - * 查询用户最近的帖子 - */ - List getRecentByUserId(String userId, Integer limit); - - /** - * 更新帖子点赞数 - */ - boolean updateLikes(String id, Integer increment); - - /** - * 更新帖子浏览数 - */ - boolean incrementViewCount(String id); - - /** - * 更新帖子评论数 - */ - boolean updateCommentCount(String id, Integer increment); - - /** - * 更新帖子隐私状态 - */ - boolean updatePrivacyStatus(String id, Integer isPrivate); - - /** - * 查询推荐帖子(基于类型和地点) - */ - List getRecommendedPosts(String type, String locationId, Integer limit); - + CommunityPostResponse getById(String id); + /** * 创建帖子 */ - CommunityPost createPost(String userId, String title, String content, String type, - String locationId, String tags, Integer isPrivate); -} + CommunityPostResponse create(CommunityPostCreateRequest request); + + /** + * 更新帖子 + */ + CommunityPostResponse update(CommunityPostUpdateRequest request); + + /** + * 删除帖子 + */ + boolean delete(String id); +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/ConversationService.java b/backend-single/src/main/java/com/emotion/service/ConversationService.java index 43358d7..c74abd6 100644 --- a/backend-single/src/main/java/com/emotion/service/ConversationService.java +++ b/backend-single/src/main/java/com/emotion/service/ConversationService.java @@ -2,9 +2,13 @@ package com.emotion.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.ConversationCreateRequest; +import com.emotion.dto.request.ConversationPageRequest; +import com.emotion.dto.response.ConversationResponse; import com.emotion.entity.Conversation; +import javax.servlet.http.HttpServletRequest; import java.time.LocalDateTime; import java.util.List; @@ -19,12 +23,12 @@ public interface ConversationService extends IService { /** * 分页查询会话 */ - IPage getPage(BasePageRequest request); + IPage getPage(ConversationPageRequest request); /** * 根据用户ID分页查询会话 */ - IPage getPageByUserId(BasePageRequest request, String userId); + IPage getPageByUserId(ConversationPageRequest request); /** * 根据用户ID查询会话列表 @@ -85,4 +89,60 @@ public interface ConversationService extends IService { * 结束会话 */ boolean endConversation(String conversationId); + + /** + * 分页查询会话响应 + */ + PageResult getPageWithResponse(ConversationPageRequest request); + + /** + * 根据用户ID分页查询会话响应 + */ + PageResult getPageByUserIdWithResponse(ConversationPageRequest request); + + /** + * 根据ID获取会话响应 + */ + ConversationResponse getConversationResponseById(String id); + + /** + * 根据用户ID查询会话响应列表 + */ + List getByUserIdWithResponse(String userId); + + /** + * 获取活跃会话响应列表 + */ + List getActiveConversationsWithResponse(); + + /** + * 获取归档会话响应列表 + */ + List getArchivedConversationsWithResponse(); + + /** + * 归档对话 + */ + boolean archiveConversation(String id); + + /** + * 激活对话 + */ + boolean activateConversation(String id); + + /** + * 创建会话并返回响应对象 + */ + ConversationResponse createConversationWithResponse(ConversationCreateRequest request, + HttpServletRequest httpRequest); + + /** + * 更新会话并返回响应对象 + */ + ConversationResponse updateConversationWithResponse(ConversationCreateRequest request); + + /** + * 更新会话状态 + */ + boolean updateConversationStatus(String id, String status); } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/CozeApiCallService.java b/backend-single/src/main/java/com/emotion/service/CozeApiCallService.java index a88181c..293d9de 100644 --- a/backend-single/src/main/java/com/emotion/service/CozeApiCallService.java +++ b/backend-single/src/main/java/com/emotion/service/CozeApiCallService.java @@ -1,8 +1,11 @@ package com.emotion.service; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.coze.CozeApiCallPageRequest; +import com.emotion.dto.request.coze.CozeApiCallCreateRequest; +import com.emotion.dto.request.coze.CozeApiCallUpdateRequest; +import com.emotion.dto.response.coze.CozeApiCallResponse; import com.emotion.entity.CozeApiCall; import java.math.BigDecimal; @@ -20,37 +23,27 @@ public interface CozeApiCallService extends IService { /** * 分页查询API调用记录 */ - IPage getPage(BasePageRequest request); + PageResult getPage(CozeApiCallPageRequest request); /** - * 根据会话ID分页查询API调用记录 + * 根据ID获取API调用记录 */ - IPage getPageByConversationId(BasePageRequest request, String conversationId); - + CozeApiCallResponse getById(String id); + /** - * 根据用户ID分页查询API调用记录 + * 创建API调用记录 */ - IPage getPageByUserId(BasePageRequest request, String userId); - + CozeApiCallResponse create(CozeApiCallCreateRequest request); + /** - * 根据Bot ID查询API调用记录 + * 更新API调用记录 */ - List getByBotId(String botId); - + CozeApiCallResponse update(CozeApiCallUpdateRequest request); + /** - * 根据状态查询API调用记录 + * 删除API调用记录 */ - List getByStatus(String status); - - /** - * 根据请求类型查询API调用记录 - */ - List getByRequestType(String requestType); - - /** - * 根据时间范围查询API调用记录 - */ - List getByTimeRange(LocalDateTime startTime, LocalDateTime endTime); + boolean delete(String id); /** * 统计用户的API调用次数 @@ -76,42 +69,4 @@ public interface CozeApiCallService extends IService { * 统计用户的API调用费用 */ BigDecimal sumCostByUserId(String userId); - - /** - * 查询失败的API调用记录 - */ - List getFailedCalls(); - - /** - * 查询超时的API调用记录 - */ - List getTimeoutCalls(); - - /** - * 根据追踪ID查询API调用记录 - */ - CozeApiCall getByTraceId(String traceId); - - /** - * 根据会话ID和请求类型查询API调用记录 - */ - List getByConversationIdAndRequestType(String conversationId, String requestType); - - /** - * 更新API调用状态 - */ - boolean updateStatus(String id, String status, String finalStatus, LocalDateTime endTime); - - /** - * 更新API调用结果 - */ - boolean updateResult(String id, Integer responseStatus, String responseBody, String aiReply, - Integer totalTokens, BigDecimal cost, String status, String finalStatus, - LocalDateTime endTime); - - /** - * 创建API调用记录 - */ - CozeApiCall createApiCall(String conversationId, String messageId, String userId, - String requestType, String requestUrl, String requestBody); -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/DiaryCommentService.java b/backend-single/src/main/java/com/emotion/service/DiaryCommentService.java index 8429f84..6cb9805 100644 --- a/backend-single/src/main/java/com/emotion/service/DiaryCommentService.java +++ b/backend-single/src/main/java/com/emotion/service/DiaryCommentService.java @@ -2,10 +2,12 @@ package com.emotion.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.DiaryCommentCreateRequest; +import com.emotion.dto.request.DiaryCommentPageRequest; +import com.emotion.dto.response.DiaryCommentResponse; import com.emotion.entity.DiaryComment; -import java.math.BigDecimal; import java.util.List; /** @@ -19,100 +21,28 @@ public interface DiaryCommentService extends IService { /** * 分页查询评论 */ - IPage getPage(BasePageRequest request); - + PageResult getPageWithResponse(DiaryCommentPageRequest request); + /** - * 根据日记ID分页查询评论 + * 根据ID获取评论响应 */ - IPage getPageByDiaryId(String diaryId, BasePageRequest request); - + DiaryCommentResponse getCommentResponseById(String id); + /** - * 根据用户ID分页查询评论 + * 获取评论树结构响应 */ - IPage getPageByUserId(String userId, BasePageRequest request); - + List getCommentTreeWithResponse(String diaryId); + /** - * 根据父评论ID查询回复列表 + * 创建评论并返回响应对象 */ - List getRepliesByParentId(String parentCommentId); - + DiaryCommentResponse createCommentWithResponse(DiaryCommentCreateRequest request); + /** - * 根据评论类型查询评论列表 + * 更新评论并返回响应对象 */ - List getByCommentType(String commentType); - - /** - * 根据日记ID和评论类型查询评论列表 - */ - List getByDiaryIdAndCommentType(String diaryId, String commentType); - - /** - * 增加点赞数 - */ - boolean incrementLikeCount(String commentId); - - /** - * 减少点赞数 - */ - boolean decrementLikeCount(String commentId); - - /** - * 增加回复数 - */ - boolean incrementReplyCount(String commentId); - - /** - * 减少回复数 - */ - boolean decrementReplyCount(String commentId); - - /** - * 更新最后回复时间 - */ - boolean updateLastReplyTime(String commentId); - - /** - * 设置置顶状态 - */ - boolean setTop(String commentId, Integer isTop); - - /** - * 统计日记评论数量 - */ - Long countByDiaryId(String diaryId); - - /** - * 统计用户评论数量 - */ - Long countByUserId(String userId); - - /** - * 统计指定类型的评论数量 - */ - Long countByCommentType(String commentType); - - /** - * 统计回复数量 - */ - Long countReplies(String parentCommentId); - - /** - * 创建评论 - */ - DiaryComment createComment(String diaryId, String userId, String content, List images, - String parentCommentId, Integer isAnonymous); - - /** - * 创建AI评论 - */ - DiaryComment createAiComment(String diaryId, String content, String aiCommentSource, - BigDecimal emotionScore, BigDecimal sentimentScore); - - /** - * 更新评论 - */ - boolean updateComment(String commentId, String content, List images); - + DiaryCommentResponse updateCommentWithResponse(DiaryCommentCreateRequest request); + /** * 删除评论 */ @@ -129,7 +59,17 @@ public interface DiaryCommentService extends IService { boolean restoreComment(String commentId); /** - * 获取评论树结构 + * 增加点赞数 */ - List getCommentTree(String diaryId); -} \ No newline at end of file + boolean incrementLikeCount(String commentId); + + /** + * 减少点赞数 + */ + boolean decrementLikeCount(String commentId); + + /** + * 设置置顶状态 + */ + boolean setTop(String commentId, Integer isTop); +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/DiaryPostService.java b/backend-single/src/main/java/com/emotion/service/DiaryPostService.java index 6dcc690..8bc683f 100644 --- a/backend-single/src/main/java/com/emotion/service/DiaryPostService.java +++ b/backend-single/src/main/java/com/emotion/service/DiaryPostService.java @@ -1,8 +1,11 @@ package com.emotion.service; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.DiaryPostCreateRequest; +import com.emotion.dto.request.DiaryPostPageRequest; +import com.emotion.dto.request.DiaryPostUpdateRequest; +import com.emotion.dto.response.DiaryPostResponse; import com.emotion.entity.DiaryPost; import java.math.BigDecimal; @@ -19,47 +22,37 @@ public interface DiaryPostService extends IService { /** * 分页查询日记 */ - IPage getPage(BasePageRequest request); + PageResult getPageWithResponse(DiaryPostPageRequest request); + + /** + * 根据ID获取日记响应 + */ + DiaryPostResponse getDiaryPostResponseById(String id); + + /** + * 创建日记并返回响应对象 + */ + DiaryPostResponse createDiaryPostWithResponse(DiaryPostCreateRequest request); + + /** + * 更新日记并返回响应对象 + */ + DiaryPostResponse updateDiaryPostWithResponse(DiaryPostUpdateRequest request); + + /** + * 删除日记 + */ + boolean deleteDiaryPost(String diaryId); /** - * 根据用户ID分页查询日记 + * 软删除日记 */ - IPage getPageByUserId(String userId, BasePageRequest request); + boolean softDeleteDiaryPost(String diaryId); /** - * 根据用户ID查询公开日记 + * 恢复日记 */ - IPage getPublicPageByUserId(String userId, BasePageRequest request); - - /** - * 查询精选日记 - */ - IPage getFeaturedPage(BasePageRequest request); - - /** - * 根据状态查询日记列表 - */ - List getByStatus(String status); - - /** - * 根据用户ID和状态查询日记列表 - */ - List getByUserIdAndStatus(String userId, String status); - - /** - * 根据心情状态查询日记列表 - */ - List getByMood(String mood); - - /** - * 根据标签查询日记列表 - */ - List getByTags(List tags); - - /** - * 根据地点查询日记列表 - */ - List getByLocation(String location); + boolean restoreDiaryPost(String diaryId); /** * 增加浏览数 @@ -120,47 +113,9 @@ public interface DiaryPostService extends IService { * 统计精选日记数量 */ Long countFeatured(); - - /** - * 统计指定状态的日记数量 - */ - Long countByStatus(String status); - - /** - * 创建日记 - */ - DiaryPost createDiaryPost(com.emotion.dto.request.DiaryPostCreateRequest request); - - /** - * 更新日记 - */ - boolean updateDiaryPost(String diaryId, com.emotion.dto.request.DiaryPostUpdateRequest request); - - /** - * 删除日记 - */ - boolean deleteDiaryPost(String diaryId); - - /** - * 软删除日记 - */ - boolean softDeleteDiaryPost(String diaryId); - - /** - * 恢复日记 - */ - boolean restoreDiaryPost(String diaryId); - - /** - * 添加AI评论 - */ - boolean addAiComment(String diaryId, String aiComment, Object aiEmotionAnalysis, - BigDecimal aiSentimentScore, List aiKeywords, String aiSuggestions); - + /** * 发表日记并生成AI评论 - * @param request 日记创建请求 - * @return 日记响应对象,包含AI评论 */ - com.emotion.dto.response.DiaryPostResponse publishDiaryWithAiComment(com.emotion.dto.request.DiaryPostCreateRequest request); -} \ No newline at end of file + DiaryPostResponse publishDiaryWithAiComment(DiaryPostCreateRequest request); +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/EmotionAnalysisService.java b/backend-single/src/main/java/com/emotion/service/EmotionAnalysisService.java index 91deb6c..f52ab2f 100644 --- a/backend-single/src/main/java/com/emotion/service/EmotionAnalysisService.java +++ b/backend-single/src/main/java/com/emotion/service/EmotionAnalysisService.java @@ -3,6 +3,11 @@ package com.emotion.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.EmotionAnalysisCreateRequest; +import com.emotion.dto.request.EmotionAnalysisPageRequest; +import com.emotion.dto.request.EmotionAnalysisUpdateRequest; +import com.emotion.dto.response.EmotionAnalysisResponse; import com.emotion.entity.EmotionAnalysis; import java.time.LocalDateTime; @@ -96,4 +101,67 @@ public interface EmotionAnalysisService extends IService { */ EmotionAnalysis createEmotionAnalysis(String messageId, String userId, String primaryEmotion, String polarity, Double intensity, Double confidence); -} + + // 新增的方法 + + /** + * 分页查询情绪分析记录响应 + */ + PageResult getPageWithResponse(EmotionAnalysisPageRequest request); + + /** + * 根据用户ID分页查询情绪分析记录响应 + */ + PageResult getPageByUserIdWithResponse(String userId, EmotionAnalysisPageRequest request); + + /** + * 根据ID获取情绪分析记录响应 + */ + EmotionAnalysisResponse getEmotionAnalysisResponseById(String id); + + /** + * 根据消息ID获取情绪分析记录响应 + */ + EmotionAnalysisResponse getEmotionAnalysisResponseByMessageId(String messageId); + + /** + * 创建情绪分析记录并返回响应 + */ + EmotionAnalysisResponse createEmotionAnalysisWithResponse(EmotionAnalysisCreateRequest request); + + /** + * 更新情绪分析记录并返回响应 + */ + EmotionAnalysisResponse updateEmotionAnalysisWithResponse(EmotionAnalysisUpdateRequest request); + + /** + * 删除情绪分析记录 + */ + boolean deleteEmotionAnalysis(String id); + + /** + * 根据主要情绪查询分析记录响应 + */ + List getEmotionAnalysisResponsesByPrimaryEmotion(String primaryEmotion); + + /** + * 根据情绪极性查询分析记录响应 + */ + List getEmotionAnalysisResponsesByPolarity(String polarity); + + /** + * 根据用户ID和情绪类型查询分析记录响应 + */ + List getEmotionAnalysisResponsesByUserIdAndEmotion(String userId, String primaryEmotion); + + /** + * 根据时间范围查询用户情绪分析记录响应 + */ + List getEmotionAnalysisResponsesByUserIdAndTimeRange(String userId, + LocalDateTime startTime, LocalDateTime endTime); + + /** + * 查询用户最近的情绪分析记录响应 + */ + List getEmotionAnalysisResponsesRecentByUserId(String userId, Integer limit); +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/EmotionRecordService.java b/backend-single/src/main/java/com/emotion/service/EmotionRecordService.java index bf0b8b5..26c6081 100644 --- a/backend-single/src/main/java/com/emotion/service/EmotionRecordService.java +++ b/backend-single/src/main/java/com/emotion/service/EmotionRecordService.java @@ -3,10 +3,16 @@ package com.emotion.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.EmotionRecordCreateRequest; +import com.emotion.dto.request.EmotionRecordPageRequest; +import com.emotion.dto.request.EmotionRecordUpdateRequest; +import com.emotion.dto.response.EmotionRecordResponse; import com.emotion.entity.EmotionRecord; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; /** * 情绪记录服务接口 @@ -111,4 +117,46 @@ public interface EmotionRecordService extends IService { */ EmotionRecord createEmotionRecord(String userId, String emotionType, Double intensity, String trigger, String location, String notes); -} + + // 新增的方法 + + /** + * 分页查询情绪记录响应 + */ + PageResult getPageWithResponse(EmotionRecordPageRequest request); + + /** + * 根据用户ID分页查询情绪记录响应 + */ + PageResult getPageByUserIdWithResponse(String userId, EmotionRecordPageRequest request); + + /** + * 根据ID获取情绪记录响应 + */ + EmotionRecordResponse getEmotionRecordResponseById(String id); + + /** + * 创建情绪记录并返回响应 + */ + EmotionRecordResponse createEmotionRecordWithResponse(EmotionRecordCreateRequest request); + + /** + * 更新情绪记录并返回响应 + */ + EmotionRecordResponse updateEmotionRecordWithResponse(EmotionRecordUpdateRequest request); + + /** + * 删除情绪记录 + */ + boolean deleteEmotionRecord(String id); + + /** + * 获取用户情绪统计 + */ + Map getEmotionStats(String userId, String startDate, String endDate); + + /** + * 查询用户最近的情绪记录并返回响应 + */ + List getRecentByUserIdWithResponse(String userId, Integer limit); +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/TokenService.java b/backend-single/src/main/java/com/emotion/service/TokenService.java index 060d921..c587c05 100644 --- a/backend-single/src/main/java/com/emotion/service/TokenService.java +++ b/backend-single/src/main/java/com/emotion/service/TokenService.java @@ -2,6 +2,8 @@ package com.emotion.service; import com.emotion.dto.response.UserInfoResponse; +import javax.servlet.http.HttpServletRequest; + /** * 令牌服务接口 * @@ -13,24 +15,24 @@ public interface TokenService { /** * 从请求中提取并验证令牌,获取用户信息 * - * @param token 访问令牌 + * @param request HTTP请求 * @return 用户信息响应 */ - UserInfoResponse getUserInfoByToken(String token); + UserInfoResponse getUserInfoByToken(HttpServletRequest request); /** * 从请求中提取并验证令牌,获取用户名 * - * @param token 访问令牌 + * @param request HTTP请求 * @return 用户名 */ - String getUsernameByToken(String token); + String getUsernameByToken(HttpServletRequest request); /** * 验证令牌并返回用户ID * - * @param token 访问令牌 + * @param request HTTP请求 * @return 用户ID */ - String validateTokenAndGetUserId(String token); -} + String validateTokenAndGetUserId(HttpServletRequest request); +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/WebSocketService.java b/backend-single/src/main/java/com/emotion/service/WebSocketService.java index de6d679..7e62c12 100644 --- a/backend-single/src/main/java/com/emotion/service/WebSocketService.java +++ b/backend-single/src/main/java/com/emotion/service/WebSocketService.java @@ -1,6 +1,6 @@ package com.emotion.service; -import com.emotion.dto.websocket.ChatRequest; +import com.emotion.dto.request.WebSocketRequest; import com.emotion.dto.websocket.ConnectRequest; import java.security.Principal; @@ -9,32 +9,48 @@ import java.security.Principal; * WebSocket服务接口 * * @author emotion-museum - * @date 2025-07-25 + * @date 2025-09-08 */ public interface WebSocketService { /** * 处理聊天消息 + * + * @param request WebSocket请求对象 + * @param sessionId 会话ID + * @param principal 用户主体 */ - void handleChatMessage(ChatRequest request, String sessionId, Principal principal); + void handleChatMessage(WebSocketRequest request, String sessionId, Principal principal); /** * 处理用户连接 + * + * @param request 连接请求对象 + * @param sessionId 会话ID + * @param principal 用户主体 */ void handleUserConnect(ConnectRequest request, String sessionId, Principal principal); /** * 处理用户断开连接 + * + * @param sessionId 会话ID + * @param principal 用户主体 */ void handleUserDisconnect(String sessionId, Principal principal); /** * 处理心跳消息 + * + * @param sessionId 会话ID + * @param principal 用户主体 */ void handleHeartbeat(String sessionId, Principal principal); /** * 获取在线用户数量 + * + * @return 在线用户数量 */ int getOnlineUserCount(); -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/AchievementServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/AchievementServiceImpl.java index 386c581..e528885 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/AchievementServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/AchievementServiceImpl.java @@ -5,27 +5,41 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.achievement.AchievementCreateRequest; +import com.emotion.dto.request.achievement.AchievementPageRequest; +import com.emotion.dto.request.achievement.AchievementUpdateRequest; +import com.emotion.dto.response.achievement.AchievementResponse; import com.emotion.entity.Achievement; import com.emotion.mapper.AchievementMapper; import com.emotion.service.AchievementService; +import com.emotion.util.SnowflakeIdGenerator; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.stream.Collectors; /** * 成就服务实现类 * * @author emotion-museum - * @date 2025-07-23 + * @date 2025-09-08 */ @Service public class AchievementServiceImpl extends ServiceImpl implements AchievementService { + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @Override - public IPage getPage(BasePageRequest request) { + public IPage getPage(AchievementPageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); @@ -35,6 +49,30 @@ public class AchievementServiceImpl extends ServiceImpl getPageWithResponse(AchievementPageRequest request) { + IPage page = getPage(request); + List responses = page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + PageResult pageResult = new PageResult<>(); + pageResult.setCurrent(page.getCurrent()); + pageResult.setSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setRecords(responses); + return pageResult; + } + + @Override + public AchievementResponse getAchievementResponseById(String id) { + Achievement achievement = this.getById(id); + if (achievement == null) { + return null; + } + return convertToResponse(achievement); + } + + @Override + public AchievementResponse createAchievementWithResponse(AchievementCreateRequest request) { + Achievement achievement = new Achievement(); + org.springframework.beans.BeanUtils.copyProperties(request, achievement); + achievement.setId(snowflakeIdGenerator.nextIdAsString()); + + boolean saved = this.save(achievement); + if (!saved) { + return null; + } + return convertToResponse(achievement); + } + + @Override + public AchievementResponse updateAchievementWithResponse(AchievementUpdateRequest request) { + Achievement achievement = this.getById(request.getId()); + if (achievement == null) { + return null; + } + + // 更新非空字段 + if (request.getTitle() != null) { + achievement.setTitle(request.getTitle()); + } + if (request.getDescription() != null) { + achievement.setDescription(request.getDescription()); + } + if (request.getCategory() != null) { + achievement.setCategory(request.getCategory()); + } + if (request.getIcon() != null) { + achievement.setIcon(request.getIcon()); + } + if (request.getRarity() != null) { + achievement.setRarity(request.getRarity()); + } + if (request.getConditionType() != null) { + achievement.setConditionType(request.getConditionType()); + } + if (request.getConditionValue() != null) { + achievement.setConditionValue(request.getConditionValue()); + } + if (request.getRewards() != null) { + achievement.setRewards(request.getRewards()); + } + if (request.getIsHidden() != null) { + achievement.setIsHidden(request.getIsHidden()); + } + + achievement.setUpdateTime(LocalDateTime.now()); + + boolean updated = this.updateById(achievement); + if (!updated) { + return null; + } + return convertToResponse(achievement); + } + + @Override + public List getByCategoryWithResponse(String category) { + List achievements = getByCategory(category); + return achievements.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getByRarityWithResponse(String rarity) { + List achievements = getByRarity(rarity); + return achievements.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getUnlockedAchievementsWithResponse() { + List achievements = getUnlockedAchievements(); + return achievements.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getLockedAchievementsWithResponse() { + List achievements = getLockedAchievements(); + return achievements.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getRecentlyUnlockedWithResponse(Integer limit) { + List achievements = getRecentlyUnlocked(limit); + return achievements.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + /** + * 转换为响应对象 + */ + private AchievementResponse convertToResponse(Achievement achievement) { + AchievementResponse response = new AchievementResponse(); + org.springframework.beans.BeanUtils.copyProperties(achievement, response); + response.setId(achievement.getId()); + if (achievement.getCreateTime() != null) { + response.setCreateTime(achievement.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (achievement.getUpdateTime() != null) { + response.setUpdateTime(achievement.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + if (achievement.getUnlockedTime() != null) { + response.setUnlockedTime(achievement.getUnlockedTime().format(DATE_TIME_FORMATTER)); + } + return response; + } +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java index 85c3f7f..c1eb571 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/AiChatServiceImpl.java @@ -13,6 +13,10 @@ import com.emotion.service.ConversationService; import com.emotion.service.CozeApiCallService; import com.emotion.service.EmotionRecordService; import com.emotion.service.EmotionAnalysisService; +import com.emotion.dto.request.*; +import com.emotion.dto.response.*; +import com.emotion.util.SnowflakeIdGenerator; +import com.emotion.util.UserContextUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -66,6 +70,9 @@ public class AiChatServiceImpl implements AiChatService { @Autowired private EmotionAnalysisService emotionAnalysisService; + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + @Value("${emotion.coze.api.token:}") private String cozeApiToken; @@ -106,27 +113,32 @@ public class AiChatServiceImpl implements AiChatService { private static final String CONTENT_TYPE_KEY = "content_type"; private static final String TEXT_TYPE = "text"; private static final String ANSWER_TYPE = "answer"; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Override - public String sendChatMessage(String conversationId, String message, String userId) { - log.info("发送聊天消息: conversationId={}, userId={}, message={}", conversationId, userId, message); + public AiChatResponse sendChatMessage(AiChatRequest request) { + log.info("发送聊天消息: conversationId={}, userId={}, message={}", + request.getConversationId(), request.getUserId(), request.getMessage()); try { // 先保存用户消息 Message userMessage = new Message(); - userMessage.setConversationId(conversationId); - userMessage.setCreateBy(userId); - userMessage.setContent(message); + userMessage.setId(snowflakeIdGenerator.nextIdAsString()); + userMessage.setConversationId(request.getConversationId()); + userMessage.setCreateBy(request.getUserId()); + userMessage.setContent(request.getMessage()); userMessage.setType("text"); userMessage.setSender("user"); userMessage = messageService.createMessage(userMessage); // 调用Coze API(传入messageId) - String aiReply = sendMessageWithMessageId(conversationId, userMessage.getId(), message, userId); + String aiReply = sendMessageWithMessageId(request.getConversationId(), userMessage.getId(), + request.getMessage(), request.getUserId()); // 保存AI回复 Message aiMessage = new Message(); - aiMessage.setConversationId(conversationId); + aiMessage.setId(snowflakeIdGenerator.nextIdAsString()); + aiMessage.setConversationId(request.getConversationId()); aiMessage.setCreateBy("ai"); aiMessage.setContent(aiReply); aiMessage.setType("text"); @@ -136,11 +148,259 @@ public class AiChatServiceImpl implements AiChatService { log.info("聊天消息处理完成: userMessageId={}, aiMessageId={}", userMessage.getId(), aiMessage.getId()); - return aiReply; + // 构建响应 + AiChatResponse response = new AiChatResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setConversationId(request.getConversationId()); + response.setUserMessage(request.getMessage()); + response.setAiReply(aiReply); + response.setUserId(request.getUserId()); + response.setTimestamp(System.currentTimeMillis()); + + return response; } catch (Exception e) { log.error("发送聊天消息失败", e); - return "抱歉,AI服务暂时不可用,请稍后再试。"; + // 构建错误响应 + AiChatResponse response = new AiChatResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setConversationId(request.getConversationId()); + response.setUserMessage(request.getMessage()); + response.setAiReply("抱歉,AI服务暂时不可用,请稍后再试。"); + response.setUserId(request.getUserId()); + response.setTimestamp(System.currentTimeMillis()); + return response; + } + } + + @Override + public AiSummaryResponse generateConversationSummary(AiSummaryRequest request) { + log.info("生成对话总结: conversationId={}, userId={}", request.getConversationId(), request.getUserId()); + + try { + // 获取对话历史 + String conversationHistory = getConversationHistory(request.getConversationId()); + + // 构建总结请求 + String summaryPrompt = "请为以下对话生成一个简洁的总结:\n\n" + conversationHistory; + + // 调用AI生成总结 - 使用专门的总结bot + String summary = sendSummaryMessage(request.getConversationId(), summaryPrompt, request.getUserId()); + + log.info("对话总结生成完成: conversationId={}", request.getConversationId()); + + // 构建响应 + AiSummaryResponse response = new AiSummaryResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setConversationId(request.getConversationId()); + response.setSummary(summary); + response.setUserId(request.getUserId()); + response.setTimestamp(System.currentTimeMillis()); + + return response; + + } catch (Exception e) { + log.error("生成对话总结失败", e); + // 构建错误响应 + AiSummaryResponse response = new AiSummaryResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setConversationId(request.getConversationId()); + response.setSummary("无法生成对话总结,请稍后再试。"); + response.setUserId(request.getUserId()); + response.setTimestamp(System.currentTimeMillis()); + return response; + } + } + + @Override + public AiStatusResponse getServiceStatus() { + log.info("获取AI服务状态"); + + boolean available = isServiceAvailable(); + + // 构建响应 + AiStatusResponse response = new AiStatusResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setAvailable(available); + response.setStatus(available ? "available" : "unavailable"); + response.setTimestamp(System.currentTimeMillis()); + + return response; + } + + @Override + public ChatStatsResponse getChatStats(ChatStatsRequest request) { + log.info("获取聊天统计: userId={}, conversationId={}", request.getUserId(), request.getConversationId()); + + // 构建响应 + ChatStatsResponse response = new ChatStatsResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + + if (request.getUserId() != null && !request.getUserId().trim().isEmpty()) { + Long userConversationCount = conversationService.countByUserId(request.getUserId()); + Long activeConversationCount = conversationService.countActiveByUserId(request.getUserId()); + + response.setUserConversationCount(userConversationCount); + response.setActiveConversationCount(activeConversationCount); + } + + if (request.getConversationId() != null && !request.getConversationId().trim().isEmpty()) { + Long conversationMessageCount = messageService.countByConversationId(request.getConversationId()); + response.setConversationMessageCount(conversationMessageCount); + } + + response.setTimestamp(System.currentTimeMillis()); + + return response; + } + + @Override + public GuestChatResponse guestChat(GuestChatRequest request, String clientIp) { + log.info("访客聊天: message={}, clientIp={}", request.getMessage(), clientIp); + + try { + // 生成访客会话ID + String guestConversationId = "guest_" + clientIp.replace(".", "_") + "_" + System.currentTimeMillis(); + + // 调用AI服务 + String aiReply = sendMessage(guestConversationId, request.getMessage(), "guest"); + + // 保存访客消息 + Message guestMessage = new Message(); + guestMessage.setId(snowflakeIdGenerator.nextIdAsString()); + guestMessage.setConversationId(guestConversationId); + guestMessage.setCreateBy("guest"); + guestMessage.setContent(request.getMessage()); + guestMessage.setType("text"); + guestMessage.setSender("guest"); + guestMessage = messageService.createMessage(guestMessage); + + // 保存AI回复 + Message aiMessage = new Message(); + aiMessage.setId(snowflakeIdGenerator.nextIdAsString()); + aiMessage.setConversationId(guestConversationId); + aiMessage.setCreateBy("ai"); + aiMessage.setContent(aiReply); + aiMessage.setType("text"); + aiMessage.setSender("ai"); + aiMessage = messageService.createMessage(aiMessage); + + // 构建响应 + GuestChatResponse response = new GuestChatResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setMessage(aiReply); + response.setMessageId(aiMessage.getId()); + response.setTimestamp(System.currentTimeMillis()); + response.setError(false); + + log.info("访客聊天处理完成: guestMessageId={}, aiMessageId={}", + guestMessage.getId(), aiMessage.getId()); + + return response; + + } catch (Exception e) { + log.error("访客聊天失败", e); + // 构建错误响应 + GuestChatResponse response = new GuestChatResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setMessage("抱歉,服务暂时不可用,请稍后再试。"); + response.setMessageId(null); + response.setTimestamp(System.currentTimeMillis()); + response.setError(true); + return response; + } + } + + @Override + public GuestUserInfoResponse getGuestUserInfo(String clientIp) { + log.info("获取访客用户信息: clientIp={}", clientIp); + + try { + // 生成访客用户信息 + String guestId = "guest_" + clientIp.replace(".", "_"); + String guestUsername = "访客_" + clientIp.substring(clientIp.lastIndexOf(".") + 1); + + // 构建响应 + GuestUserInfoResponse response = new GuestUserInfoResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setId(guestId); + response.setUsername(guestUsername); + response.setNickname(guestUsername); + response.setType("guest"); + response.setClientIp(clientIp); + response.setUserCreateTime(System.currentTimeMillis()); + + log.info("访客用户信息获取成功: guestId={}", guestId); + + return response; + + } catch (Exception e) { + log.error("获取访客用户信息失败", e); + // 构建错误响应 + GuestUserInfoResponse response = new GuestUserInfoResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setId("error"); + response.setUsername("访客用户"); + response.setNickname("访客用户"); + response.setType("guest"); + response.setClientIp(clientIp); + response.setUserCreateTime(System.currentTimeMillis()); + return response; + } + } + + @Override + public ConversationResponse createConversation(ConversationCreateRequest request, String clientIp) { + log.info("创建对话: userId={}, title={}", request.getUserId(), request.getTitle()); + + try { + // 调用AI服务创建对话 + Map aiConversation = createConversation(request.getUserId(), request.getTitle()); + + // 创建数据库对话记录 + Conversation conversation = conversationService.createConversation( + request.getUserId(), + request.getTitle(), + request.getType() != null ? request.getType() : "user"); + + // 构建响应 + ConversationResponse response = new ConversationResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setUserId(conversation.getUserId()); + response.setCozeConversationId(conversation.getCozeConversationId()); + response.setTitle(conversation.getTitle()); + response.setType(conversation.getType()); + response.setStatus(conversation.getConversationStatus()); + response.setMessageCount(conversation.getMessageCount()); + if (conversation.getLastActiveTime() != null) { + response.setLastMessageTime(conversation.getLastActiveTime().format(DATE_TIME_FORMATTER)); + } + response.setClientIp(clientIp); + + if (conversation.getCreateTime() != null) { + response.setCreateTime(conversation.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (conversation.getUpdateTime() != null) { + response.setUpdateTime(conversation.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + + log.info("对话创建成功: conversationId={}", conversation.getId()); + + return response; + + } catch (Exception e) { + log.error("创建对话失败", e); + // 构建错误响应 + ConversationResponse response = new ConversationResponse(); + response.setId(snowflakeIdGenerator.nextIdAsString()); + response.setUserId(request.getUserId()); + response.setTitle(request.getTitle()); + response.setType(request.getType() != null ? request.getType() : "user"); + response.setStatus("error"); + response.setMessageCount(0); + response.setLastMessageTime(""); + response.setClientIp(clientIp); + return response; } } @@ -155,6 +415,7 @@ public class AiChatServiceImpl implements AiChatService { // 注意:不保存用户消息,因为WebSocket处理器已经保存了 // 只保存AI回复 Message aiMessage = new Message(); + aiMessage.setId(snowflakeIdGenerator.nextIdAsString()); aiMessage.setConversationId(conversationId); aiMessage.setCreateBy("ai"); aiMessage.setContent(aiReply); @@ -184,8 +445,9 @@ public class AiChatServiceImpl implements AiChatService { // 注意:不保存用户消息,因为WebSocket处理器已经保存了 // 只保存AI回复 Message aiMessage = new Message(); + aiMessage.setId(snowflakeIdGenerator.nextIdAsString()); aiMessage.setConversationId(conversationId); - aiMessage.setCreateBy(userId); // 设置创建人为当前用户 + aiMessage.setCreateBy(userId); // 设置创建人为当前用户 aiMessage.setContent(aiReply); aiMessage.setType("text"); aiMessage.setSender("ai"); @@ -205,58 +467,11 @@ public class AiChatServiceImpl implements AiChatService { } @Override - public String generateConversationSummary(String conversationId, String userId) { - log.info("生成对话总结: conversationId={}, userId={}", conversationId, userId); + public String sendMessage(String conversationId, String userMessage, String userId) { + log.info("发送消息到Coze AI: conversationId={}, userId={}", conversationId, userId); - try { - // 获取对话历史 - String conversationHistory = getConversationHistory(conversationId); - - // 构建总结请求 - String summaryPrompt = "请为以下对话生成一个简洁的总结:\n\n" + conversationHistory; - - // 调用AI生成总结 - 使用专门的总结bot - String summary = sendSummaryMessage(conversationId, summaryPrompt, userId); - - log.info("对话总结生成完成: conversationId={}", conversationId); - - return summary; - - } catch (Exception e) { - log.error("生成对话总结失败", e); - return "无法生成对话总结,请稍后再试。"; - } - } - - @Override - public boolean isServiceAvailable() { - try { - // 简单的健康检查 - return cozeApiToken != null && !cozeApiToken.isEmpty() && - chatBotId != null && !chatBotId.isEmpty(); - } catch (Exception e) { - log.error("检查AI服务可用性失败", e); - return false; - } - } - - @Override - public String getServiceStatus() { - if (isServiceAvailable()) { - return "available"; - } else { - return "unavailable"; - } - } - - /** - * 发送消息到Coze AI(带messageId) - */ - private String sendMessageWithMessageId(String conversationId, String messageId, String userMessage, String userId) { - log.info("发送消息到Coze AI: conversationId={}, messageId={}, userId={}", conversationId, messageId, userId); - - // 创建API调用记录(包含messageId) - CozeApiCall apiCall = createApiCallRecord(conversationId, messageId, userMessage, userId, "chat"); + // 创建API调用记录(不包含messageId,用于向后兼容) + CozeApiCall apiCall = createApiCallRecord(conversationId, null, userMessage, userId, "chat"); try { return executeCozeApiCall(apiCall, conversationId, userMessage, userId); @@ -268,11 +483,198 @@ public class AiChatServiceImpl implements AiChatService { } @Override - public String sendMessage(String conversationId, String userMessage, String userId) { - log.info("发送消息到Coze AI: conversationId={}, userId={}", conversationId, userId); + public String streamChat(String conversationId, String message, String userId) { + log.info("流式聊天: conversationId={}, userId={}", conversationId, userId); - // 创建API调用记录(不包含messageId,用于向后兼容) - CozeApiCall apiCall = createApiCallRecord(conversationId, null, userMessage, userId, "chat"); + try { + // 构建流式请求 + Map requestBody = buildCozeRequest(conversationId, message, userId); + requestBody.put("stream", true); + + // 这里应该实现流式处理,暂时降级到普通聊天 + return sendMessage(conversationId, message, userId); + + } catch (Exception e) { + log.error("流式聊天失败", e); + return "抱歉,流式聊天暂时不可用,请稍后再试。"; + } + } + + @Override + public boolean healthCheck() { + try { + // 简化健康检查 - 检查必要配置是否存在 + boolean configValid = cozeApiToken != null && !cozeApiToken.trim().isEmpty() && + chatBotId != null && !chatBotId.trim().isEmpty() && + cozeBaseUrl != null && !cozeBaseUrl.trim().isEmpty(); + + if (!configValid) { + log.warn("Coze API 配置不完整"); + return false; + } + + // 可选:发送一个简单的测试请求 + // 这里可以调用一个轻量级的API来验证连接 + return true; + + } catch (Exception e) { + log.error("健康检查失败: {}", e.getMessage()); + return false; + } + } + + @Override + public Map generateEmotionSummary(String userId) { + log.info("生成用户情绪记录总结: userId={}", userId); + + Map result = new HashMap<>(); + + try { + // 获取用户当天的所有聊天记录 + LocalDate today = LocalDate.now(); + LocalDateTime startOfDay = today.atStartOfDay(); + LocalDateTime endOfDay = today.atTime(23, 59, 59); + + List todayMessages = messageService.getByUserIdAndTimeRange(userId, startOfDay, endOfDay); + log.info("获取到用户当天聊天记录数量: {}", todayMessages.size()); + + if (todayMessages.isEmpty()) { + result.put("success", false); + result.put("message", "今天还没有聊天记录,无法生成情绪总结"); + return result; + } + + // 整合聊天记录 + String chatHistory = integrateChatHistory(todayMessages); + log.info("聊天记录整合完成,总长度: {}", chatHistory.length()); + + // Coze 中已经在工作流设置了提示词,目前不需要构建情绪分析提示词 + // String emotionPrompt = buildEmotionAnalysisPrompt(chatHistory); + + // 调用Coze API进行情绪分析总结 + String conversationId = "emotion_summary_" + userId + "_" + + today.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + String emotionSummary = sendSummaryMessage(conversationId, chatHistory, userId); + log.info("情绪分析总结生成完成: {}", emotionSummary); + + // 解析AI返回的情绪分析结果 + EmotionAnalysis analysisResult = parseEmotionSummary(emotionSummary); + + // 创建情绪记录 + EmotionRecord emotionRecord = createEmotionRecord(userId, analysisResult, chatHistory); + + result.put("success", true); + result.put("emotionRecord", emotionRecord); + result.put("summary", emotionSummary); + result.put("analysisResult", analysisResult); + result.put("messageCount", todayMessages.size()); + result.put("recordDate", today); + + log.info("情绪记录总结生成成功: recordId={}", emotionRecord.getId()); + + } catch (Exception e) { + log.error("生成情绪记录总结失败", e); + result.put("success", false); + result.put("message", "生成情绪总结失败: " + e.getMessage()); + } + + return result; + } + + @Override + @Async("taskExecutor") + public CompletableFuture> generateEmotionSummaryAsync(String userId) { + Map result = generateEmotionSummary(userId); + return CompletableFuture.completedFuture(result); + } + + @Override + public String sendSummaryMessage(String conversationId, String userMessage, String userId) { + log.info("发送总结消息到Coze AI: conversationId={}, userId={}", conversationId, userId); + + // 创建API调用记录(总结不需要messageId) + CozeApiCall apiCall = createSummaryApiCallRecord(conversationId, null, userMessage, userId, "summary"); + + try { + // 构建请求头 + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + cozeApiToken); + headers.set("Content-Type", "application/json"); + + // 构建请求体 - 使用总结专用的bot和workflow + Map requestBody = buildSummaryRequest(conversationId, userMessage, userId); + + // 更新API调用记录的请求信息 + updateApiCallRequest(apiCall, cozeBaseUrl + chatPath, requestBody, headers); + + HttpEntity> request = new HttpEntity<>(requestBody, headers); + + // 构建完整的API URL + String cozeApiUrl = cozeBaseUrl + chatPath; + log.info("发送Coze总结请求到: {}, 请求体: {}", cozeApiUrl, requestBody); + + // 发送请求 + ResponseEntity response = restTemplate.exchange( + cozeApiUrl, + HttpMethod.POST, + request, + String.class); + + log.info("收到Coze总结初始响应: {}", response.getBody()); + + // 更新API调用记录的响应信息 + updateApiCallResponse(apiCall, response); + + // 解析响应获取chat_id和conversation_id + JSONObject responseJson = JSON.parseObject(response.getBody()); + String chatId = extractChatIdFromResponse(responseJson); + String cozeConversationId = extractConversationIdFromResponse(responseJson); + + if (chatId != null && cozeConversationId != null) { + // 更新API调用记录的Coze ID信息 + updateApiCallCozeIds(apiCall, chatId, cozeConversationId); + + // 轮询聊天状态直到完成并获取回复内容 + String aiReply = waitForChatCompletionWithTracking(chatId, cozeConversationId, apiCall); + log.info("Coze AI总结响应成功: reply={}", aiReply); + + // 更新API调用记录的最终结果 + updateApiCallSuccess(apiCall, aiReply); + + return aiReply; + } else { + log.error("无法从Coze总结响应中获取chat_id或conversation_id"); + updateApiCallError(apiCall, "INVALID_RESPONSE", "无法从Coze总结响应中获取chat_id或conversation_id"); + return "抱歉,AI总结服务响应异常,请稍后再试。"; + } + + } catch (Exception e) { + log.error("发送总结消息到Coze AI失败", e); + updateApiCallError(apiCall, "REQUEST_FAILED", e.getMessage()); + return "抱歉,AI总结服务暂时不可用,请稍后再试。"; + } + } + + public boolean isServiceAvailable() { + try { + // 简单的健康检查 + return cozeApiToken != null && !cozeApiToken.isEmpty() && + chatBotId != null && !chatBotId.isEmpty(); + } catch (Exception e) { + log.error("检查AI服务可用性失败", e); + return false; + } + } + + /** + * 发送消息到Coze AI(带messageId) + */ + private String sendMessageWithMessageId(String conversationId, String messageId, String userMessage, + String userId) { + log.info("发送消息到Coze AI: conversationId={}, messageId={}, userId={}", conversationId, messageId, userId); + + // 创建API调用记录(包含messageId) + CozeApiCall apiCall = createApiCallRecord(conversationId, messageId, userMessage, userId, "chat"); try { return executeCozeApiCall(apiCall, conversationId, userMessage, userId); @@ -340,7 +742,6 @@ public class AiChatServiceImpl implements AiChatService { } } - @Override public Map guestChat(String message, String clientIp) { log.info("访客聊天: message={}, clientIp={}", message, clientIp); @@ -390,7 +791,6 @@ public class AiChatServiceImpl implements AiChatService { return result; } - @Override public Map createConversation(String userId, String title) { log.info("创建对话: userId={}, title={}", userId, title); @@ -417,75 +817,6 @@ public class AiChatServiceImpl implements AiChatService { return result; } - @Override - public Map getGuestUserInfo(String clientIp) { - log.info("获取访客用户信息: clientIp={}", clientIp); - - Map result = new HashMap<>(); - - try { - // 生成访客用户信息 - String guestId = "guest_" + clientIp.replace(".", "_"); - String guestUsername = "访客_" + clientIp.substring(clientIp.lastIndexOf(".") + 1); - - result.put("id", guestId); - result.put("username", guestUsername); - result.put("nickname", guestUsername); - result.put("type", "guest"); - result.put("clientIp", clientIp); - result.put("createTime", System.currentTimeMillis()); - - log.info("访客用户信息获取成功: guestId={}", guestId); - - } catch (Exception e) { - log.error("获取访客用户信息失败", e); - result.put("error", "获取用户信息失败"); - } - - return result; - } - - @Override - public String streamChat(String conversationId, String message, String userId) { - log.info("流式聊天: conversationId={}, userId={}", conversationId, userId); - - try { - // 构建流式请求 - Map requestBody = buildCozeRequest(conversationId, message, userId); - requestBody.put("stream", true); - - // 这里应该实现流式处理,暂时降级到普通聊天 - return sendMessage(conversationId, message, userId); - - } catch (Exception e) { - log.error("流式聊天失败", e); - return "抱歉,流式聊天暂时不可用,请稍后再试。"; - } - } - - @Override - public boolean healthCheck() { - try { - // 简化健康检查 - 检查必要配置是否存在 - boolean configValid = cozeApiToken != null && !cozeApiToken.trim().isEmpty() && - chatBotId != null && !chatBotId.trim().isEmpty() && - cozeBaseUrl != null && !cozeBaseUrl.trim().isEmpty(); - - if (!configValid) { - log.warn("Coze API 配置不完整"); - return false; - } - - // 可选:发送一个简单的测试请求 - // 这里可以调用一个轻量级的API来验证连接 - return true; - - } catch (Exception e) { - log.error("健康检查失败: {}", e.getMessage()); - return false; - } - } - /** * 构建Coze API请求 - 根据官方文档修正格式 */ @@ -559,7 +890,8 @@ public class AiChatServiceImpl implements AiChatService { log.info("轮询聊天状态,第{}次尝试: chatId={}, conversationId={}", attempt + 1, chatId, conversationId); // 构建状态查询URL - String statusUrl = cozeBaseUrl + "/v3/chat/retrieve?chat_id=" + chatId + "&conversation_id=" + conversationId; + String statusUrl = cozeBaseUrl + "/v3/chat/retrieve?chat_id=" + chatId + "&conversation_id=" + + conversationId; // 构建请求头 HttpHeaders headers = new HttpHeaders(); @@ -618,7 +950,8 @@ public class AiChatServiceImpl implements AiChatService { log.info("获取聊天消息: chatId={}, conversationId={}", chatId, conversationId); // 构建消息查询URL - String messagesUrl = cozeBaseUrl + "/v3/chat/message/list?chat_id=" + chatId + "&conversation_id=" + conversationId; + String messagesUrl = cozeBaseUrl + "/v3/chat/message/list?chat_id=" + chatId + "&conversation_id=" + + conversationId; // 构建请求头 HttpHeaders headers = new HttpHeaders(); @@ -638,7 +971,8 @@ public class AiChatServiceImpl implements AiChatService { log.info("消息响应: {}", messagesResponse); if (messagesResponse != null && messagesResponse.getJSONArray("data") != null) { - java.util.List messages = messagesResponse.getJSONArray("data").toJavaList(JSONObject.class); + java.util.List messages = messagesResponse.getJSONArray("data") + .toJavaList(JSONObject.class); log.info("收到{}条消息", messages.size()); // 查找AI的回复消息(role=assistant, type=answer) @@ -667,77 +1001,6 @@ public class AiChatServiceImpl implements AiChatService { } } - - - /** - * 发送总结消息到Coze AI - */ - public String sendSummaryMessage(String conversationId, String userMessage, String userId) { - log.info("发送总结消息到Coze AI: conversationId={}, userId={}", conversationId, userId); - - // 创建API调用记录(总结不需要messageId) - CozeApiCall apiCall = createSummaryApiCallRecord(conversationId, null, userMessage, userId, "summary"); - - try { - // 构建请求头 - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", "Bearer " + cozeApiToken); - headers.set("Content-Type", "application/json"); - - // 构建请求体 - 使用总结专用的bot和workflow - Map requestBody = buildSummaryRequest(conversationId, userMessage, userId); - - // 更新API调用记录的请求信息 - updateApiCallRequest(apiCall, cozeBaseUrl + chatPath, requestBody, headers); - - HttpEntity> request = new HttpEntity<>(requestBody, headers); - - // 构建完整的API URL - String cozeApiUrl = cozeBaseUrl + chatPath; - log.info("发送Coze总结请求到: {}, 请求体: {}", cozeApiUrl, requestBody); - - // 发送请求 - ResponseEntity response = restTemplate.exchange( - cozeApiUrl, - HttpMethod.POST, - request, - String.class); - - log.info("收到Coze总结初始响应: {}", response.getBody()); - - // 更新API调用记录的响应信息 - updateApiCallResponse(apiCall, response); - - // 解析响应获取chat_id和conversation_id - JSONObject responseJson = JSON.parseObject(response.getBody()); - String chatId = extractChatIdFromResponse(responseJson); - String cozeConversationId = extractConversationIdFromResponse(responseJson); - - if (chatId != null && cozeConversationId != null) { - // 更新API调用记录的Coze ID信息 - updateApiCallCozeIds(apiCall, chatId, cozeConversationId); - - // 轮询聊天状态直到完成并获取回复内容 - String aiReply = waitForChatCompletionWithTracking(chatId, cozeConversationId, apiCall); - log.info("Coze AI总结响应成功: reply={}", aiReply); - - // 更新API调用记录的最终结果 - updateApiCallSuccess(apiCall, aiReply); - - return aiReply; - } else { - log.error("无法从Coze总结响应中获取chat_id或conversation_id"); - updateApiCallError(apiCall, "INVALID_RESPONSE", "无法从Coze总结响应中获取chat_id或conversation_id"); - return "抱歉,AI总结服务响应异常,请稍后再试。"; - } - - } catch (Exception e) { - log.error("发送总结消息到Coze AI失败", e); - updateApiCallError(apiCall, "REQUEST_FAILED", e.getMessage()); - return "抱歉,AI总结服务暂时不可用,请稍后再试。"; - } - } - /** * 构建总结请求 - 使用专门的总结bot和workflow */ @@ -791,10 +1054,11 @@ public class AiChatServiceImpl implements AiChatService { /** * 创建API调用记录 */ - private CozeApiCall createApiCallRecord(String conversationId, String messageId, String userMessage, String userId, String requestType) { + private CozeApiCall createApiCallRecord(String conversationId, String messageId, String userMessage, String userId, + String requestType) { CozeApiCall apiCall = CozeApiCall.builder() .conversationId(conversationId) - .messageId(messageId) // 设置messageId + .messageId(messageId) // 设置messageId .userId(userId) .requestType(requestType) .userMessage(userMessage) @@ -804,8 +1068,8 @@ public class AiChatServiceImpl implements AiChatService { .status("pending") .startTime(LocalDateTime.now()) .traceId(UUID.randomUUID().toString()) - .createBy(userId) // 设置创建人为当前用户 - .updateBy(userId) // 设置更新人为当前用户 + .createBy(userId) // 设置创建人为当前用户 + .updateBy(userId) // 设置更新人为当前用户 .build(); // 获取客户端信息 @@ -830,10 +1094,11 @@ public class AiChatServiceImpl implements AiChatService { /** * 创建总结API调用记录 */ - private CozeApiCall createSummaryApiCallRecord(String conversationId, String messageId, String userMessage, String userId, String requestType) { + private CozeApiCall createSummaryApiCallRecord(String conversationId, String messageId, String userMessage, + String userId, String requestType) { CozeApiCall apiCall = CozeApiCall.builder() .conversationId(conversationId) - .messageId(messageId) // 设置messageId + .messageId(messageId) // 设置messageId .userId(userId) .requestType(requestType) .userMessage(userMessage) @@ -843,8 +1108,8 @@ public class AiChatServiceImpl implements AiChatService { .status("pending") .startTime(LocalDateTime.now()) .traceId(UUID.randomUUID().toString()) - .createBy(userId) // 设置创建人为当前用户 - .updateBy(userId) // 设置更新人为当前用户 + .createBy(userId) // 设置创建人为当前用户 + .updateBy(userId) // 设置更新人为当前用户 .build(); // 获取客户端信息 @@ -869,7 +1134,8 @@ public class AiChatServiceImpl implements AiChatService { /** * 更新API调用记录的请求信息 */ - private void updateApiCallRequest(CozeApiCall apiCall, String requestUrl, Map requestBody, HttpHeaders headers) { + private void updateApiCallRequest(CozeApiCall apiCall, String requestUrl, Map requestBody, + HttpHeaders headers) { try { apiCall.setRequestUrl(requestUrl); apiCall.setRequestBody(JSON.toJSONString(requestBody)); @@ -921,7 +1187,7 @@ public class AiChatServiceImpl implements AiChatService { apiCall.setFinalStatus("completed"); apiCall.setEndTime(endTime); apiCall.setDurationMs((int) durationMs); - apiCall.setUpdateBy(apiCall.getUserId()); // 设置更新人 + apiCall.setUpdateBy(apiCall.getUserId()); // 设置更新人 cozeApiCallService.updateById(apiCall); log.info("API调用成功: id={}, duration={}ms", apiCall.getId(), durationMs); @@ -943,7 +1209,7 @@ public class AiChatServiceImpl implements AiChatService { apiCall.setEndTime(endTime); apiCall.setDurationMs((int) durationMs); apiCall.setErrorMessage(errorMessage); - apiCall.setUpdateBy(apiCall.getUserId()); // 设置更新人 + apiCall.setUpdateBy(apiCall.getUserId()); // 设置更新人 cozeApiCallService.updateById(apiCall); log.info("API调用失败: id={}, duration={}ms, error={}", apiCall.getId(), durationMs, errorMessage); @@ -979,7 +1245,8 @@ public class AiChatServiceImpl implements AiChatService { */ private HttpServletRequest getCurrentRequest() { try { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); return attributes != null ? attributes.getRequest() : null; } catch (Exception e) { return null; @@ -990,17 +1257,7 @@ public class AiChatServiceImpl implements AiChatService { * 获取客户端IP地址 */ private String getClientIp(HttpServletRequest request) { - String ip = request.getHeader("X-Forwarded-For"); - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - return ip; + return UserContextUtils.getClientIpAddress(request); } /** @@ -1027,70 +1284,6 @@ public class AiChatServiceImpl implements AiChatService { } } - @Override - public Map generateEmotionSummary(String userId) { - log.info("开始生成用户情绪记录总结: userId={}", userId); - - Map result = new HashMap<>(); - - try { - // 获取用户当天的所有聊天记录 - LocalDate today = LocalDate.now(); - LocalDateTime startOfDay = today.atStartOfDay(); - LocalDateTime endOfDay = today.atTime(23, 59, 59); - - List todayMessages = messageService.getByUserIdAndTimeRange(userId, startOfDay, endOfDay); - log.info("获取到用户当天聊天记录数量: {}", todayMessages.size()); - - if (todayMessages.isEmpty()) { - result.put("success", false); - result.put("message", "今天还没有聊天记录,无法生成情绪总结"); - return result; - } - - // 整合聊天记录 - String chatHistory = integrateChatHistory(todayMessages); - log.info("聊天记录整合完成,总长度: {}", chatHistory.length()); - - // Coze 中已经在工作流设置了提示词,目前不需要构建情绪分析提示词 - // String emotionPrompt = buildEmotionAnalysisPrompt(chatHistory); - - // 调用Coze API进行情绪分析总结 - String conversationId = "emotion_summary_" + userId + "_" + today.format(DateTimeFormatter.ofPattern("yyyyMMdd")); - String emotionSummary = sendSummaryMessage(conversationId, chatHistory, userId); - log.info("情绪分析总结生成完成: {}", emotionSummary); - - // 解析AI返回的情绪分析结果 - EmotionAnalysis analysisResult = parseEmotionSummary(emotionSummary); - - // 创建情绪记录 - EmotionRecord emotionRecord = createEmotionRecord(userId, analysisResult, chatHistory); - - result.put("success", true); - result.put("emotionRecord", emotionRecord); - result.put("summary", emotionSummary); - result.put("analysisResult", analysisResult); - result.put("messageCount", todayMessages.size()); - result.put("recordDate", today); - - log.info("情绪记录总结生成成功: recordId={}", emotionRecord.getId()); - - } catch (Exception e) { - log.error("生成情绪记录总结失败", e); - result.put("success", false); - result.put("message", "生成情绪总结失败: " + e.getMessage()); - } - - return result; - } - - @Override - @Async("taskExecutor") - public CompletableFuture> generateEmotionSummaryAsync(String userId) { - Map result = generateEmotionSummary(userId); - return CompletableFuture.completedFuture(result); - } - /** * 整合聊天记录 */ @@ -1112,27 +1305,27 @@ public class AiChatServiceImpl implements AiChatService { */ private String buildEmotionAnalysisPrompt(String chatHistory) { return String.format(""" - 请分析以下聊天记录中用户的情绪状态,并生成一个情绪总结报告。 + 请分析以下聊天记录中用户的情绪状态,并生成一个情绪总结报告。 - %s + %s - 请从以下几个维度进行分析: - 1. 主要情绪类型(如:开心、焦虑、愤怒、悲伤、平静等) - 2. 情绪强度(0-1之间的数值,0表示很轻微,1表示很强烈) - 3. 情绪触发因素(导致情绪变化的主要原因) - 4. 情绪变化趋势(情绪在对话过程中的变化) - 5. 建议和关怀(针对用户情绪状态的建议) + 请从以下几个维度进行分析: + 1. 主要情绪类型(如:开心、焦虑、愤怒、悲伤、平静等) + 2. 情绪强度(0-1之间的数值,0表示很轻微,1表示很强烈) + 3. 情绪触发因素(导致情绪变化的主要原因) + 4. 情绪变化趋势(情绪在对话过程中的变化) + 5. 建议和关怀(针对用户情绪状态的建议) - 请以JSON格式返回分析结果: - { - "primaryEmotion": "主要情绪类型", - "intensity": 0.8, - "triggers": "触发因素描述", - "emotionTrend": "情绪变化趋势", - "suggestions": "建议和关怀", - "summary": "整体情绪总结" - } - """, chatHistory); + 请以JSON格式返回分析结果: + { + "primaryEmotion": "主要情绪类型", + "intensity": 0.8, + "triggers": "触发因素描述", + "emotionTrend": "情绪变化趋势", + "suggestions": "建议和关怀", + "summary": "整体情绪总结" + } + """, chatHistory); } /** @@ -1185,20 +1378,20 @@ public class AiChatServiceImpl implements AiChatService { // 积极情绪 if (emotion.contains("快乐") || emotion.contains("高兴") || emotion.contains("喜悦") || - emotion.contains("兴奋") || emotion.contains("满足") || emotion.contains("感激") || - emotion.contains("爱") || emotion.contains("希望") || emotion.contains("自信") || - emotion.contains("平静") || emotion.contains("放松") || emotion.contains("开心") || - emotion.contains("幸福") || emotion.contains("乐观") || emotion.contains("满意")) { + emotion.contains("兴奋") || emotion.contains("满足") || emotion.contains("感激") || + emotion.contains("爱") || emotion.contains("希望") || emotion.contains("自信") || + emotion.contains("平静") || emotion.contains("放松") || emotion.contains("开心") || + emotion.contains("幸福") || emotion.contains("乐观") || emotion.contains("满意")) { return "positive"; } // 消极情绪 if (emotion.contains("悲伤") || emotion.contains("愤怒") || emotion.contains("恐惧") || - emotion.contains("焦虑") || emotion.contains("沮丧") || emotion.contains("失望") || - emotion.contains("孤独") || emotion.contains("痛苦") || emotion.contains("绝望") || - emotion.contains("愧疚") || emotion.contains("羞耻") || emotion.contains("嫉妒") || - emotion.contains("厌恶") || emotion.contains("烦躁") || emotion.contains("压抑") || - emotion.contains("无助") || emotion.contains("困惑") || emotion.contains("担心")) { + emotion.contains("焦虑") || emotion.contains("沮丧") || emotion.contains("失望") || + emotion.contains("孤独") || emotion.contains("痛苦") || emotion.contains("绝望") || + emotion.contains("愧疚") || emotion.contains("羞耻") || emotion.contains("嫉妒") || + emotion.contains("厌恶") || emotion.contains("烦躁") || emotion.contains("压抑") || + emotion.contains("无助") || emotion.contains("困惑") || emotion.contains("担心")) { return "negative"; } diff --git a/backend-single/src/main/java/com/emotion/service/impl/AuthServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/AuthServiceImpl.java index 23a8cd6..c5a05e5 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/AuthServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/AuthServiceImpl.java @@ -13,6 +13,7 @@ import com.emotion.exception.TokenException; import com.emotion.service.AuthService; import com.emotion.service.UserService; import com.emotion.util.JwtUtil; +import com.emotion.util.TokenUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +23,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; @@ -55,6 +57,9 @@ public class AuthServiceImpl implements AuthService { @Autowired private JwtUtil jwtUtil; + @Autowired + private TokenUtil tokenUtil; + private static final String CAPTCHA_PREFIX = "captcha:"; private static final String TOKEN_PREFIX = "token:"; private static final String REFRESH_TOKEN_PREFIX = "refresh_token:"; @@ -239,7 +244,8 @@ public class AuthServiceImpl implements AuthService { } @Override - public boolean logoutByToken(String token) { + public boolean logoutByToken(HttpServletRequest request) { + String token = tokenUtil.extractToken(request); String userId = validateTokenAndGetUserId(token); return logout(userId, token); } @@ -248,7 +254,7 @@ public class AuthServiceImpl implements AuthService { * 验证令牌并获取用户ID */ private String validateTokenAndGetUserId(String token) { - if (!StringUtils.hasText(token)) { + if (!tokenUtil.isValidToken(token)) { throw new TokenException("未提供访问令牌"); } @@ -293,9 +299,15 @@ public class AuthServiceImpl implements AuthService { return response; } + @Override + public boolean validateToken(HttpServletRequest request) { + String token = tokenUtil.extractToken(request); + return validateToken(token); + } + @Override public boolean validateToken(String token) { - if (!StringUtils.hasText(token)) { + if (!tokenUtil.isValidToken(token)) { return false; } @@ -314,7 +326,7 @@ public class AuthServiceImpl implements AuthService { @Override public String getUserIdFromToken(String token) { - if (!StringUtils.hasText(token)) { + if (!tokenUtil.isValidToken(token)) { return null; } @@ -336,7 +348,7 @@ public class AuthServiceImpl implements AuthService { @Override public String getUsernameFromToken(String token) { - if (!StringUtils.hasText(token)) { + if (!tokenUtil.isValidToken(token)) { return null; } @@ -498,4 +510,4 @@ public class AuthServiceImpl implements AuthService { User user = userService.getByPhone(phone); return user != null; } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/CommentServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/CommentServiceImpl.java index 56ea0f5..6bd1f93 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/CommentServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/CommentServiceImpl.java @@ -1,18 +1,27 @@ package com.emotion.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.comment.CommentCreateRequest; +import com.emotion.dto.request.comment.CommentUpdateRequest; +import com.emotion.dto.request.comment.CommentPageRequest; +import com.emotion.dto.response.comment.CommentResponse; import com.emotion.entity.Comment; import com.emotion.mapper.CommentMapper; import com.emotion.service.CommentService; +import com.emotion.util.SnowflakeIdGenerator; +import com.emotion.util.UserContextUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.stream.Collectors; /** * 评论服务实现类 @@ -23,219 +32,125 @@ import java.util.List; @Service public class CommentServiceImpl extends ServiceImpl implements CommentService { + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @Override - public IPage getPage(BasePageRequest request) { + public PageResult getPage(CommentPageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 根据请求参数构建查询条件 + if (StringUtils.hasText(request.getPostId())) { + wrapper.eq(Comment::getPostId, request.getPostId()); + } + + if (StringUtils.hasText(request.getUserId())) { + wrapper.eq(Comment::getUserId, request.getUserId()); + } + if (StringUtils.hasText(request.getKeyword())) { wrapper.like(Comment::getContent, request.getKeyword()); } - + wrapper.eq(Comment::getIsDeleted, 0).orderByDesc(Comment::getCreateTime); - return this.page(page, wrapper); + page = this.page(page, wrapper); + + return convertPageToPageResult(page); } @Override - public IPage getPageByPostId(BasePageRequest request, String postId) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime); - return this.page(page, wrapper); + public CommentResponse getById(String id) { + Comment comment = this.getBaseMapper().selectById(id); + if (comment == null) { + return null; + } + return convertToResponse(comment); } @Override - public IPage getPageByUserId(BasePageRequest request, String userId) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getUserId, userId) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime); - return this.page(page, wrapper); + public CommentResponse create(CommentCreateRequest request) { + Comment comment = new Comment(); + comment.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID + comment.setPostId(request.getPostId()); + + // 从上下文中获取当前用户ID + String currentUserId = UserContextUtils.getCurrentUserId(); + if (currentUserId != null) { + comment.setUserId(currentUserId); + } else if (request.getUserId() != null) { + // 如果上下文中没有用户ID,则使用请求中的用户ID(向后兼容) + comment.setUserId(request.getUserId()); + } else { + throw new IllegalArgumentException("用户ID不能为空"); + } + + comment.setContent(request.getContent()); + comment.setReplyToId(request.getReplyToId()); + comment.setLikes(0); + + this.save(comment); + return convertToResponse(comment); } @Override - public List getByPostId(String postId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .eq(Comment::getIsDeleted, 0) - .orderByAsc(Comment::getCreateTime); - return this.list(wrapper); + public CommentResponse update(CommentUpdateRequest request) { + Comment comment = this.getBaseMapper().selectById(request.getId()); + if (comment == null) { + return null; + } + + // 只更新非空字段 + if (StringUtils.hasText(request.getContent())) { + comment.setContent(request.getContent()); + } + + this.updateById(comment); + return convertToResponse(comment); } @Override - public List getByUserId(String userId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getUserId, userId) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime); - return this.list(wrapper); - } - - @Override - public List getRepliesByCommentId(String replyToId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getReplyToId, replyToId) - .eq(Comment::getIsDeleted, 0) - .orderByAsc(Comment::getCreateTime); - return this.list(wrapper); - } - - @Override - public List getTopLevelCommentsByPostId(String postId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .isNull(Comment::getReplyToId) - .eq(Comment::getIsDeleted, 0) - .orderByAsc(Comment::getCreateTime); - return this.list(wrapper); - } - - @Override - public List getByLikesRange(Integer minLikes, Integer maxLikes) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.between(Comment::getLikes, minLikes, maxLikes) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getLikes); - return this.list(wrapper); - } - - @Override - public List getByTimeRange(LocalDateTime startTime, LocalDateTime endTime) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.between(Comment::getCreateTime, startTime, endTime) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime); - return this.list(wrapper); - } - - @Override - public Long countByPostId(String postId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .eq(Comment::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countByUserId(String userId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getUserId, userId) - .eq(Comment::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countRepliesByCommentId(String commentId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getReplyToId, commentId) - .eq(Comment::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countTopLevelCommentsByPostId(String postId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .isNull(Comment::getReplyToId) - .eq(Comment::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public List getMostLikedCommentsByPostId(String postId, Integer limit) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getLikes) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public List getLatestCommentsByPostId(String postId, Integer limit) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public List getRecentByUserId(String userId, Integer limit) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getUserId, userId) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public List getPopularCommentsByPostId(String postId, Integer limit) { - // 这里需要自定义SQL查询,暂时返回最新评论 - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public List searchByKeyword(String keyword) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.like(Comment::getContent, keyword) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime); - return this.list(wrapper); - } - - @Override - public List searchByPostIdAndKeyword(String postId, String keyword) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .like(Comment::getContent, keyword) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime); - return this.list(wrapper); - } - - @Override - public List getByPostIdAndUserId(String postId, String userId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Comment::getPostId, postId) - .eq(Comment::getUserId, userId) - .eq(Comment::getIsDeleted, 0) - .orderByDesc(Comment::getCreateTime); - return this.list(wrapper); - } - - @Override - public boolean updateLikes(String id, Integer increment) { - Comment comment = this.getById(id); + public boolean delete(String id) { + Comment comment = this.getBaseMapper().selectById(id); if (comment == null) { return false; } - Integer newLikes = comment.getLikes() + increment; - comment.setLikes(newLikes); + // 逻辑删除 + comment.setIsDeleted(1); return this.updateById(comment); } - @Override - public Comment createComment(String postId, String userId, String content, String replyToId) { - Comment comment = new Comment(); - comment.setPostId(postId); - comment.setUserId(userId); - comment.setContent(content); - comment.setReplyToId(replyToId); - comment.setLikes(0); + /** + * 转换为响应对象 + */ + private CommentResponse convertToResponse(Comment comment) { + CommentResponse response = new CommentResponse(); + BeanUtils.copyProperties(comment, response); + response.setId(comment.getId()); + if (comment.getCreateTime() != null) { + response.setCreateTime(comment.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (comment.getUpdateTime() != null) { + response.setUpdateTime(comment.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + return response; + } - this.save(comment); - return comment; + /** + * 转换分页对象为PageResult对象 + */ + private PageResult convertPageToPageResult(Page page) { + PageResult pageResult = new PageResult<>(); + pageResult.setCurrent(page.getCurrent()); + pageResult.setSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setRecords(page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList())); + return pageResult; } } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/CommunityPostServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/CommunityPostServiceImpl.java index 1e8698a..6f32ded 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/CommunityPostServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/CommunityPostServiceImpl.java @@ -1,18 +1,26 @@ package com.emotion.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.community.CommunityPostCreateRequest; +import com.emotion.dto.request.community.CommunityPostUpdateRequest; +import com.emotion.dto.request.community.CommunityPostPageRequest; +import com.emotion.dto.response.community.CommunityPostResponse; import com.emotion.entity.CommunityPost; import com.emotion.mapper.CommunityPostMapper; import com.emotion.service.CommunityPostService; +import com.emotion.util.SnowflakeIdGenerator; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.stream.Collectors; /** * 社区帖子服务实现类 @@ -23,296 +31,149 @@ import java.util.List; @Service public class CommunityPostServiceImpl extends ServiceImpl implements CommunityPostService { + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @Override - public IPage getPage(BasePageRequest request) { + public PageResult getPage(CommunityPostPageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 根据请求参数构建查询条件 + if (StringUtils.hasText(request.getUserId())) { + wrapper.eq(CommunityPost::getUserId, request.getUserId()); + } + + if (StringUtils.hasText(request.getType())) { + wrapper.eq(CommunityPost::getType, request.getType()); + } + + if (StringUtils.hasText(request.getLocationId())) { + wrapper.eq(CommunityPost::getLocationId, request.getLocationId()); + } + + if (request.getPublicOnly() != null && request.getPublicOnly()) { + wrapper.eq(CommunityPost::getIsPrivate, 0); + } + if (StringUtils.hasText(request.getKeyword())) { wrapper.and(w -> w.like(CommunityPost::getTitle, request.getKeyword()) .or().like(CommunityPost::getContent, request.getKeyword())); } - + wrapper.eq(CommunityPost::getIsDeleted, 0).orderByDesc(CommunityPost::getCreateTime); - return this.page(page, wrapper); + page = this.page(page, wrapper); + + return convertPageToResponse(page); } @Override - public IPage getPublicPostsPage(BasePageRequest request) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - - if (StringUtils.hasText(request.getKeyword())) { - wrapper.and(w -> w.like(CommunityPost::getTitle, request.getKeyword()) - .or().like(CommunityPost::getContent, request.getKeyword())); - } - - wrapper.eq(CommunityPost::getIsPrivate, 0) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime); - return this.page(page, wrapper); - } - - @Override - public IPage getPageByUserId(BasePageRequest request, String userId) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getUserId, userId) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime); - return this.page(page, wrapper); - } - - @Override - public List getByLocationId(String locationId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getLocationId, locationId) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime); - return this.list(wrapper); - } - - @Override - public List getByType(String type) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getType, type) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime); - return this.list(wrapper); - } - - @Override - public List getPrivatePostsByUserId(String userId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getUserId, userId) - .eq(CommunityPost::getIsPrivate, 1) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime); - return this.list(wrapper); - } - - @Override - public List getByLikesRange(Integer minLikes, Integer maxLikes) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.between(CommunityPost::getLikes, minLikes, maxLikes) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getLikes); - return this.list(wrapper); - } - - @Override - public List getByViewRange(Integer minViews, Integer maxViews) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.between(CommunityPost::getViewCount, minViews, maxViews) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getViewCount); - return this.list(wrapper); - } - - @Override - public List getByCommentRange(Integer minComments, Integer maxComments) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.between(CommunityPost::getCommentCount, minComments, maxComments) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCommentCount); - return this.list(wrapper); - } - - @Override - public List getByTimeRange(LocalDateTime startTime, LocalDateTime endTime) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.between(CommunityPost::getCreateTime, startTime, endTime) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime); - return this.list(wrapper); - } - - @Override - public Long countByUserId(String userId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getUserId, userId) - .eq(CommunityPost::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countPublicPostsByUserId(String userId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getUserId, userId) - .eq(CommunityPost::getIsPrivate, 0) - .eq(CommunityPost::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countPrivatePostsByUserId(String userId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getUserId, userId) - .eq(CommunityPost::getIsPrivate, 1) - .eq(CommunityPost::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countByType(String type) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getType, type) - .eq(CommunityPost::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countByLocationId(String locationId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getLocationId, locationId) - .eq(CommunityPost::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public List getMostLikedPosts(Integer limit) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getLikes) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public List getMostViewedPosts(Integer limit) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getViewCount) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public List getLatestPosts(Integer limit) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public List getPopularPosts(Integer limit) { - // 这里需要自定义SQL查询,暂时返回最新帖子 - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public List getByTag(String tag) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.like(CommunityPost::getTags, tag) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime); - return this.list(wrapper); - } - - @Override - public List searchByKeyword(String keyword) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.and(w -> w.like(CommunityPost::getTitle, keyword) - .or().like(CommunityPost::getContent, keyword)) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime); - return this.list(wrapper); - } - - @Override - public List getRecentByUserId(String userId, Integer limit) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getUserId, userId) - .eq(CommunityPost::getIsDeleted, 0) - .orderByDesc(CommunityPost::getCreateTime) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public boolean updateLikes(String id, Integer increment) { - CommunityPost post = this.getById(id); + public CommunityPostResponse getById(String id) { + CommunityPost post = this.getBaseMapper().selectById(id); if (post == null) { - return false; + return null; } + // 增加浏览数 + Integer viewCount = post.getViewCount() == null ? 1 : post.getViewCount() + 1; + post.setViewCount(viewCount); + this.updateById(post); - Integer newLikes = post.getLikes() + increment; - post.setLikes(newLikes); - return this.updateById(post); + return convertToResponse(post); } @Override - public boolean incrementViewCount(String id) { - CommunityPost post = this.getById(id); - if (post == null) { - return false; - } - - Integer newViewCount = post.getViewCount() + 1; - post.setViewCount(newViewCount); - return this.updateById(post); - } - - @Override - public boolean updateCommentCount(String id, Integer increment) { - CommunityPost post = this.getById(id); - if (post == null) { - return false; - } - - Integer newCommentCount = post.getCommentCount() + increment; - post.setCommentCount(newCommentCount); - return this.updateById(post); - } - - @Override - public boolean updatePrivacyStatus(String id, Integer isPrivate) { + public CommunityPostResponse create(CommunityPostCreateRequest request) { CommunityPost post = new CommunityPost(); - post.setId(id); - post.setIsPrivate(isPrivate); - return this.updateById(post); - } - - @Override - public List getRecommendedPosts(String type, String locationId, Integer limit) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CommunityPost::getIsDeleted, 0); - - if (StringUtils.hasText(type)) { - wrapper.eq(CommunityPost::getType, type); - } - - if (StringUtils.hasText(locationId)) { - wrapper.eq(CommunityPost::getLocationId, locationId); - } - - wrapper.orderByDesc(CommunityPost::getCreateTime) - .last("LIMIT " + limit); - return this.list(wrapper); - } - - @Override - public CommunityPost createPost(String userId, String title, String content, String type, - String locationId, String tags, Integer isPrivate) { - CommunityPost post = new CommunityPost(); - post.setUserId(userId); - post.setTitle(title); - post.setContent(content); - post.setType(type); - post.setLocationId(locationId); - post.setTags(tags); - post.setIsPrivate(isPrivate); + post.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID + post.setUserId(request.getUserId()); + post.setTitle(request.getTitle()); + post.setContent(request.getContent()); + post.setType(request.getType()); + post.setLocationId(request.getLocationId()); + post.setTags(request.getTags()); + post.setIsPrivate(request.getIsPrivate() != null ? request.getIsPrivate() : 0); // 默认公开 post.setLikes(0); post.setViewCount(0); post.setCommentCount(0); this.save(post); - return post; + return convertToResponse(post); + } + + @Override + public CommunityPostResponse update(CommunityPostUpdateRequest request) { + CommunityPost post = this.getBaseMapper().selectById(request.getId()); + if (post == null) { + return null; + } + + // 只更新非空字段 + if (request.getTitle() != null) { + post.setTitle(request.getTitle()); + } + if (request.getContent() != null) { + post.setContent(request.getContent()); + } + if (request.getType() != null) { + post.setType(request.getType()); + } + if (request.getLocationId() != null) { + post.setLocationId(request.getLocationId()); + } + if (request.getTags() != null) { + post.setTags(request.getTags()); + } + if (request.getIsPrivate() != null) { + post.setIsPrivate(request.getIsPrivate()); + } + + this.updateById(post); + return convertToResponse(post); + } + + @Override + public boolean delete(String id) { + CommunityPost post = this.getBaseMapper().selectById(id); + if (post == null) { + return false; + } + + // 逻辑删除 + post.setIsDeleted(1); + return this.updateById(post); + } + + /** + * 转换为响应对象 + */ + private CommunityPostResponse convertToResponse(CommunityPost post) { + CommunityPostResponse response = new CommunityPostResponse(); + BeanUtils.copyProperties(post, response); + response.setId(post.getId()); + if (post.getCreateTime() != null) { + response.setCreateTime(post.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (post.getUpdateTime() != null) { + response.setUpdateTime(post.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + return response; + } + + /** + * 转换分页对象为响应对象 + */ + private PageResult convertPageToResponse(Page page) { + PageResult responsePage = new PageResult<>(); + responsePage.setCurrent(page.getCurrent()); + responsePage.setSize(page.getSize()); + responsePage.setTotal(page.getTotal()); + responsePage.setPages(page.getPages()); + responsePage.setRecords( + page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList())); + return responsePage; } } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/ConversationServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/ConversationServiceImpl.java index 42e8d4b..54c7b01 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/ConversationServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/ConversationServiceImpl.java @@ -4,15 +4,25 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.ConversationCreateRequest; +import com.emotion.dto.request.ConversationPageRequest; +import com.emotion.dto.response.ConversationResponse; import com.emotion.entity.Conversation; import com.emotion.mapper.ConversationMapper; import com.emotion.service.ConversationService; +import com.emotion.util.SnowflakeIdGenerator; +import com.emotion.util.UserContextUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import javax.servlet.http.HttpServletRequest; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.stream.Collectors; /** * 会话服务实现类 @@ -23,11 +33,29 @@ import java.util.List; @Service public class ConversationServiceImpl extends ServiceImpl implements ConversationService { + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @Override - public IPage getPage(BasePageRequest request) { + public IPage getPage(ConversationPageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 根据请求参数构建查询条件 + if (StringUtils.hasText(request.getUserId())) { + wrapper.eq(Conversation::getUserId, request.getUserId()); + } + + if (StringUtils.hasText(request.getStatus())) { + wrapper.eq(Conversation::getConversationStatus, request.getStatus()); + } + + if (StringUtils.hasText(request.getType())) { + wrapper.eq(Conversation::getType, request.getType()); + } + if (StringUtils.hasText(request.getKeyword())) { wrapper.and(w -> w.like(Conversation::getTitle, request.getKeyword()) .or().like(Conversation::getSummary, request.getKeyword())); @@ -38,10 +66,16 @@ public class ConversationServiceImpl extends ServiceImpl getPageByUserId(BasePageRequest request, String userId) { + public IPage getPageByUserId(ConversationPageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(Conversation::getUserId, userId) + + // 必须提供userId参数 + if (!StringUtils.hasText(request.getUserId())) { + throw new IllegalArgumentException("userId不能为空"); + } + + wrapper.eq(Conversation::getUserId, request.getUserId()) .eq(Conversation::getIsDeleted, 0) .orderByDesc(Conversation::getCreateTime); return this.page(page, wrapper); @@ -148,6 +182,7 @@ public class ConversationServiceImpl extends ServiceImpl getPageWithResponse(ConversationPageRequest request) { + IPage page = this.getPage(request); + return convertPageToResponse(page); + } + + @Override + public PageResult getPageByUserIdWithResponse(ConversationPageRequest request) { + IPage page = this.getPageByUserId(request); + return convertPageToResponse(page); + } + + @Override + public ConversationResponse getConversationResponseById(String id) { + Conversation conversation = this.getById(id); + if (conversation == null) { + return null; + } + return convertToResponse(conversation); + } + + @Override + public List getByUserIdWithResponse(String userId) { + List conversations = this.getByUserId(userId); + return conversations.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getActiveConversationsWithResponse() { + // 实现获取活跃对话的逻辑 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Conversation::getConversationStatus, "active") + .eq(Conversation::getIsDeleted, 0) + .orderByDesc(Conversation::getLastActiveTime); + List conversations = this.list(wrapper); + return conversations.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getArchivedConversationsWithResponse() { + // 实现获取归档对话的逻辑 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Conversation::getConversationStatus, "archived") + .eq(Conversation::getIsDeleted, 0) + .orderByDesc(Conversation::getCreateTime); + List conversations = this.list(wrapper); + return conversations.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public boolean archiveConversation(String id) { + Conversation conversation = new Conversation(); + conversation.setId(id); + conversation.setConversationStatus("archived"); + return this.updateById(conversation); + } + + @Override + public boolean activateConversation(String id) { + Conversation conversation = new Conversation(); + conversation.setId(id); + conversation.setConversationStatus("active"); + return this.updateById(conversation); + } + + @Override + public ConversationResponse createConversationWithResponse(ConversationCreateRequest request, + HttpServletRequest httpRequest) { + // 获取客户端IP地址 + String clientIp = UserContextUtils.getClientIpAddress(httpRequest); + + Conversation conversation = this.createConversation( + request.getUserId(), + request.getTitle(), + null // cozeConversationId + ); + + // 设置客户端IP + conversation.setClientIp(clientIp); + this.updateById(conversation); + + return convertToResponse(conversation); + } + + @Override + public ConversationResponse updateConversationWithResponse(ConversationCreateRequest request) { + Conversation conversation = this.getById(request.getId()); // 修复:使用ID而不是userId + if (conversation == null) { + return null; + } + + // 更新字段 + if (request.getTitle() != null) { + conversation.setTitle(request.getTitle()); + } + if (request.getType() != null) { + conversation.setType(request.getType()); + } + + this.updateById(conversation); + return convertToResponse(conversation); + } + + @Override + public boolean updateConversationStatus(String id, String status) { + Conversation conversation = new Conversation(); + conversation.setId(id); + conversation.setConversationStatus(status); + return this.updateById(conversation); + } + + /** + * 转换为响应对象 + */ + private ConversationResponse convertToResponse(Conversation conversation) { + ConversationResponse response = new ConversationResponse(); + BeanUtils.copyProperties(conversation, response); + response.setId(conversation.getId()); + response.setStatus(conversation.getConversationStatus()); + if (conversation.getCreateTime() != null) { + response.setCreateTime(conversation.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (conversation.getUpdateTime() != null) { + response.setUpdateTime(conversation.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + if (conversation.getLastActiveTime() != null) { + response.setLastMessageTime(conversation.getLastActiveTime().format(DATE_TIME_FORMATTER)); + } + return response; + } + + /** + * 转换分页对象为响应对象 + */ + private PageResult convertPageToResponse(IPage page) { + PageResult responsePage = new PageResult<>(); + responsePage.setCurrent(page.getCurrent()); + responsePage.setSize(page.getSize()); + responsePage.setTotal(page.getTotal()); + responsePage.setPages(page.getPages()); + responsePage.setRecords( + page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList())); + return responsePage; + } } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/CozeApiCallServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/CozeApiCallServiceImpl.java index e41d906..96bf003 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/CozeApiCallServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/CozeApiCallServiceImpl.java @@ -1,20 +1,27 @@ package com.emotion.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.coze.CozeApiCallPageRequest; +import com.emotion.dto.request.coze.CozeApiCallCreateRequest; +import com.emotion.dto.request.coze.CozeApiCallUpdateRequest; +import com.emotion.dto.response.coze.CozeApiCallResponse; import com.emotion.entity.CozeApiCall; import com.emotion.mapper.CozeApiCallMapper; import com.emotion.service.CozeApiCallService; +import com.emotion.util.SnowflakeIdGenerator; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.stream.Collectors; /** * Coze API调用记录服务实现类 @@ -25,15 +32,46 @@ import java.util.List; @Service public class CozeApiCallServiceImpl extends ServiceImpl implements CozeApiCallService { + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @Override - public IPage getPage(BasePageRequest request) { + public PageResult getPage(CozeApiCallPageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 根据请求参数构建查询条件 + if (StringUtils.hasText(request.getConversationId())) { + wrapper.eq(CozeApiCall::getConversationId, request.getConversationId()); + } + + if (StringUtils.hasText(request.getUserId())) { + wrapper.eq(CozeApiCall::getUserId, request.getUserId()); + } + + if (StringUtils.hasText(request.getBotId())) { + wrapper.eq(CozeApiCall::getBotId, request.getBotId()); + } + + if (StringUtils.hasText(request.getStatus())) { + wrapper.eq(CozeApiCall::getStatus, request.getStatus()); + } + + if (StringUtils.hasText(request.getRequestType())) { + wrapper.eq(CozeApiCall::getRequestType, request.getRequestType()); + } + + if (StringUtils.hasText(request.getTraceId())) { + wrapper.eq(CozeApiCall::getTraceId, request.getTraceId()); + } + // 关键词搜索 if (StringUtils.hasText(request.getKeyword())) { - wrapper.like(CozeApiCall::getRequestUrl, request.getKeyword()) - .or().like(CozeApiCall::getAiReply, request.getKeyword()); + wrapper.and(w -> w.like(CozeApiCall::getRequestUrl, request.getKeyword()) + .or().like(CozeApiCall::getAiReply, request.getKeyword()) + .or().like(CozeApiCall::getUserMessage, request.getKeyword())); } wrapper.eq(CozeApiCall::getIsDeleted, 0); @@ -49,77 +87,193 @@ public class CozeApiCallServiceImpl extends ServiceImpl getPageByConversationId(BasePageRequest request, String conversationId) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CozeApiCall::getConversationId, conversationId) - .eq(CozeApiCall::getIsDeleted, 0); + public CozeApiCallResponse getById(String id) { + CozeApiCall apiCall = this.getBaseMapper().selectById(id); + if (apiCall == null || apiCall.getIsDeleted() == 1) { + return null; + } + return convertToResponse(apiCall); + } - // 关键词搜索 - if (StringUtils.hasText(request.getKeyword())) { - wrapper.like(CozeApiCall::getRequestUrl, request.getKeyword()) - .or().like(CozeApiCall::getAiReply, request.getKeyword()); + @Override + public CozeApiCallResponse create(CozeApiCallCreateRequest request) { + CozeApiCall apiCall = new CozeApiCall(); + apiCall.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID + + // 复制属性 + apiCall.setConversationId(request.getConversationId()); + apiCall.setMessageId(request.getMessageId()); + apiCall.setCozeChatId(request.getCozeChatId()); + apiCall.setCozeConversationId(request.getCozeConversationId()); + apiCall.setBotId(request.getBotId()); + apiCall.setWorkflowId(request.getWorkflowId()); + apiCall.setUserId(request.getUserId()); + apiCall.setRequestType(request.getRequestType()); + apiCall.setRequestUrl(request.getRequestUrl()); + apiCall.setRequestBody(request.getRequestBody()); + apiCall.setRequestHeaders(request.getRequestHeaders()); + apiCall.setUserMessage(request.getUserMessage()); + apiCall.setUserMessageType(request.getUserMessageType()); + apiCall.setAiReply(request.getAiReply()); + apiCall.setAiReplyType(request.getAiReplyType()); + apiCall.setResponseStatus(request.getResponseStatus()); + apiCall.setResponseBody(request.getResponseBody()); + apiCall.setResponseHeaders(request.getResponseHeaders()); + apiCall.setPollCount(request.getPollCount()); + apiCall.setFinalStatus(request.getFinalStatus()); + apiCall.setStatus(request.getStatus()); + apiCall.setDurationMs(request.getDurationMs()); + apiCall.setPromptTokens(request.getPromptTokens()); + apiCall.setCompletionTokens(request.getCompletionTokens()); + apiCall.setTotalTokens(request.getTotalTokens()); + apiCall.setCost(request.getCost()); + apiCall.setFunctionCalls(request.getFunctionCalls()); + apiCall.setFunctionResults(request.getFunctionResults()); + apiCall.setErrorCode(request.getErrorCode()); + apiCall.setErrorMessage(request.getErrorMessage()); + apiCall.setClientIp(request.getClientIp()); + apiCall.setUserAgent(request.getUserAgent()); + apiCall.setSessionId(request.getSessionId()); + apiCall.setTraceId(request.getTraceId()); + apiCall.setMetadata(request.getMetadata()); + + this.save(apiCall); + return convertToResponse(apiCall); + } + + @Override + public CozeApiCallResponse update(CozeApiCallUpdateRequest request) { + CozeApiCall apiCall = this.getBaseMapper().selectById(request.getId()); + if (apiCall == null || apiCall.getIsDeleted() == 1) { + return null; } - wrapper.orderByDesc(CozeApiCall::getStartTime); - return this.page(page, wrapper); - } - - @Override - public IPage getPageByUserId(BasePageRequest request, String userId) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CozeApiCall::getUserId, userId) - .eq(CozeApiCall::getIsDeleted, 0); - - // 关键词搜索 - if (StringUtils.hasText(request.getKeyword())) { - wrapper.like(CozeApiCall::getRequestUrl, request.getKeyword()) - .or().like(CozeApiCall::getAiReply, request.getKeyword()); + // 只更新非空字段 + if (request.getConversationId() != null) { + apiCall.setConversationId(request.getConversationId()); + } + if (request.getMessageId() != null) { + apiCall.setMessageId(request.getMessageId()); + } + if (request.getCozeChatId() != null) { + apiCall.setCozeChatId(request.getCozeChatId()); + } + if (request.getCozeConversationId() != null) { + apiCall.setCozeConversationId(request.getCozeConversationId()); + } + if (request.getBotId() != null) { + apiCall.setBotId(request.getBotId()); + } + if (request.getWorkflowId() != null) { + apiCall.setWorkflowId(request.getWorkflowId()); + } + if (request.getUserId() != null) { + apiCall.setUserId(request.getUserId()); + } + if (request.getRequestType() != null) { + apiCall.setRequestType(request.getRequestType()); + } + if (request.getRequestUrl() != null) { + apiCall.setRequestUrl(request.getRequestUrl()); + } + if (request.getRequestBody() != null) { + apiCall.setRequestBody(request.getRequestBody()); + } + if (request.getRequestHeaders() != null) { + apiCall.setRequestHeaders(request.getRequestHeaders()); + } + if (request.getUserMessage() != null) { + apiCall.setUserMessage(request.getUserMessage()); + } + if (request.getUserMessageType() != null) { + apiCall.setUserMessageType(request.getUserMessageType()); + } + if (request.getAiReply() != null) { + apiCall.setAiReply(request.getAiReply()); + } + if (request.getAiReplyType() != null) { + apiCall.setAiReplyType(request.getAiReplyType()); + } + if (request.getResponseStatus() != null) { + apiCall.setResponseStatus(request.getResponseStatus()); + } + if (request.getResponseBody() != null) { + apiCall.setResponseBody(request.getResponseBody()); + } + if (request.getResponseHeaders() != null) { + apiCall.setResponseHeaders(request.getResponseHeaders()); + } + if (request.getPollCount() != null) { + apiCall.setPollCount(request.getPollCount()); + } + if (request.getFinalStatus() != null) { + apiCall.setFinalStatus(request.getFinalStatus()); + } + if (request.getStatus() != null) { + apiCall.setStatus(request.getStatus()); + } + if (request.getDurationMs() != null) { + apiCall.setDurationMs(request.getDurationMs()); + } + if (request.getPromptTokens() != null) { + apiCall.setPromptTokens(request.getPromptTokens()); + } + if (request.getCompletionTokens() != null) { + apiCall.setCompletionTokens(request.getCompletionTokens()); + } + if (request.getTotalTokens() != null) { + apiCall.setTotalTokens(request.getTotalTokens()); + } + if (request.getCost() != null) { + apiCall.setCost(request.getCost()); + } + if (request.getFunctionCalls() != null) { + apiCall.setFunctionCalls(request.getFunctionCalls()); + } + if (request.getFunctionResults() != null) { + apiCall.setFunctionResults(request.getFunctionResults()); + } + if (request.getErrorCode() != null) { + apiCall.setErrorCode(request.getErrorCode()); + } + if (request.getErrorMessage() != null) { + apiCall.setErrorMessage(request.getErrorMessage()); + } + if (request.getClientIp() != null) { + apiCall.setClientIp(request.getClientIp()); + } + if (request.getUserAgent() != null) { + apiCall.setUserAgent(request.getUserAgent()); + } + if (request.getSessionId() != null) { + apiCall.setSessionId(request.getSessionId()); + } + if (request.getTraceId() != null) { + apiCall.setTraceId(request.getTraceId()); + } + if (request.getMetadata() != null) { + apiCall.setMetadata(request.getMetadata()); } - wrapper.orderByDesc(CozeApiCall::getStartTime); - return this.page(page, wrapper); + this.updateById(apiCall); + return convertToResponse(apiCall); } @Override - public List getByBotId(String botId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CozeApiCall::getBotId, botId) - .eq(CozeApiCall::getIsDeleted, 0) - .orderByDesc(CozeApiCall::getStartTime); - return this.list(wrapper); - } + public boolean delete(String id) { + CozeApiCall apiCall = this.getBaseMapper().selectById(id); + if (apiCall == null) { + return false; + } - @Override - public List getByStatus(String status) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CozeApiCall::getStatus, status) - .eq(CozeApiCall::getIsDeleted, 0) - .orderByDesc(CozeApiCall::getStartTime); - return this.list(wrapper); - } - - @Override - public List getByRequestType(String requestType) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CozeApiCall::getRequestType, requestType) - .eq(CozeApiCall::getIsDeleted, 0) - .orderByDesc(CozeApiCall::getStartTime); - return this.list(wrapper); - } - - @Override - public List getByTimeRange(LocalDateTime startTime, LocalDateTime endTime) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.between(CozeApiCall::getStartTime, startTime, endTime) - .eq(CozeApiCall::getIsDeleted, 0) - .orderByDesc(CozeApiCall::getStartTime); - return this.list(wrapper); + // 逻辑删除 + apiCall.setIsDeleted(1); + return this.updateById(apiCall); } @Override @@ -167,87 +321,46 @@ public class CozeApiCallServiceImpl extends ServiceImpl getFailedCalls() { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.and(w -> w.eq(CozeApiCall::getStatus, "failed").or().eq(CozeApiCall::getFinalStatus, "failed")) - .eq(CozeApiCall::getIsDeleted, 0) - .orderByDesc(CozeApiCall::getStartTime); - return this.list(wrapper); + /** + * 转换为响应对象 + */ + private CozeApiCallResponse convertToResponse(CozeApiCall apiCall) { + CozeApiCallResponse response = new CozeApiCallResponse(); + BeanUtils.copyProperties(apiCall, response); + response.setId(apiCall.getId()); + if (apiCall.getCreateTime() != null) { + response.setCreateTime(apiCall.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (apiCall.getUpdateTime() != null) { + response.setUpdateTime(apiCall.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + if (apiCall.getStartTime() != null) { + response.setStartTime(apiCall.getStartTime().format(DATE_TIME_FORMATTER)); + } + if (apiCall.getEndTime() != null) { + response.setEndTime(apiCall.getEndTime().format(DATE_TIME_FORMATTER)); + } + if (apiCall.getPollStartTime() != null) { + response.setPollStartTime(apiCall.getPollStartTime().format(DATE_TIME_FORMATTER)); + } + if (apiCall.getPollEndTime() != null) { + response.setPollEndTime(apiCall.getPollEndTime().format(DATE_TIME_FORMATTER)); + } + return response; } - @Override - public List getTimeoutCalls() { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.and(w -> w.eq(CozeApiCall::getStatus, "timeout").or().eq(CozeApiCall::getFinalStatus, "timeout")) - .eq(CozeApiCall::getIsDeleted, 0) - .orderByDesc(CozeApiCall::getStartTime); - return this.list(wrapper); + /** + * 转换分页对象为PageResult对象 + */ + private PageResult convertPageToPageResult(Page page) { + PageResult pageResult = new PageResult<>(); + pageResult.setCurrent(page.getCurrent()); + pageResult.setSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setRecords(page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList())); + return pageResult; } - - @Override - public CozeApiCall getByTraceId(String traceId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CozeApiCall::getTraceId, traceId) - .eq(CozeApiCall::getIsDeleted, 0); - return this.getOne(wrapper); - } - - @Override - public List getByConversationIdAndRequestType(String conversationId, String requestType) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CozeApiCall::getConversationId, conversationId) - .eq(CozeApiCall::getRequestType, requestType) - .eq(CozeApiCall::getIsDeleted, 0) - .orderByDesc(CozeApiCall::getStartTime); - return this.list(wrapper); - } - - @Override - public boolean updateStatus(String id, String status, String finalStatus, LocalDateTime endTime) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(CozeApiCall::getId, id) - .set(CozeApiCall::getStatus, status) - .set(CozeApiCall::getFinalStatus, finalStatus) - .set(CozeApiCall::getEndTime, endTime) - .set(CozeApiCall::getUpdateTime, LocalDateTime.now()); - return this.update(wrapper); - } - - @Override - public boolean updateResult(String id, Integer responseStatus, String responseBody, String aiReply, - Integer totalTokens, BigDecimal cost, String status, String finalStatus, - LocalDateTime endTime) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(CozeApiCall::getId, id) - .set(CozeApiCall::getResponseStatus, responseStatus) - .set(CozeApiCall::getResponseBody, responseBody) - .set(CozeApiCall::getAiReply, aiReply) - .set(CozeApiCall::getTotalTokens, totalTokens) - .set(CozeApiCall::getCost, cost) - .set(CozeApiCall::getStatus, status) - .set(CozeApiCall::getFinalStatus, finalStatus) - .set(CozeApiCall::getEndTime, endTime) - .set(CozeApiCall::getUpdateTime, LocalDateTime.now()); - return this.update(wrapper); - } - - @Override - public CozeApiCall createApiCall(String conversationId, String messageId, String userId, - String requestType, String requestUrl, String requestBody) { - CozeApiCall apiCall = CozeApiCall.builder() - .conversationId(conversationId) - .messageId(messageId) - .userId(userId) - .requestType(requestType) - .requestUrl(requestUrl) - .requestBody(requestBody) - .status("pending") - .startTime(LocalDateTime.now()) - .createBy(userId) // 设置创建人为当前用户 - .updateBy(userId) // 设置更新人为当前用户 - .build(); - this.save(apiCall); - return apiCall; - } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/DiaryCommentServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/DiaryCommentServiceImpl.java index fd99108..635adf6 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/DiaryCommentServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/DiaryCommentServiceImpl.java @@ -5,18 +5,24 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.DiaryCommentCreateRequest; +import com.emotion.dto.request.DiaryCommentPageRequest; +import com.emotion.dto.response.DiaryCommentResponse; import com.emotion.entity.DiaryComment; import com.emotion.mapper.DiaryCommentMapper; import com.emotion.service.DiaryCommentService; +import com.emotion.util.SnowflakeIdGenerator; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import java.math.BigDecimal; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.stream.Collectors; @@ -29,207 +35,105 @@ import java.util.stream.Collectors; @Service public class DiaryCommentServiceImpl extends ServiceImpl implements DiaryCommentService { + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + @Autowired private ObjectMapper objectMapper; + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @Override - public IPage getPage(BasePageRequest request) { + public PageResult getPageWithResponse(DiaryCommentPageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getIsDeleted, 0) - .orderByDesc(DiaryComment::getIsTop) + + // 基础查询条件 + wrapper.eq(DiaryComment::getIsDeleted, 0); + + // 根据请求参数添加查询条件 + if (StringUtils.hasText(request.getDiaryId())) { + wrapper.eq(DiaryComment::getDiaryId, request.getDiaryId()); + } + if (StringUtils.hasText(request.getUserId())) { + wrapper.eq(DiaryComment::getUserId, request.getUserId()); + } + if (StringUtils.hasText(request.getCommentType())) { + wrapper.eq(DiaryComment::getCommentType, request.getCommentType()); + } + if (request.getTopLevelOnly() != null && request.getTopLevelOnly()) { + wrapper.isNull(DiaryComment::getParentCommentId); + } + + // 排序 + wrapper.orderByDesc(DiaryComment::getIsTop) .orderByDesc(DiaryComment::getPublishTime); - return this.page(page, wrapper); + + IPage resultPage = this.page(page, wrapper); + return convertPageToResponse(resultPage); } @Override - public IPage getPageByDiaryId(String diaryId, BasePageRequest request) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getDiaryId, diaryId) - .eq(DiaryComment::getIsDeleted, 0) - .orderByDesc(DiaryComment::getIsTop) - .orderByDesc(DiaryComment::getPublishTime); - return this.page(page, wrapper); + public DiaryCommentResponse getCommentResponseById(String id) { + DiaryComment comment = this.getById(id); + if (comment == null) { + return null; + } + return convertToResponse(comment); } @Override - public IPage getPageByUserId(String userId, BasePageRequest request) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getUserId, userId) - .eq(DiaryComment::getIsDeleted, 0) - .orderByDesc(DiaryComment::getPublishTime); - return this.page(page, wrapper); + public List getCommentTreeWithResponse(String diaryId) { + List comments = this.getCommentTree(diaryId); + return comments.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); } @Override - public List getRepliesByParentId(String parentCommentId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getParentCommentId, parentCommentId) - .eq(DiaryComment::getIsDeleted, 0) - .orderByAsc(DiaryComment::getPublishTime); - return this.list(wrapper); - } - - @Override - public List getByCommentType(String commentType) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getCommentType, commentType) - .eq(DiaryComment::getIsDeleted, 0) - .orderByDesc(DiaryComment::getPublishTime); - return this.list(wrapper); - } - - @Override - public List getByDiaryIdAndCommentType(String diaryId, String commentType) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getDiaryId, diaryId) - .eq(DiaryComment::getCommentType, commentType) - .eq(DiaryComment::getIsDeleted, 0) - .orderByDesc(DiaryComment::getPublishTime); - return this.list(wrapper); - } - - @Override - public boolean incrementLikeCount(String commentId) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryComment::getId, commentId) - .setSql("like_count = like_count + 1"); - return this.update(wrapper); - } - - @Override - public boolean decrementLikeCount(String commentId) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryComment::getId, commentId) - .setSql("like_count = GREATEST(like_count - 1, 0)"); - return this.update(wrapper); - } - - @Override - public boolean incrementReplyCount(String commentId) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryComment::getId, commentId) - .setSql("reply_count = reply_count + 1"); - return this.update(wrapper); - } - - @Override - public boolean decrementReplyCount(String commentId) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryComment::getId, commentId) - .setSql("reply_count = GREATEST(reply_count - 1, 0)"); - return this.update(wrapper); - } - - @Override - public boolean updateLastReplyTime(String commentId) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryComment::getId, commentId) - .set(DiaryComment::getLastReplyTime, LocalDateTime.now()); - return this.update(wrapper); - } - - @Override - public boolean setTop(String commentId, Integer isTop) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryComment::getId, commentId) - .set(DiaryComment::getIsTop, isTop); - return this.update(wrapper); - } - - @Override - public Long countByDiaryId(String diaryId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getDiaryId, diaryId) - .eq(DiaryComment::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countByUserId(String userId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getUserId, userId) - .eq(DiaryComment::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countByCommentType(String commentType) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getCommentType, commentType) - .eq(DiaryComment::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public Long countReplies(String parentCommentId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryComment::getParentCommentId, parentCommentId) - .eq(DiaryComment::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public DiaryComment createComment(String diaryId, String userId, String content, List images, - String parentCommentId, Integer isAnonymous) { + public DiaryCommentResponse createCommentWithResponse(DiaryCommentCreateRequest request) { DiaryComment comment = DiaryComment.builder() - .diaryId(diaryId) - .userId(userId) - .content(content) - .images(convertListToJson(images)) - .parentCommentId(parentCommentId) + .id(snowflakeIdGenerator.nextIdAsString()) // 使用雪花算法生成ID + .diaryId(request.getDiaryId()) + .userId(request.getUserId()) + .content(request.getContent()) + .images(convertListToJson(request.getImages())) + .parentCommentId(request.getParentCommentId()) .commentType("user") .likeCount(0) .replyCount(0) - .isAnonymous(isAnonymous) + .isAnonymous(request.getIsAnonymous()) .isTop(0) .status("published") .publishTime(LocalDateTime.now()) .build(); - + this.save(comment); - + // 如果有父评论,更新父评论的回复数 - if (StringUtils.hasText(parentCommentId)) { - this.incrementReplyCount(parentCommentId); - this.updateLastReplyTime(parentCommentId); + if (StringUtils.hasText(request.getParentCommentId())) { + this.incrementReplyCount(request.getParentCommentId()); + this.updateLastReplyTime(request.getParentCommentId()); } - - return comment; + + return convertToResponse(comment); } @Override - public DiaryComment createAiComment(String diaryId, String content, String aiCommentSource, - BigDecimal emotionScore, BigDecimal sentimentScore) { - DiaryComment comment = DiaryComment.builder() - .diaryId(diaryId) - .userId("system") // AI评论使用system用户ID - .content(content) - .commentType("ai") - .aiCommentSource(aiCommentSource) - .likeCount(0) - .replyCount(0) - .isAnonymous(0) - .isTop(0) - .status("published") - .publishTime(LocalDateTime.now()) - .emotionScore(emotionScore) - .sentimentScore(sentimentScore) - .build(); - - this.save(comment); - return comment; - } - - @Override - public boolean updateComment(String commentId, String content, List images) { + public DiaryCommentResponse updateCommentWithResponse(DiaryCommentCreateRequest request) { LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryComment::getId, commentId) - .set(StringUtils.hasText(content), DiaryComment::getContent, content) - .set(images != null, DiaryComment::getImages, convertListToJson(images)); - return this.update(wrapper); + wrapper.eq(DiaryComment::getId, request.getId()) + .set(StringUtils.hasText(request.getContent()), DiaryComment::getContent, request.getContent()) + .set(request.getImages() != null, DiaryComment::getImages, convertListToJson(request.getImages())) + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); + + boolean updated = this.update(wrapper); + if (!updated) { + return null; + } + + DiaryComment updatedComment = this.getById(request.getId()); + return updatedComment != null ? convertToResponse(updatedComment) : null; } @Override @@ -241,7 +145,8 @@ public class DiaryCommentServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryComment::getId, commentId) - .set(DiaryComment::getIsDeleted, 1); + .set(DiaryComment::getIsDeleted, 1) + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -249,22 +154,52 @@ public class DiaryCommentServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryComment::getId, commentId) - .set(DiaryComment::getIsDeleted, 0); + .set(DiaryComment::getIsDeleted, 0) + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @Override - public List getCommentTree(String diaryId) { + public boolean incrementLikeCount(String commentId) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryComment::getId, commentId) + .setSql("like_count = like_count + 1") + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); + } + + @Override + public boolean decrementLikeCount(String commentId) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryComment::getId, commentId) + .setSql("like_count = GREATEST(like_count - 1, 0)") + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); + } + + @Override + public boolean setTop(String commentId, Integer isTop) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryComment::getId, commentId) + .set(DiaryComment::getIsTop, isTop) + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); + } + + /** + * 获取评论树结构 + */ + private List getCommentTree(String diaryId) { // 获取所有顶级评论(没有父评论的评论) LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(DiaryComment::getDiaryId, diaryId) - .isNull(DiaryComment::getParentCommentId) - .eq(DiaryComment::getIsDeleted, 0) - .orderByDesc(DiaryComment::getIsTop) - .orderByDesc(DiaryComment::getPublishTime); - + .isNull(DiaryComment::getParentCommentId) + .eq(DiaryComment::getIsDeleted, 0) + .orderByDesc(DiaryComment::getIsTop) + .orderByDesc(DiaryComment::getPublishTime); + List topComments = this.list(wrapper); - + // 为每个顶级评论加载回复 return topComments.stream() .peek(comment -> { @@ -274,6 +209,103 @@ public class DiaryCommentServiceImpl extends ServiceImpl getRepliesByParentId(String parentCommentId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(DiaryComment::getParentCommentId, parentCommentId) + .eq(DiaryComment::getIsDeleted, 0) + .orderByAsc(DiaryComment::getPublishTime); + return this.list(wrapper); + } + + /** + * 增加回复数 + */ + private boolean incrementReplyCount(String commentId) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryComment::getId, commentId) + .setSql("reply_count = reply_count + 1") + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); + } + + /** + * 减少回复数 + */ + private boolean decrementReplyCount(String commentId) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryComment::getId, commentId) + .setSql("reply_count = GREATEST(reply_count - 1, 0)") + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); + } + + /** + * 更新最后回复时间 + */ + private boolean updateLastReplyTime(String commentId) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryComment::getId, commentId) + .set(DiaryComment::getLastReplyTime, LocalDateTime.now()) + .set(DiaryComment::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); + } + + /** + * 转换为响应对象 + */ + private DiaryCommentResponse convertToResponse(DiaryComment comment) { + DiaryCommentResponse response = new DiaryCommentResponse(); + BeanUtils.copyProperties(comment, response); + + // 转换时间格式 + if (comment.getPublishTime() != null) { + response.setPublishTime(comment.getPublishTime().format(DATE_TIME_FORMATTER)); + } + if (comment.getLastReplyTime() != null) { + response.setLastReplyTime(comment.getLastReplyTime().format(DATE_TIME_FORMATTER)); + } + if (comment.getCreateTime() != null) { + response.setCreateTime(comment.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (comment.getUpdateTime() != null) { + response.setUpdateTime(comment.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + + // 转换JSON字段 + try { + if (comment.getImages() != null) { + response.setImages(objectMapper.readValue(comment.getImages(), new TypeReference>() { + })); + } + if (comment.getMetadata() != null) { + response.setMetadata(objectMapper.readValue(comment.getMetadata(), Object.class)); + } + } catch (JsonProcessingException e) { + // 忽略JSON解析错误 + } + + return response; + } + + /** + * 转换分页对象为响应对象 + */ + private PageResult convertPageToResponse(IPage page) { + PageResult responsePage = new PageResult<>(); + responsePage.setCurrent(page.getCurrent()); + responsePage.setSize(page.getSize()); + responsePage.setTotal(page.getTotal()); + responsePage.setPages(page.getPages()); + responsePage.setRecords( + page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList())); + return responsePage; + } + /** * 将List转换为JSON字符串 */ @@ -287,4 +319,4 @@ public class DiaryCommentServiceImpl extends ServiceImpl getPage(BasePageRequest request) { + public PageResult getPageWithResponse(DiaryPostPageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPriority) + + // 基础查询条件 + wrapper.eq(DiaryPost::getIsDeleted, 0); + + // 根据请求参数添加查询条件 + if (StringUtils.hasText(request.getUserId())) { + wrapper.eq(DiaryPost::getUserId, request.getUserId()); + } + if (Boolean.TRUE.equals(request.getPublicOnly())) { + wrapper.eq(DiaryPost::getIsPublic, 1) + .eq(DiaryPost::getStatus, "published"); + } + if (Boolean.TRUE.equals(request.getFeaturedOnly())) { + wrapper.eq(DiaryPost::getFeatured, 1) + .eq(DiaryPost::getIsPublic, 1) + .eq(DiaryPost::getStatus, "published"); + } + if (StringUtils.hasText(request.getMood())) { + wrapper.eq(DiaryPost::getMood, request.getMood()); + } + if (StringUtils.hasText(request.getTag())) { + // 标签查询可能需要特殊处理,因为tags字段是JSON格式 + wrapper.like(DiaryPost::getTags, request.getTag()); + } + + // 排序 + wrapper.orderByDesc(DiaryPost::getPriority) .orderByDesc(DiaryPost::getPublishTime); - return this.page(page, wrapper); + + IPage resultPage = this.page(page, wrapper); + List responses = resultPage.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + return createPageResult(resultPage, responses); } @Override - public IPage getPageByUserId(String userId, BasePageRequest request) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getUserId, userId) - .eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPriority) - .orderByDesc(DiaryPost::getPublishTime); - return this.page(page, wrapper); + public DiaryPostResponse getDiaryPostResponseById(String id) { + DiaryPost diaryPost = this.getById(id); + if (diaryPost == null) { + return null; + } + // 增加浏览数 + incrementViewCount(id); + return convertToResponse(diaryPost); } @Override - public IPage getPublicPageByUserId(String userId, BasePageRequest request) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getUserId, userId) - .eq(DiaryPost::getIsPublic, 1) - .eq(DiaryPost::getStatus, "published") - .eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPriority) - .orderByDesc(DiaryPost::getPublishTime); - return this.page(page, wrapper); + public DiaryPostResponse createDiaryPostWithResponse(DiaryPostCreateRequest request) { + // 处理标题:如果为空,则设置为null或生成默认标题 + String title = request.getTitle(); + if (title == null || title.trim().isEmpty()) { + // 可以选择设置为null,或者生成一个默认标题 + // 这里我们设置为null,让数据库使用默认值 + title = null; + } + + DiaryPost diaryPost = DiaryPost.builder() + .id(snowflakeIdGenerator.nextIdAsString()) // 使用雪花算法生成ID + .userId(request.getUserId()) + .title(title) + .content(request.getContent()) + .images(convertListToJson(request.getImages())) + .videos(convertListToJson(request.getVideos())) + .location(request.getLocation()) + .weather(request.getWeather()) + .mood(request.getMood()) + .tags(convertListToJson(request.getTags())) + .isPublic(request.getIsPublic()) + .isAnonymous(request.getIsAnonymous()) + .viewCount(0) + .likeCount(0) + .commentCount(0) + .shareCount(0) + .publishTime(LocalDateTime.now()) + .status("published") + .priority(0) + .featured(0) + .build(); + + this.save(diaryPost); + return convertToResponse(diaryPost); } @Override - public IPage getFeaturedPage(BasePageRequest request) { - Page page = new Page<>(request.getCurrent(), request.getSize()); - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getFeatured, 1) - .eq(DiaryPost::getIsPublic, 1) - .eq(DiaryPost::getStatus, "published") - .eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPriority) - .orderByDesc(DiaryPost::getPublishTime); - return this.page(page, wrapper); + public DiaryPostResponse updateDiaryPostWithResponse(DiaryPostUpdateRequest request) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryPost::getId, request.getId()) + .set(StringUtils.hasText(request.getTitle()), DiaryPost::getTitle, request.getTitle()) + .set(StringUtils.hasText(request.getContent()), DiaryPost::getContent, request.getContent()) + .set(request.getImages() != null, DiaryPost::getImages, convertListToJson(request.getImages())) + .set(request.getVideos() != null, DiaryPost::getVideos, convertListToJson(request.getVideos())) + .set(StringUtils.hasText(request.getLocation()), DiaryPost::getLocation, request.getLocation()) + .set(StringUtils.hasText(request.getWeather()), DiaryPost::getWeather, request.getWeather()) + .set(StringUtils.hasText(request.getMood()), DiaryPost::getMood, request.getMood()) + .set(request.getTags() != null, DiaryPost::getTags, convertListToJson(request.getTags())) + .set(request.getIsPublic() != null, DiaryPost::getIsPublic, request.getIsPublic()) + .set(request.getIsAnonymous() != null, DiaryPost::getIsAnonymous, request.getIsAnonymous()) + .set(StringUtils.hasText(request.getStatus()), DiaryPost::getStatus, request.getStatus()) + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); + + boolean updated = this.update(wrapper); + if (!updated) { + return null; + } + DiaryPost updatedDiaryPost = this.getById(request.getId()); + return convertToResponse(updatedDiaryPost); } @Override - public List getByStatus(String status) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getStatus, status) - .eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPublishTime); - return this.list(wrapper); + public boolean deleteDiaryPost(String diaryId) { + return this.removeById(diaryId); } @Override - public List getByUserIdAndStatus(String userId, String status) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getUserId, userId) - .eq(DiaryPost::getStatus, status) - .eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPublishTime); - return this.list(wrapper); + public boolean softDeleteDiaryPost(String diaryId) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryPost::getId, diaryId) + .set(DiaryPost::getIsDeleted, 1) + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); } @Override - public List getByMood(String mood) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getMood, mood) - .eq(DiaryPost::getIsPublic, 1) - .eq(DiaryPost::getStatus, "published") - .eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPublishTime); - return this.list(wrapper); - } - - @Override - public List getByTags(List tags) { - // 这里需要根据实际需求实现标签查询逻辑 - // 由于tags字段是JSON格式,可能需要使用数据库的JSON查询功能 - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getIsPublic, 1) - .eq(DiaryPost::getStatus, "published") - .eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPublishTime); - return this.list(wrapper); - } - - @Override - public List getByLocation(String location) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.like(DiaryPost::getLocation, location) - .eq(DiaryPost::getIsPublic, 1) - .eq(DiaryPost::getStatus, "published") - .eq(DiaryPost::getIsDeleted, 0) - .orderByDesc(DiaryPost::getPublishTime); - return this.list(wrapper); + public boolean restoreDiaryPost(String diaryId) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryPost::getId, diaryId) + .set(DiaryPost::getIsDeleted, 0) + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); } @Override public boolean incrementViewCount(String diaryId) { LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .setSql("view_count = view_count + 1"); + .setSql("view_count = view_count + 1") + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -152,7 +199,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .setSql("like_count = like_count + 1"); + .setSql("like_count = like_count + 1") + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -160,7 +208,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .setSql("like_count = GREATEST(like_count - 1, 0)"); + .setSql("like_count = GREATEST(like_count - 1, 0)") + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -168,7 +217,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .setSql("comment_count = comment_count + 1"); + .setSql("comment_count = comment_count + 1") + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -176,7 +226,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .setSql("comment_count = GREATEST(comment_count - 1, 0)"); + .setSql("comment_count = GREATEST(comment_count - 1, 0)") + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -184,7 +235,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .setSql("share_count = share_count + 1"); + .setSql("share_count = share_count + 1") + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -192,7 +244,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .set(DiaryPost::getLastCommentTime, LocalDateTime.now()); + .set(DiaryPost::getLastCommentTime, LocalDateTime.now()) + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -200,7 +253,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .set(DiaryPost::getFeatured, featured); + .set(DiaryPost::getFeatured, featured) + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -208,7 +262,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(DiaryPost::getId, diaryId) - .set(DiaryPost::getPriority, priority); + .set(DiaryPost::getPriority, priority) + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); return this.update(wrapper); } @@ -239,26 +294,12 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(DiaryPost::getStatus, status) - .eq(DiaryPost::getIsDeleted, 0); - return this.count(wrapper); - } - - @Override - public DiaryPost createDiaryPost(com.emotion.dto.request.DiaryPostCreateRequest request) { - // 处理标题:如果为空,则设置为null或生成默认标题 - String title = request.getTitle(); - if (title == null || title.trim().isEmpty()) { - // 可以选择设置为null,或者生成一个默认标题 - // 这里我们设置为null,让数据库使用默认值 - title = null; - } - + public DiaryPostResponse publishDiaryWithAiComment(DiaryPostCreateRequest request) { + // 1. 保存日记 DiaryPost diaryPost = DiaryPost.builder() + .id(snowflakeIdGenerator.nextIdAsString()) // 使用雪花算法生成ID .userId(request.getUserId()) - .title(title) + .title(request.getTitle()) .content(request.getContent()) .images(convertListToJson(request.getImages())) .videos(convertListToJson(request.getVideos())) @@ -277,69 +318,8 @@ public class DiaryPostServiceImpl extends ServiceImpl wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryPost::getId, diaryId) - .set(StringUtils.hasText(request.getTitle()), DiaryPost::getTitle, request.getTitle()) - .set(StringUtils.hasText(request.getContent()), DiaryPost::getContent, request.getContent()) - .set(request.getImages() != null, DiaryPost::getImages, convertListToJson(request.getImages())) - .set(request.getVideos() != null, DiaryPost::getVideos, convertListToJson(request.getVideos())) - .set(StringUtils.hasText(request.getLocation()), DiaryPost::getLocation, request.getLocation()) - .set(StringUtils.hasText(request.getWeather()), DiaryPost::getWeather, request.getWeather()) - .set(StringUtils.hasText(request.getMood()), DiaryPost::getMood, request.getMood()) - .set(request.getTags() != null, DiaryPost::getTags, convertListToJson(request.getTags())) - .set(request.getIsPublic() != null, DiaryPost::getIsPublic, request.getIsPublic()) - .set(request.getIsAnonymous() != null, DiaryPost::getIsAnonymous, request.getIsAnonymous()) - .set(StringUtils.hasText(request.getStatus()), DiaryPost::getStatus, request.getStatus()); - return this.update(wrapper); - } - - @Override - public boolean deleteDiaryPost(String diaryId) { - return this.removeById(diaryId); - } - - @Override - public boolean softDeleteDiaryPost(String diaryId) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryPost::getId, diaryId) - .set(DiaryPost::getIsDeleted, 1); - return this.update(wrapper); - } - - @Override - public boolean restoreDiaryPost(String diaryId) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryPost::getId, diaryId) - .set(DiaryPost::getIsDeleted, 0); - return this.update(wrapper); - } - - @Override - public boolean addAiComment(String diaryId, String aiComment, Object aiEmotionAnalysis, - BigDecimal aiSentimentScore, List aiKeywords, String aiSuggestions) { - LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); - wrapper.eq(DiaryPost::getId, diaryId) - .set(DiaryPost::getAiComment, aiComment) - .set(DiaryPost::getAiCommentTime, LocalDateTime.now()) - .set(DiaryPost::getAiEmotionAnalysis, convertObjectToJson(aiEmotionAnalysis)) - .set(DiaryPost::getAiSentimentScore, aiSentimentScore) - .set(DiaryPost::getAiKeywords, convertListToJson(aiKeywords)) - .set(DiaryPost::getAiSuggestions, aiSuggestions); - return this.update(wrapper); - } - - @Override - public com.emotion.dto.response.DiaryPostResponse publishDiaryWithAiComment(com.emotion.dto.request.DiaryPostCreateRequest request) { - // 1. 保存日记 - DiaryPost diaryPost = createDiaryPost(request); // 2. 生成AI评论 String aiComment = null; try { @@ -350,13 +330,14 @@ public class DiaryPostServiceImpl extends ServiceImpl aiComments = diaryCommentService.getByDiaryIdAndCommentType(diaryPost.getId(), "ai"); - if (!aiComments.isEmpty()) { - response.setAiComment(aiComments.get(0).getContent()); - } - // 转换时间格式 - if (diaryPost.getPublishTime() != null) { - response.setPublishTime(diaryPost.getPublishTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); - } - if (diaryPost.getLastCommentTime() != null) { - response.setLastCommentTime(diaryPost.getLastCommentTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); - } - if (diaryPost.getAiCommentTime() != null) { - response.setAiCommentTime(diaryPost.getAiCommentTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); - } - if (diaryPost.getCreateTime() != null) { - response.setCreateTime(diaryPost.getCreateTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); - } - if (diaryPost.getUpdateTime() != null) { - response.setUpdateTime(diaryPost.getUpdateTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); - } - // 转换JSON字段 - try { - if (diaryPost.getImages() != null) { - response.setImages(objectMapper.readValue(diaryPost.getImages(), new com.fasterxml.jackson.core.type.TypeReference>() {})); - } - if (diaryPost.getVideos() != null) { - response.setVideos(objectMapper.readValue(diaryPost.getVideos(), new com.fasterxml.jackson.core.type.TypeReference>() {})); - } - if (diaryPost.getTags() != null) { - response.setTags(objectMapper.readValue(diaryPost.getTags(), new com.fasterxml.jackson.core.type.TypeReference>() {})); - } - if (diaryPost.getAiKeywords() != null) { - response.setAiKeywords(objectMapper.readValue(diaryPost.getAiKeywords(), new com.fasterxml.jackson.core.type.TypeReference>() {})); - } - if (diaryPost.getAiEmotionAnalysis() != null) { - response.setAiEmotionAnalysis(objectMapper.readValue(diaryPost.getAiEmotionAnalysis(), Object.class)); - } - if (diaryPost.getMetadata() != null) { - response.setMetadata(objectMapper.readValue(diaryPost.getMetadata(), Object.class)); - } - } catch (com.fasterxml.jackson.core.JsonProcessingException e) { - // 忽略JSON解析错误 - } - return response; + return convertToResponse(diaryPost); + } + + /** + * 添加AI评论 + */ + private boolean addAiComment(String diaryId, String aiComment, Object aiEmotionAnalysis, + BigDecimal aiSentimentScore, List aiKeywords, String aiSuggestions) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(DiaryPost::getId, diaryId) + .set(DiaryPost::getAiComment, aiComment) + .set(DiaryPost::getAiCommentTime, LocalDateTime.now()) + .set(DiaryPost::getAiEmotionAnalysis, convertObjectToJson(aiEmotionAnalysis)) + .set(DiaryPost::getAiSentimentScore, aiSentimentScore) + .set(DiaryPost::getAiKeywords, convertListToJson(aiKeywords)) + .set(DiaryPost::getAiSuggestions, aiSuggestions) + .set(DiaryPost::getUpdateTime, LocalDateTime.now()); + return this.update(wrapper); + } + + /** + * 创建PageResult对象 + */ + private PageResult createPageResult(IPage page, List records) { + PageResult pageResult = new PageResult<>(); + pageResult.setCurrent(page.getCurrent()); + pageResult.setSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setRecords(records); + return pageResult; } /** @@ -443,4 +408,60 @@ public class DiaryPostServiceImpl extends ServiceImpl>() { + })); + } + if (diaryPost.getVideos() != null) { + response.setVideos(objectMapper.readValue(diaryPost.getVideos(), new TypeReference>() { + })); + } + if (diaryPost.getTags() != null) { + response.setTags(objectMapper.readValue(diaryPost.getTags(), new TypeReference>() { + })); + } + if (diaryPost.getAiKeywords() != null) { + response.setAiKeywords( + objectMapper.readValue(diaryPost.getAiKeywords(), new TypeReference>() { + })); + } + if (diaryPost.getAiEmotionAnalysis() != null) { + response.setAiEmotionAnalysis(objectMapper.readValue(diaryPost.getAiEmotionAnalysis(), Object.class)); + } + if (diaryPost.getMetadata() != null) { + response.setMetadata(objectMapper.readValue(diaryPost.getMetadata(), Object.class)); + } + } catch (JsonProcessingException e) { + // 忽略JSON解析错误 + } + + return response; + } +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/EmotionAnalysisServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/EmotionAnalysisServiceImpl.java index 10ede3d..aaa1370 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/EmotionAnalysisServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/EmotionAnalysisServiceImpl.java @@ -5,15 +5,25 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.EmotionAnalysisCreateRequest; +import com.emotion.dto.request.EmotionAnalysisPageRequest; +import com.emotion.dto.request.EmotionAnalysisUpdateRequest; +import com.emotion.dto.response.EmotionAnalysisResponse; import com.emotion.entity.EmotionAnalysis; import com.emotion.mapper.EmotionAnalysisMapper; import com.emotion.service.EmotionAnalysisService; +import com.emotion.util.SnowflakeIdGenerator; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.stream.Collectors; /** * 情绪分析服务实现类 @@ -24,6 +34,11 @@ import java.util.List; @Service public class EmotionAnalysisServiceImpl extends ServiceImpl implements EmotionAnalysisService { + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @Override public IPage getPage(BasePageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); @@ -173,6 +188,7 @@ public class EmotionAnalysisServiceImpl extends ServiceImpl getPageWithResponse(EmotionAnalysisPageRequest request) { + IPage page = getPage(request); + List responses = page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + return createPageResult(page, responses); + } + + @Override + public PageResult getPageByUserIdWithResponse(String userId, + EmotionAnalysisPageRequest request) { + IPage page = getPageByUserId(request, userId); + List responses = page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + return createPageResult(page, responses); + } + + @Override + public EmotionAnalysisResponse getEmotionAnalysisResponseById(String id) { + EmotionAnalysis analysis = this.getById(id); + if (analysis == null) { + return null; + } + return convertToResponse(analysis); + } + + @Override + public EmotionAnalysisResponse getEmotionAnalysisResponseByMessageId(String messageId) { + EmotionAnalysis analysis = getByMessageId(messageId); + if (analysis == null) { + return null; + } + return convertToResponse(analysis); + } + + @Override + public EmotionAnalysisResponse createEmotionAnalysisWithResponse(EmotionAnalysisCreateRequest request) { + EmotionAnalysis analysis = createEmotionAnalysis( + request.getMessageId(), + request.getUserId(), + request.getPrimaryEmotion(), + request.getPolarity(), + request.getIntensity(), + request.getConfidence()); + return convertToResponse(analysis); + } + + @Override + public EmotionAnalysisResponse updateEmotionAnalysisWithResponse(EmotionAnalysisUpdateRequest request) { + EmotionAnalysis analysis = this.getById(request.getId()); + if (analysis == null) { + return null; + } + + // 更新字段 + if (request.getMessageId() != null) { + analysis.setMessageId(request.getMessageId()); + } + if (request.getUserId() != null) { + analysis.setCreateBy(request.getUserId()); + } + if (request.getPrimaryEmotion() != null) { + analysis.setPrimaryEmotion(request.getPrimaryEmotion()); + } + if (request.getPolarity() != null) { + analysis.setPolarity(request.getPolarity()); + } + if (request.getIntensity() != null) { + analysis.setIntensity(BigDecimal.valueOf(request.getIntensity())); + } + if (request.getConfidence() != null) { + analysis.setConfidence(BigDecimal.valueOf(request.getConfidence())); + } + + this.updateById(analysis); + return convertToResponse(analysis); + } + + @Override + public boolean deleteEmotionAnalysis(String id) { + return this.removeById(id); + } + + @Override + public List getEmotionAnalysisResponsesByPrimaryEmotion(String primaryEmotion) { + List analyses = getByPrimaryEmotion(primaryEmotion); + return analyses.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getEmotionAnalysisResponsesByPolarity(String polarity) { + List analyses = getByPolarity(polarity); + return analyses.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getEmotionAnalysisResponsesByUserIdAndEmotion(String userId, + String primaryEmotion) { + List analyses = getByUserIdAndEmotion(userId, primaryEmotion); + return analyses.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getEmotionAnalysisResponsesByUserIdAndTimeRange(String userId, + LocalDateTime startTime, LocalDateTime endTime) { + List analyses = getByUserIdAndTimeRange(userId, startTime, endTime); + return analyses.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + @Override + public List getEmotionAnalysisResponsesRecentByUserId(String userId, Integer limit) { + List analyses = getRecentByUserId(userId, limit); + return analyses.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + /** + * 创建PageResult对象 + */ + private PageResult createPageResult(IPage page, List records) { + PageResult pageResult = new PageResult<>(); + pageResult.setCurrent(page.getCurrent()); + pageResult.setSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setRecords(records); + return pageResult; + } + + /** + * 转换为响应对象 + */ + private EmotionAnalysisResponse convertToResponse(EmotionAnalysis analysis) { + EmotionAnalysisResponse response = new EmotionAnalysisResponse(); + BeanUtils.copyProperties(analysis, response); + response.setId(analysis.getId()); + if (analysis.getCreateTime() != null) { + response.setCreateTime(analysis.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (analysis.getUpdateTime() != null) { + response.setUpdateTime(analysis.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + return response; + } } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/EmotionRecordServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/EmotionRecordServiceImpl.java index 0d66c83..c25a035 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/EmotionRecordServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/EmotionRecordServiceImpl.java @@ -5,17 +5,29 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.emotion.common.BasePageRequest; +import com.emotion.common.PageResult; +import com.emotion.dto.request.EmotionRecordCreateRequest; +import com.emotion.dto.request.EmotionRecordPageRequest; +import com.emotion.dto.request.EmotionRecordUpdateRequest; +import com.emotion.dto.response.EmotionRecordResponse; import com.emotion.entity.EmotionRecord; import com.emotion.mapper.EmotionRecordMapper; import com.emotion.service.EmotionRecordService; +import com.emotion.util.SnowflakeIdGenerator; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.Map; +import java.util.HashMap; import java.util.Collections; +import java.util.stream.Collectors; /** * 情绪记录服务实现类 @@ -26,6 +38,11 @@ import java.util.Collections; @Service public class EmotionRecordServiceImpl extends ServiceImpl implements EmotionRecordService { + @Autowired + private SnowflakeIdGenerator snowflakeIdGenerator; + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + @Override public IPage getPage(BasePageRequest request) { Page page = new Page<>(request.getCurrent(), request.getSize()); @@ -134,20 +151,59 @@ public class EmotionRecordServiceImpl extends ServiceImpl wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(EmotionRecord::getUserId, userId) + .eq(EmotionRecord::getIsDeleted, 0); + + List records = this.list(wrapper); + if (records.isEmpty()) { + return 0.0; + } + + double sum = records.stream() + .mapToDouble(record -> record.getIntensity().doubleValue()) + .sum(); + + return sum / records.size(); } @Override public Double getAvgIntensityByUserIdAndTimeRange(String userId, LocalDateTime startTime, LocalDateTime endTime) { - // 这里需要自定义SQL查询平均值,暂时返回0 - return 0.0; + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(EmotionRecord::getUserId, userId) + .between(EmotionRecord::getCreateTime, startTime, endTime) + .eq(EmotionRecord::getIsDeleted, 0); + + List records = this.list(wrapper); + if (records.isEmpty()) { + return 0.0; + } + + double sum = records.stream() + .mapToDouble(record -> record.getIntensity().doubleValue()) + .sum(); + + return sum / records.size(); } @Override public String getMostFrequentEmotionByUserId(String userId) { - // 这里需要自定义SQL查询最常见的情绪类型,暂时返回null - return null; + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(EmotionRecord::getUserId, userId) + .eq(EmotionRecord::getIsDeleted, 0); + + List records = this.list(wrapper); + if (records.isEmpty()) { + return null; + } + + return records.stream() + .collect(Collectors.groupingBy(EmotionRecord::getEmotionType, Collectors.counting())) + .entrySet() + .stream() + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElse(null); } @Override @@ -181,6 +237,7 @@ public class EmotionRecordServiceImpl extends ServiceImpl getPageWithResponse(EmotionRecordPageRequest request) { + IPage page = getPage(request); + List responses = page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + PageResult pageResult = new PageResult<>(); + pageResult.setCurrent(page.getCurrent()); + pageResult.setSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setRecords(responses); + return pageResult; + } + + @Override + public PageResult getPageByUserIdWithResponse(String userId, EmotionRecordPageRequest request) { + IPage page = getPageByUserId(request, userId); + List responses = page.getRecords().stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + PageResult pageResult = new PageResult<>(); + pageResult.setCurrent(page.getCurrent()); + pageResult.setSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setRecords(responses); + return pageResult; + } + + @Override + public EmotionRecordResponse getEmotionRecordResponseById(String id) { + EmotionRecord record = this.getById(id); + if (record == null) { + return null; + } + return convertToResponse(record); + } + + @Override + public EmotionRecordResponse createEmotionRecordWithResponse(EmotionRecordCreateRequest request) { + EmotionRecord record = new EmotionRecord(); + record.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID + record.setUserId(request.getUserId()); + record.setRecordDate(request.getRecordDate()); + record.setEmotionType(request.getEmotionType()); + record.setIntensity(request.getIntensity()); + record.setTriggers(request.getTriggers()); + record.setDescription(request.getDescription()); + // 注意:tags字段在实体类中是String类型,需要转换 + record.setTags(request.getTags() != null ? request.getTags().toString() : null); + record.setWeather(request.getWeather()); + record.setLocation(request.getLocation()); + record.setActivity(request.getActivity()); + record.setPeople(request.getPeople()); + record.setNotes(request.getNotes()); + + this.save(record); + return convertToResponse(record); + } + + @Override + public EmotionRecordResponse updateEmotionRecordWithResponse(EmotionRecordUpdateRequest request) { + EmotionRecord record = this.getById(request.getId()); + if (record == null) { + return null; + } + + // 更新字段 + if (request.getRecordDate() != null) { + record.setRecordDate(request.getRecordDate()); + } + if (request.getEmotionType() != null) { + record.setEmotionType(request.getEmotionType()); + } + if (request.getIntensity() != null) { + record.setIntensity(request.getIntensity()); + } + if (request.getTriggers() != null) { + record.setTriggers(request.getTriggers()); + } + if (request.getDescription() != null) { + record.setDescription(request.getDescription()); + } + // 注意:tags字段在实体类中是String类型,需要转换 + if (request.getTags() != null) { + record.setTags(request.getTags().toString()); + } + if (request.getWeather() != null) { + record.setWeather(request.getWeather()); + } + if (request.getLocation() != null) { + record.setLocation(request.getLocation()); + } + if (request.getActivity() != null) { + record.setActivity(request.getActivity()); + } + if (request.getPeople() != null) { + record.setPeople(request.getPeople()); + } + if (request.getNotes() != null) { + record.setNotes(request.getNotes()); + } + + this.updateById(record); + return convertToResponse(record); + } + + @Override + public boolean deleteEmotionRecord(String id) { + return this.removeById(id); + } + + @Override + public Map getEmotionStats(String userId, String startDate, String endDate) { + Map stats = new HashMap<>(); + + // 构建查询条件 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(EmotionRecord::getUserId, userId) + .eq(EmotionRecord::getIsDeleted, 0); + + // 如果提供了日期范围,则添加时间条件 + if (StringUtils.hasText(startDate) && StringUtils.hasText(endDate)) { + try { + LocalDateTime start = LocalDate.parse(startDate).atStartOfDay(); + LocalDateTime end = LocalDate.parse(endDate).atTime(23, 59, 59); + wrapper.between(EmotionRecord::getCreateTime, start, end); + } catch (Exception e) { + // 如果日期解析失败,忽略日期条件 + } + } + + // 查询用户的所有情绪记录 + List records = this.list(wrapper); + + if (records.isEmpty()) { + stats.put("emotionDistribution", new HashMap<>()); + stats.put("totalRecords", 0); + stats.put("averageIntensity", 0.0); + stats.put("mostFrequentEmotion", null); + stats.put("emotionTrend", "no_data"); + return stats; + } + + // 情绪类型分布 + Map emotionDistribution = records.stream() + .collect(Collectors.groupingBy(EmotionRecord::getEmotionType, Collectors.counting())); + + stats.put("emotionDistribution", emotionDistribution); + stats.put("totalRecords", records.size()); + + // 平均情绪强度 + double avgIntensity = records.stream() + .mapToDouble(record -> record.getIntensity().doubleValue()) + .average() + .orElse(0.0); + stats.put("averageIntensity", avgIntensity); + + // 最常见的情绪类型 + String mostFrequentEmotion = emotionDistribution.entrySet().stream() + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElse(null); + stats.put("mostFrequentEmotion", mostFrequentEmotion); + + // 情绪趋势(简单实现:比较前半段和后半段的平均强度) + if (records.size() > 1) { + int mid = records.size() / 2; + double firstHalfAvg = records.subList(0, mid).stream() + .mapToDouble(record -> record.getIntensity().doubleValue()) + .average() + .orElse(0.0); + + double secondHalfAvg = records.subList(mid, records.size()).stream() + .mapToDouble(record -> record.getIntensity().doubleValue()) + .average() + .orElse(0.0); + + if (secondHalfAvg > firstHalfAvg) { + stats.put("emotionTrend", "improving"); + } else if (secondHalfAvg < firstHalfAvg) { + stats.put("emotionTrend", "declining"); + } else { + stats.put("emotionTrend", "stable"); + } + } else { + stats.put("emotionTrend", "insufficient_data"); + } + + return stats; + } + + @Override + public List getRecentByUserIdWithResponse(String userId, Integer limit) { + List records = getRecentByUserId(userId, limit); + return records.stream() + .map(this::convertToResponse) + .collect(Collectors.toList()); + } + + /** + * 转换为响应对象 + */ + private EmotionRecordResponse convertToResponse(EmotionRecord record) { + EmotionRecordResponse response = new EmotionRecordResponse(); + BeanUtils.copyProperties(record, response); + response.setId(record.getId()); + if (record.getCreateTime() != null) { + response.setCreateTime(record.getCreateTime().format(DATE_TIME_FORMATTER)); + } + if (record.getUpdateTime() != null) { + response.setUpdateTime(record.getUpdateTime().format(DATE_TIME_FORMATTER)); + } + return response; + } } \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/TokenServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/TokenServiceImpl.java index bc15d66..caa6f63 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/TokenServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/TokenServiceImpl.java @@ -4,10 +4,13 @@ import com.emotion.dto.response.UserInfoResponse; import com.emotion.exception.TokenException; import com.emotion.service.AuthService; import com.emotion.service.TokenService; +import com.emotion.util.TokenUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import javax.servlet.http.HttpServletRequest; + /** * 令牌服务实现类 * @@ -20,15 +23,19 @@ public class TokenServiceImpl implements TokenService { @Autowired private AuthService authService; + @Autowired + private TokenUtil tokenUtil; + @Override - public UserInfoResponse getUserInfoByToken(String token) { - String userId = validateTokenAndGetUserId(token); + public UserInfoResponse getUserInfoByToken(HttpServletRequest request) { + String userId = validateTokenAndGetUserId(request); return authService.getCurrentUserInfo(userId); } @Override - public String getUsernameByToken(String token) { - if (!StringUtils.hasText(token)) { + public String getUsernameByToken(HttpServletRequest request) { + String token = tokenUtil.extractToken(request); + if (!tokenUtil.isValidToken(token)) { throw new TokenException("未提供访问令牌"); } @@ -45,8 +52,9 @@ public class TokenServiceImpl implements TokenService { } @Override - public String validateTokenAndGetUserId(String token) { - if (!StringUtils.hasText(token)) { + public String validateTokenAndGetUserId(HttpServletRequest request) { + String token = tokenUtil.extractToken(request); + if (!tokenUtil.isValidToken(token)) { throw new TokenException("未提供访问令牌"); } @@ -61,4 +69,4 @@ public class TokenServiceImpl implements TokenService { return userId; } -} +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/service/impl/WebSocketServiceImpl.java b/backend-single/src/main/java/com/emotion/service/impl/WebSocketServiceImpl.java index 22904fb..a154e93 100644 --- a/backend-single/src/main/java/com/emotion/service/impl/WebSocketServiceImpl.java +++ b/backend-single/src/main/java/com/emotion/service/impl/WebSocketServiceImpl.java @@ -1,5 +1,6 @@ package com.emotion.service.impl; +import com.emotion.dto.request.WebSocketRequest; import com.emotion.dto.websocket.ChatRequest; import com.emotion.dto.websocket.ConnectRequest; import com.emotion.dto.websocket.WebSocketMessage; @@ -13,6 +14,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.security.Principal; import java.time.LocalDateTime; @@ -48,64 +50,54 @@ public class WebSocketServiceImpl implements WebSocketService { * 处理聊天消息 */ @Override - public void handleChatMessage(ChatRequest request, String sessionId, Principal principal) { + public void handleChatMessage(WebSocketRequest webSocketRequest, String sessionId, Principal principal) { try { - log.info("处理聊天消息: request={}, sessionId={}, principal={}", request, sessionId, principal); + log.info("处理聊天消息: request={}, sessionId={}, principal={}", webSocketRequest, sessionId, principal); // 验证请求参数 - if (request.getContent() == null || request.getContent().trim().isEmpty()) { - sendErrorMessage(request.getSenderId(), "消息内容不能为空"); + if (webSocketRequest.getContent() == null || webSocketRequest.getContent().trim().isEmpty()) { + sendErrorMessage(getUserId(principal, sessionId), "消息内容不能为空"); return; } - // 确定用户身份和类型 - String userId = request.getSenderId(); - WebSocketMessage.SenderType senderType = WebSocketMessage.SenderType.GUEST; + // 设置默认值 + setWebSocketRequestDefaults(webSocketRequest, principal, sessionId); - if (principal != null) { - userId = principal.getName(); - // 如果用户ID不是以guest_开头,说明是认证用户 - if (!userId.startsWith("guest_")) { - senderType = WebSocketMessage.SenderType.USER; - } - } + log.info("确定用户身份: userId={}, senderType={}", webSocketRequest.getSenderId(), + webSocketRequest.getSenderType()); - // 更新请求中的用户信息 - request.setSenderId(userId); - request.setSenderType(senderType == WebSocketMessage.SenderType.USER ? ChatRequest.SenderType.USER - : ChatRequest.SenderType.GUEST); - - log.info("确定用户身份: userId={}, senderType={}", userId, senderType); + // 转换请求对象 + ChatRequest chatRequest = convertToChatRequest(webSocketRequest); // 构建用户消息 WebSocketMessage userMessage = WebSocketMessage.builder() .messageId(UUID.randomUUID().toString()) - .conversationId(request.getConversationId()) - .type(WebSocketMessage.MessageType.TEXT) - .content(request.getContent()) - .senderId(userId) - .senderType(senderType) - .status(WebSocketMessage.MessageStatus.SENT) + .conversationId(chatRequest.getConversationId()) + .type("TEXT") + .content(chatRequest.getContent()) + .senderId(chatRequest.getSenderId()) + .senderType(getSenderType(chatRequest.getSenderType())) + .status("SENT") .createTime(LocalDateTime.now()) .build(); // 发送用户消息到会话 - if (request.getConversationId() != null) { - messagingTemplate.convertAndSend("/topic/conversation/" + request.getConversationId(), userMessage); + if (chatRequest.getConversationId() != null) { + messagingTemplate.convertAndSend("/topic/conversation/" + chatRequest.getConversationId(), userMessage); } // 发送给用户私有队列 - messagingTemplate.convertAndSendToUser(request.getSenderId(), "/queue/messages", userMessage); + messagingTemplate.convertAndSendToUser(chatRequest.getSenderId(), "/queue/messages", userMessage); // 发送AI思考状态 - sendAiThinkingMessage(request.getSenderId(), request.getConversationId()); + sendAiThinkingMessage(chatRequest.getSenderId(), chatRequest.getConversationId()); // 异步调用AI服务 - processAiResponse(request); + processAiResponse(chatRequest); } catch (Exception e) { log.error("处理聊天消息失败", e); - sendErrorMessage(request.getSenderId(), "消息处理失败,请稍后重试"); + sendErrorMessage(getUserId(principal, sessionId), "消息处理失败,请稍后重试"); } } @@ -115,39 +107,27 @@ public class WebSocketServiceImpl implements WebSocketService { @Override public void handleUserConnect(ConnectRequest request, String sessionId, Principal principal) { try { - String userId = request.getUserId(); - boolean isAuthenticated = false; - - // 优先从Principal获取认证用户信息 - if (principal != null) { - userId = principal.getName(); - // 检查是否是认证用户(不是访客) - isAuthenticated = !userId.startsWith("guest_"); - } - - // 如果还没有userId,生成访客ID - if (userId == null) { - userId = "guest_" + sessionId; - } + // 设置默认值 + setConnectRequestDefaults(request, principal, sessionId); log.info("用户连接WebSocket: userId={}, sessionId={}, authenticated={}", - userId, sessionId, isAuthenticated); + request.getUserId(), sessionId, isUserAuthenticated(request.getUserId())); // 记录在线用户 - onlineUsers.put(sessionId, userId); + onlineUsers.put(sessionId, request.getUserId()); // 发送连接成功消息 WebSocketMessage connectMessage = WebSocketMessage.builder() .messageId(UUID.randomUUID().toString()) - .type(WebSocketMessage.MessageType.CONNECTION) + .type("CONNECTION") .content("连接成功") .senderId("system") - .senderType(WebSocketMessage.SenderType.SYSTEM) - .status(WebSocketMessage.MessageStatus.SENT) + .senderType("SYSTEM") + .status("SENT") .createTime(LocalDateTime.now()) .build(); - messagingTemplate.convertAndSendToUser(userId, "/queue/messages", connectMessage); + messagingTemplate.convertAndSendToUser(request.getUserId(), "/queue/messages", connectMessage); } catch (Exception e) { log.error("处理用户连接失败", e); @@ -182,11 +162,11 @@ public class WebSocketServiceImpl implements WebSocketService { // 发送心跳响应 WebSocketMessage heartbeatMessage = WebSocketMessage.builder() .messageId(UUID.randomUUID().toString()) - .type(WebSocketMessage.MessageType.HEARTBEAT) + .type("HEARTBEAT") .content("pong") .senderId("system") - .senderType(WebSocketMessage.SenderType.SYSTEM) - .status(WebSocketMessage.MessageStatus.SENT) + .senderType("SYSTEM") + .status("SENT") .createTime(LocalDateTime.now()) .build(); @@ -206,11 +186,11 @@ public class WebSocketServiceImpl implements WebSocketService { WebSocketMessage thinkingMessage = WebSocketMessage.builder() .messageId(UUID.randomUUID().toString()) .conversationId(conversationId) - .type(WebSocketMessage.MessageType.AI_THINKING) + .type("AI_THINKING") .content("AI正在思考中...") .senderId("ai") - .senderType(WebSocketMessage.SenderType.AI) - .status(WebSocketMessage.MessageStatus.SENT) + .senderType("AI") + .status("SENT") .createTime(LocalDateTime.now()) .build(); @@ -246,7 +226,7 @@ public class WebSocketServiceImpl implements WebSocketService { userMessage.setUserId(userId); userMessage.setCreateBy(userId); // 设置创建人为当前用户 userMessage - .setUserType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest"); + .setUserType("USER".equals(request.getSenderType()) ? "registered" : "guest"); userMessage.setContent(request.getContent()); userMessage.setType("text"); userMessage.setSender("user"); @@ -281,11 +261,11 @@ public class WebSocketServiceImpl implements WebSocketService { private void sendErrorMessage(String userId, String errorContent) { WebSocketMessage errorMessage = WebSocketMessage.builder() .messageId(UUID.randomUUID().toString()) - .type(WebSocketMessage.MessageType.ERROR) + .type("ERROR") .content(errorContent) .senderId("system") - .senderType(WebSocketMessage.SenderType.SYSTEM) - .status(WebSocketMessage.MessageStatus.SENT) + .senderType("SYSTEM") + .status("SENT") .createTime(LocalDateTime.now()) .build(); @@ -309,7 +289,7 @@ public class WebSocketServiceImpl implements WebSocketService { Conversation conversation = Conversation.builder() .userId(userId) - .userType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest") + .userType("USER".equals(request.getSenderType()) ? "registered" : "guest") .title("新对话") .type("chat") .conversationStatus("active") @@ -341,7 +321,7 @@ public class WebSocketServiceImpl implements WebSocketService { // 如果会话不存在,创建一个 conversation = Conversation.builder() .userId(userId) - .userType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest") + .userType("USER".equals(request.getSenderType()) ? "registered" : "guest") .title("对话") .type("chat") .conversationStatus("active") @@ -453,11 +433,11 @@ public class WebSocketServiceImpl implements WebSocketService { WebSocketMessage fallbackMessage = WebSocketMessage.builder() .messageId(UUID.randomUUID().toString()) .conversationId(conversationId) - .type(WebSocketMessage.MessageType.TEXT) + .type("TEXT") .content(aiReply) .senderId("ai") - .senderType(WebSocketMessage.SenderType.AI) - .status(WebSocketMessage.MessageStatus.SENT) + .senderType("AI") + .status("SENT") .createTime(LocalDateTime.now()) .build(); @@ -488,11 +468,11 @@ public class WebSocketServiceImpl implements WebSocketService { WebSocketMessage aiMessage = WebSocketMessage.builder() .messageId(UUID.randomUUID().toString()) .conversationId(conversationId) - .type(WebSocketMessage.MessageType.TEXT) + .type("TEXT") .content(content) .senderId("ai") - .senderType(WebSocketMessage.SenderType.AI) - .status(WebSocketMessage.MessageStatus.SENT) + .senderType("AI") + .status("SENT") .createTime(LocalDateTime.now()) .build(); @@ -534,4 +514,95 @@ public class WebSocketServiceImpl implements WebSocketService { log.debug("没有换行符,返回原始内容"); return new String[]{aiReply}; } -} + + /** + * 设置WebSocket请求的默认值 + */ + private void setWebSocketRequestDefaults(WebSocketRequest request, Principal principal, String sessionId) { + // 如果请求中没有发送者ID,尝试从Principal获取 + if (request.getSenderId() == null && principal != null) { + request.setSenderId(principal.getName()); + } + + // 如果还是没有发送者ID,使用会话ID作为访客ID + if (request.getSenderId() == null) { + request.setSenderId("guest_" + sessionId); + request.setSenderType("GUEST"); + } + + // 设置时间戳 + if (request.getTimestamp() == null) { + request.setTimestamp(System.currentTimeMillis()); + } + } + + /** + * 设置连接请求的默认值 + */ + private void setConnectRequestDefaults(ConnectRequest request, Principal principal, String sessionId) { + // 优先从Principal获取认证用户信息 + if (principal != null) { + request.setUserId(principal.getName()); + } + + // 如果还没有userId,生成访客ID + if (request.getUserId() == null) { + request.setUserId("guest_" + sessionId); + } + + // 设置连接时间戳 + if (request.getTimestamp() == null) { + request.setTimestamp(System.currentTimeMillis()); + } + } + + /** + * 获取用户ID + */ + private String getUserId(Principal principal, String sessionId) { + if (principal != null) { + return principal.getName(); + } + return "guest_" + sessionId; + } + + /** + * 判断用户是否已认证 + */ + private boolean isUserAuthenticated(String userId) { + return userId != null && !userId.startsWith("guest_"); + } + + /** + * 获取发送者类型 + */ + private String getSenderType(String senderType) { + if ("USER".equals(senderType)) { + return "USER"; + } else if ("GUEST".equals(senderType)) { + return "GUEST"; + } else if ("AI".equals(senderType)) { + return "AI"; + } else if ("SYSTEM".equals(senderType)) { + return "SYSTEM"; + } + return "USER"; + } + + /** + * 转换WebSocketRequest到ChatRequest + * + * @param webSocketRequest WebSocket请求对象 + * @return ChatRequest对象 + */ + private ChatRequest convertToChatRequest(WebSocketRequest webSocketRequest) { + return ChatRequest.builder() + .content(webSocketRequest.getContent()) + .senderId(webSocketRequest.getSenderId()) + .senderType(webSocketRequest.getSenderType()) + .messageType(webSocketRequest.getMessageType()) + .conversationId(webSocketRequest.getConversationId()) + .timestamp(webSocketRequest.getTimestamp()) + .build(); + } +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/util/TokenUtil.java b/backend-single/src/main/java/com/emotion/util/TokenUtil.java new file mode 100644 index 0000000..62efd20 --- /dev/null +++ b/backend-single/src/main/java/com/emotion/util/TokenUtil.java @@ -0,0 +1,47 @@ +package com.emotion.util; + +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +/** + * 令牌工具类 + * + * @author emotion-museum + * @date 2025-09-08 + */ +@Component +public class TokenUtil { + + /** + * 从请求中提取访问令牌 + * + * @param request HTTP请求 + * @return 访问令牌 + */ + public String extractToken(HttpServletRequest request) { + String authHeader = request.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + + // 也可以从请求参数中获取 + String tokenParam = request.getParameter("token"); + if (tokenParam != null && !tokenParam.trim().isEmpty()) { + return tokenParam.trim(); + } + + return null; + } + + /** + * 验证令牌是否有效 + * + * @param token 令牌 + * @return 是否有效 + */ + public boolean isValidToken(String token) { + return StringUtils.hasText(token); + } +} \ No newline at end of file diff --git a/backend-single/src/main/java/com/emotion/util/UserContextUtils.java b/backend-single/src/main/java/com/emotion/util/UserContextUtils.java index 64a7247..821c6a7 100644 --- a/backend-single/src/main/java/com/emotion/util/UserContextUtils.java +++ b/backend-single/src/main/java/com/emotion/util/UserContextUtils.java @@ -1,57 +1,94 @@ package com.emotion.util; +import com.emotion.util.UserContextHolder; import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; /** * 用户上下文工具类 - * 提供便捷的用户上下文操作方法 + * 提供便捷的方法操作用户上下文信息 * * @author emotion-museum - * @date 2025-07-23 + * @date 2025-07-25 */ @Slf4j public class UserContextUtils { /** - * 获取当前用户ID,如果为空则返回默认值 + * 获取当前用户ID * - * @param defaultValue 默认值 - * @return 用户ID + * @return 当前用户ID,如果未登录则返回null */ - public static String getCurrentUserIdOrDefault(String defaultValue) { - String userId = UserContextHolder.getCurrentUserId(); - return userId != null ? userId : defaultValue; + public static String getCurrentUserId() { + return UserContextHolder.getCurrentUserId(); } - + /** - * 获取当前用户ID,如果为空则返回"system" + * 获取当前用户名 * - * @return 用户ID + * @return 当前用户名,如果未登录则返回null */ - public static String getCurrentUserIdOrSystem() { - return getCurrentUserIdOrDefault("system"); + public static String getCurrentUsername() { + return UserContextHolder.getCurrentUsername(); } - + /** - * 获取当前用户名,如果为空则返回默认值 + * 获取当前用户类型 * - * @param defaultValue 默认值 - * @return 用户名 + * @return 当前用户类型,如果未登录则返回null */ - public static String getCurrentUsernameOrDefault(String defaultValue) { - String username = UserContextHolder.getCurrentUsername(); - return username != null ? username : defaultValue; + public static String getCurrentUserType() { + return UserContextHolder.getCurrentUserType(); } - + /** - * 获取当前用户名,如果为空则返回"guest" + * 获取客户端IP * - * @return 用户名 + * @return 客户端IP */ - public static String getCurrentUsernameOrGuest() { - return getCurrentUsernameOrDefault("guest"); + public static String getClientIp() { + return UserContextHolder.getClientIp(); } - + + /** + * 获取请求ID + * + * @return 请求ID + */ + public static String getRequestId() { + return UserContextHolder.getRequestId(); + } + + /** + * 获取当前用户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; + } + /** * 获取当前用户类型,如果为空则返回默认值 * @@ -62,7 +99,7 @@ public class UserContextUtils { String userType = UserContextHolder.getCurrentUserType(); return userType != null ? userType : defaultValue; } - + /** * 获取当前用户类型,如果为空则返回"GUEST" * @@ -71,7 +108,7 @@ public class UserContextUtils { public static String getCurrentUserTypeOrGuest() { return getCurrentUserTypeOrDefault("GUEST"); } - + /** * 获取客户端IP,如果为空则返回默认值 * @@ -82,7 +119,7 @@ public class UserContextUtils { String clientIp = UserContextHolder.getClientIp(); return clientIp != null ? clientIp : defaultValue; } - + /** * 获取客户端IP,如果为空则返回"unknown" * @@ -91,7 +128,7 @@ public class UserContextUtils { public static String getClientIpOrUnknown() { return getClientIpOrDefault("unknown"); } - + /** * 获取请求ID,如果为空则返回默认值 * @@ -102,87 +139,27 @@ public class UserContextUtils { String requestId = UserContextHolder.getRequestId(); return requestId != null ? requestId : defaultValue; } - + /** - * 获取请求ID,如果为空则返回"unknown" + * 获取请求ID,如果为空则返回随机UUID * * @return 请求ID */ - public static String getRequestIdOrUnknown() { - return getRequestIdOrDefault("unknown"); - } - - /** - * 检查当前用户是否为访客 - * - * @return 是否为访客 - */ - public static boolean isGuest() { - String userId = UserContextHolder.getCurrentUserId(); - String userType = UserContextHolder.getCurrentUserType(); - - return userId == null || - userId.startsWith("guest_") || - "GUEST".equalsIgnoreCase(userType); - } - - /** - * 检查当前用户是否为系统用户 - * - * @return 是否为系统用户 - */ - public static boolean isSystem() { - String userId = UserContextHolder.getCurrentUserId(); - String userType = UserContextHolder.getCurrentUserType(); - - return "system".equals(userId) || - "SYSTEM".equalsIgnoreCase(userType); - } - - /** - * 检查当前用户是否为注册用户 - * - * @return 是否为注册用户 - */ - public static boolean isRegisteredUser() { - return !isGuest() && !isSystem(); - } - - /** - * 安全地执行需要用户上下文的操作 - * 如果没有用户上下文,会设置默认的系统上下文 - * - * @param operation 操作 - */ - public static void executeWithContext(Runnable operation) { - boolean hasContext = UserContextHolder.hasUserContext(); - - try { - // 如果没有用户上下文,设置默认的系统上下文 - if (!hasContext) { - UserContextHolder.setUserContext("system", "system", "SYSTEM", "127.0.0.1", "system"); - log.debug("设置默认系统用户上下文"); - } - - // 执行操作 - operation.run(); - - } finally { - // 如果是我们设置的默认上下文,执行后清理 - if (!hasContext) { - UserContextHolder.clear(); - log.debug("清理默认系统用户上下文"); - } + public static String getRequestIdOrRandom() { + String requestId = UserContextHolder.getRequestId(); + if (requestId != null) { + return requestId; } + return java.util.UUID.randomUUID().toString().replace("-", ""); } - + /** - * 临时设置用户上下文执行操作 + * 执行带有临时上下文的操作 * - * @param userId 用户ID - * @param username 用户名 - * @param userType 用户类型 - * @param operation 操作 + * @param userId 用户ID + * @param username 用户名 + * @param userType 用户类型 + * @param operation 要执行的操作 */ public static void executeWithTempContext(String userId, String username, String userType, Runnable operation) { // 保存当前上下文 @@ -211,4 +188,59 @@ public class UserContextUtils { } } } -} + + /** + * 从HTTP请求中获取客户端真实IP地址 + * + * @param request HTTP请求 + * @return 客户端IP地址 + */ + public static String getClientIpAddress(HttpServletRequest request) { + String ip = null; + + // 1. 从X-Forwarded-For获取(经过代理的情况) + ip = request.getHeader("X-Forwarded-For"); + if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) { + // 多个IP的情况,取第一个 + int index = ip.indexOf(','); + if (index != -1) { + ip = ip.substring(0, index); + } + return ip.trim(); + } + + // 2. 从X-Real-IP获取 + ip = request.getHeader("X-Real-IP"); + if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) { + return ip.trim(); + } + + // 3. 从Proxy-Client-IP获取 + ip = request.getHeader("Proxy-Client-IP"); + if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) { + return ip.trim(); + } + + // 4. 从WL-Proxy-Client-IP获取 + ip = request.getHeader("WL-Proxy-Client-IP"); + if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) { + return ip.trim(); + } + + // 5. 从HTTP_CLIENT_IP获取 + ip = request.getHeader("HTTP_CLIENT_IP"); + if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) { + return ip.trim(); + } + + // 6. 从HTTP_X_FORWARDED_FOR获取 + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) { + return ip.trim(); + } + + // 7. 最后从getRemoteAddr获取 + ip = request.getRemoteAddr(); + return StringUtils.hasText(ip) ? ip.trim() : "unknown"; + } +} \ No newline at end of file diff --git a/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/GuestChatController.java b/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/GuestChatController.java index 05435f3..f767409 100644 --- a/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/GuestChatController.java +++ b/backend/ai/server/src/main/java/com/emotionmuseum/ai/controller/GuestChatController.java @@ -1,10 +1,12 @@ package com.emotionmuseum.ai.controller; -import com.emotionmuseum.ai.request.*; -import com.emotionmuseum.ai.response.*; -import com.emotionmuseum.ai.dto.MessageListResponse; +import com.emotionmuseum.ai.dto.GuestChatRequest; +import com.emotionmuseum.ai.dto.GuestChatResponse; import com.emotionmuseum.ai.dto.GuestUserInfo; +import com.emotionmuseum.ai.dto.MessageListResponse; +import com.emotionmuseum.ai.dto.ConversationListResponse; import com.emotionmuseum.ai.service.GuestChatService; +import com.emotionmuseum.common.interceptor.UserContextInterceptor; import com.emotionmuseum.common.result.Result; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -12,31 +14,34 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; - import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.http.HttpServletRequest; import java.time.LocalDateTime; import java.util.List; /** * 访客聊天控制器 - * + * 提供访客模式下的聊天功能 + * * @author emotion-museum - * @since 2025-07-13 + * @since 2025-07-24 */ @Slf4j @RestController -@RequestMapping("/api/ai/guest") +@RequestMapping("/ai/guest") @RequiredArgsConstructor -@Tag(name = "访客聊天", description = "访客模式AI聊天接口") +@Tag(name = "访客聊天", description = "访客模式下的AI聊天功能") public class GuestChatController { private final GuestChatService guestChatService; + private final UserContextInterceptor userContextInterceptor = new UserContextInterceptor(); @PostMapping("/chat") @Operation(summary = "访客聊天", description = "访客模式下发送消息并获取AI回复") - public Result guestChat( - @RequestBody com.emotionmuseum.ai.dto.GuestChatRequest request) { + public Result guestChat( + @RequestBody GuestChatRequest request) { // 自动获取客户端IP和User-Agent String clientIp = getClientIp(); @@ -62,8 +67,8 @@ public class GuestChatController { return (Result) guestChatService.getGuestConversations(clientIp, pageNum, pageSize); } - @GetMapping("/conversation/{conversationId}/messages") - @Operation(summary = "获取访客会话消息", description = "获取指定会话的消息列表") + @GetMapping("/messages/{conversationId}") + @Operation(summary = "获取会话消息", description = "获取指定会话的所有消息") public Result> getGuestConversationMessages( @Parameter(description = "会话ID") @PathVariable String conversationId, @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum, @@ -72,11 +77,11 @@ public class GuestChatController { String clientIp = getClientIp(); log.info("获取访客会话消息: IP={}, ConversationId={}", clientIp, conversationId); - return guestChatService.getGuestConversationMessages(conversationId, clientIp, pageNum, pageSize); + return (Result) guestChatService.getGuestConversationMessages(conversationId, clientIp, pageNum, pageSize); } - @PostMapping("/conversation/{conversationId}/end") - @Operation(summary = "结束访客会话", description = "结束指定的访客会话") + @PostMapping("/end/{conversationId}") + @Operation(summary = "结束会话", description = "结束指定的访客会话") public Result endGuestConversation( @Parameter(description = "会话ID") @PathVariable String conversationId) { @@ -86,7 +91,7 @@ public class GuestChatController { return guestChatService.endGuestConversation(conversationId, clientIp); } - @GetMapping("/user/info") + @GetMapping("/user-info") @Operation(summary = "获取访客用户信息", description = "根据IP地址获取或创建访客用户信息") public Result getGuestUserInfo() { String clientIp = getClientIp(); @@ -94,38 +99,23 @@ public class GuestChatController { log.info("获取访客用户信息: IP={}", clientIp); - return guestChatService.getOrCreateGuestUser(clientIp, userAgent); + return guestChatService.getGuestUserInfo(clientIp, userAgent); } - @PostMapping("/emotion/analyze") - @Operation(summary = "访客情绪分析", description = "分析访客输入文本的情绪") - public Result analyzeGuestEmotion( - @RequestBody com.emotionmuseum.ai.dto.EmotionAnalysisRequest request) { + @PostMapping("/test-split") + @Operation(summary = "测试拆分功能", description = "测试AI回复的拆分功能") + public Result testSplitFunction( + @RequestBody GuestChatRequest request) { - String clientIp = getClientIp(); - log.info("访客情绪分析: IP={}, Text={}", clientIp, request.getText()); + log.info("测试拆分功能: Message={}", request.getMessage()); - return guestChatService.analyzeGuestEmotion(request, clientIp); - } - - @GetMapping("/health") - @Operation(summary = "访客服务健康检查", description = "检查访客聊天服务状态") - public Result healthCheck() { - return Result.success(true); - } - - @PostMapping("/test/split") - @Operation(summary = "测试消息拆分功能", description = "测试AI回复消息的拆分功能") - public Result testMessageSplit( - @RequestBody com.emotionmuseum.ai.dto.GuestChatRequest request) { - log.info("测试消息拆分功能: message={}", request.getMessage()); - - // 模拟包含不同换行符的AI回复进行测试 + // 根据消息内容生成不同的模拟回复 String mockAiReply; if (request.getMessage().contains("双换行")) { - mockAiReply = "这是第一段回复,介绍了基本功能。我可以帮助你进行日常对话。\n\n" + - "这是第二段回复,详细说明了聊天功能。我能理解你的情感并给出合适的回应。\n\n" + - "这是第三段回复,介绍了情感分析功能。我可以分析你的情绪状态并提供建议。"; + mockAiReply = "这是第一段回复,介绍基本功能。\n\n" + + "这是第二段回复,说明聊天功能。\n\n" + + "这是第三段回复,介绍情感分析。\n\n" + + "这是第四段回复,提供使用建议。"; } else if (request.getMessage().contains("单换行")) { mockAiReply = "这是第一行回复,介绍基本功能。\n" + "这是第二行回复,说明聊天功能。\n" + @@ -136,7 +126,7 @@ public class GuestChatController { } // 创建模拟的访客聊天响应 - com.emotionmuseum.ai.dto.GuestChatResponse response = new com.emotionmuseum.ai.dto.GuestChatResponse(); + GuestChatResponse response = new GuestChatResponse(); response.setGuestUserId("test_guest_user"); response.setGuestNickname("测试用户"); response.setConversationId("test_conversation_" + System.currentTimeMillis()); @@ -166,29 +156,7 @@ public class GuestChatController { } var request = attributes.getRequest(); - String ip = request.getHeader("X-Forwarded-For"); - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_CLIENT_IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_X_FORWARDED_FOR"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - - // 处理多个IP的情况,取第一个 - if (ip != null && ip.contains(",")) { - ip = ip.split(",")[0].trim(); - } - - return ip; + return userContextInterceptor.getClientIpAddress(request); } catch (Exception e) { return "127.0.0.1"; } @@ -211,4 +179,4 @@ public class GuestChatController { return "Unknown"; } } -} +} \ No newline at end of file