接口优化

This commit is contained in:
2025-09-08 17:54:12 +08:00
parent e20030f10d
commit d42d689bd7
84 changed files with 6403 additions and 4310 deletions
+182 -30
View File
@@ -6,15 +6,15 @@ type: "always_apply"
## 基础设置 ## 基础设置
1. 保持对话语言为中文/英文 1. 保持对话语言为中文
2. 系统环境:Mac 2. 不允许在未经允许的情况下删除代码和文件,不允许破坏已经正常的业务代码
3. 执行终端命令时要关注执行情况,避免无效等待 3. 执行终端命令时要关注执行情况,避免无效等待
## 代码规范 ## 代码规范
4. 生成代码时必须添加类级和函数级注释 4. 生成代码时必须添加类级和函数级注释
5. 使用import导包,禁止使用全限定名称引用类 5. 使用import导包,禁止使用全限定名称引用类
6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型 6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型,禁止以枚举类作为方法的入参,确保接口的通用性和可扩展性
7. 新增数据的id使用已存在的雪花算法生成器生成 7. 新增数据的id使用已存在的雪花算法生成器生成
## 架构规范 ## 架构规范
@@ -34,46 +34,198 @@ type: "always_apply"
- 使用项目已有的Result做接口返回 - 使用项目已有的Result做接口返回
13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装 13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装
14. Controller层路由禁止添加/api前缀 14. Controller层路由禁止添加/api前缀
15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空 15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空,必须明确指定value属性值且使用驼峰结构命名,避免使用下划线分隔符。所有接口注解必须显式指定value属性,如@GetMapping(value = "/page")、@PostMapping(value = "/create")等,禁止使用@GetMapping()或@PostMapping等空注解形式
16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息 16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息
17. Controller层接口的Mapping注解(PostMapping、GetMapping、PutMapping、DeleteMapping等)的value属性值要简洁明了,与接口作用相关,名称不宜过长,使用驼峰结构命名 17. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误
18. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误 18. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性
19. 所有接口注解必须明确指定value属性,不允许使用空注解 19. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等
20. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性 20. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List
21. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等 21. 接口路径应避免层级过深,建议不超过3级路径结构
22. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List 22. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中
23. 接口路径应避免层级过深,建议不超过3级路径结构 23. Controller层接口应保持简洁,避免为特定字段创建独立的更新方法(如updateStatus等),应只保留一个通用的update方法,具体的业务逻辑在Service层实现
24. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中 24. Service层在执行更新操作时,应对每个字段进行空值检查,只更新非空字段,避免空字段覆盖原有值,确保数据完整性
25. Controller层应避免创建多个特定条件的查询接口,只保留一个分页查询方法,通过在请求对象中包含所有可查询字段来支持不同的查询需求
26. 分页查询接口应支持模糊查询和精确查询,通过在Service层根据字段类型和业务需求判断查询方式
## 环境配置 ## 环境配置
25. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 27. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择
26. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 28. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置
## 数据库规范 ## 数据库规范
27. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 29. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段
28. 删除操作优先使用逻辑删除,添加deleted字段标识 30. 删除操作优先使用逻辑删除,添加deleted字段标识
29. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 31. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名
30. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 32. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名
31. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 33. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全
32. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 34. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰
33. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 35. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误
## 安全规范 ## 安全规范
34. 所有外部输入必须进行参数校验 36. 所有外部输入必须进行参数校验
35. 敏感信息不得在日志中输出 37. 敏感信息不得在日志中输出
36. 数据库操作必须使用参数化查询,防止SQL注入 38. 数据库操作必须使用参数化查询,防止SQL注入
## 性能规范 ## 性能规范
37. 避免N+1查询问题,合理使用批量查询 39. 避免N+1查询问题,合理使用批量查询
38. 大数据量查询必须分页处理 40. 大数据量查询必须分页处理
39. 缓存策略要考虑数据一致性问题 41. 缓存策略要考虑数据一致性问题
## 日志规范 ## 日志规范
40. 关键业务操作必须记录操作日志 42. 关键业务操作必须记录操作日志
41. 异常信息要包含足够的上下文信息 43. 异常信息要包含足够的上下文信息
42. 生产环境禁止输出debug级别日志 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、repositorymapper/dao)、domain/entity、converter、config、clientfeign)、event、facade、task、util、constant。
- DTO/Request/Response 与 Entity 分离;Controller 不做对象互转,统一在 Service/Converter 层完成。
### 二、分层架构规范
- Controller 层职责
- 接收请求、参数校验(Jakarta Validation)、鉴权校验(由网关/拦截器)、调用 Service,统一使用 `Result<T>` 返回。
- 禁止:业务逻辑、事务控制、数据访问、实体与请求/响应的相互转换。
- Service 层职责
- 聚合业务逻辑、领域编排、事务边界控制(@Transactional,读写分离时仅在写方法上标注),幂等与重试策略在此实现。
- 方法命名与 Controller 对应,入参使用 Request 对象,出参使用 Response 对象。
- Repository 层职责
- 仅做数据访问,统一使用 MyBatis-Plus(优先 LambdaQueryWrapper,避免硬编码字段)。
- Entity 规范
- 统一继承 BaseEntity,字段包含:created_by、create_time、updated_by、update_time、logical_delete、remark(与项目约定一致)。
- 禁止使用枚举作为实体/DTO 字段类型;以 String/Integer 存储并在注释中补充取值说明。
### 三、统一异常与返回结果
- 全局异常处理
- 继续使用 common-web 的 GlobalExceptionHandler;禁止在业务代码中大面积 try-catch,异常由全局处理。
- 业务异常一律抛出 BusinessException;校验异常统一转为 400 语义返回。
- 统一返回结构
- 统一使用 common-core 的 Result<T> 与 ResultCode;保证 traceId、timestamp 一致性。
- Controller 层仅返回 Result,不直接返回实体或集合。
- 参数校验
- 使用 jakarta.validation 注解(@NotNull@Size 等),在 Request 对象上标注;Controller 使用 @Validated
### 四、数据库操作规范
- 设计规范
- 数据表必须包含 create_time、update_time;删除优先逻辑删除 logical_delete;字段命名下划线风格。
- 建议仅创建必要索引;避免外键约束(由应用层保证一致性)。
- 查询构建
- 优先使用 LambdaQueryWrapper 和链式调用;分页查询必须使用分页插件;大数据量必须分页。
- 避免 N+1 查询;需要时使用批量查询或联表映射。
- 事务管理
- 仅在 Service 层声明事务;方法内仅包含数据库操作或与数据库一致性相关的远程调用;跨服务一致性使用可靠消息/补偿方案。
---
## 二、代码质量保障机制
- 代码审查流程(Code Review
- 所有变更必须走 PR;至少 1 名同组开发+1 名模块 Owner 审核通过方可合并。
- 审查清单:命名/注释/分层边界/异常处理/日志/性能/安全/接口兼容性/测试覆盖率/静态扫描告警。
- PR 模板需包含:改动描述、影响面、回归范围、测试说明、回滚方案。
- 静态代码分析与格式
- 集成 Checkstyle(代码风格)、SpotBugs/PMD(潜在缺陷)、SonarQube(质量门禁),EditorConfig/格式化工具保持一致风格。
- 质量门禁建议:
- 新增代码覆盖率 >= 80%,全局覆盖率 >= 70%
- Blocker/Critical 问题为 0
- 重复代码率 < 3%
- 圈复杂度阈值:方法 < 10,类 < 80。
- 测试规范
- 单元测试:命名 {ClassName}Test,方法名 should_xxx_when_xxx;使用 Mockito/AssertJ;隔离外部依赖。
- 集成测试:使用 @SpringBootTest,测试容器或嵌入式组件替代真实依赖;构造典型场景与边界场景。
- 覆盖率准则:核心业务与关键分支必须覆盖(正常/异常/边界/空数据)。
- 用例分层:Service 层单测覆盖业务规则;Controller 层使用 MockMvcRepository 层覆盖复杂 SQL 与多条件查询。
- 重构指导原则
- 小步快跑、单一职责、先加测试再重构;保持对外行为一致(新增特性使用特性开关/版本控制)。
- 严禁大规模跨模块重构合并在单次 PR;拆分为多个可审查的独立提交。
---
## 三、变更管理流程
- 变更前影响分析
- 维度:接口兼容性(路径/入参/语义)、数据库(表/字段/索引/数据迁移)、配置(Nacos/环境变量)、依赖版本、性能、容量与并发、安全与合规、可回滚性。
- 产出:影响分析文档与回滚方案(包含回滚脚本/开关)。
- 回归测试策略
- 冒烟覆盖主干流程;模块级回归覆盖变更影响域;跨服务联调验证接口契约(通过网关)。
- 回归范围基于依赖关系与影响分析确定;优先自动化回归,必要时补充手工用例。
- 版本管理与发布
- 分支策略建议:
- 主分支:master
- 功能分支:feature/{module}-{short-desc}
- 预发布:release/x.y.z
- 紧急修复:hotfix/x.y.z
- 版本号:语义化版本 SemVerMAJOR.MINOR.PATCH),发布时打 Tag;所有服务端口遵循 280xx 约定(配置文件统一)。
- 紧急修复与常规开发
- 紧急修复:创建 hotfix → 修复 → 快速测试 → 合并 master 与 release → 立即发布 → 追补测试与文档。
- 常规开发:feature → PR 审核 → 合并 → 集成测试 → 发布。
---
## 四、持续改进机制
- 规范评审与更新
- 每月一次质量例会,评审新增问题与改进项;规范变更需在本文件留痕(版本/日期/变更点)。
- 质量指标监控(建议在 Sonar/日志/监控平台看板化)
- 构建成功率、静态问题趋势、单元测试覆盖率、平均修复时长、缺陷密度、接口成功率、P95/P99 响应时间、数据库慢查询比例。
- 团队培训计划
- 新人入项必读规范与代码演练;关键模块轮训;每季度至少一次专题分享(性能调优/安全加固/重构实践)。
- 反馈闭环
- 通过代码评审、事后复盘(Postmortem)、问题工单收敛到规范条款;指定责任人与截止时间;下次评审验证落地结果。
---
## 附:标准检查清单(节选)
- PR 自检
- 命名/注释达标;无魔法值;日志分级正确且无敏感信息;空指针风险已处理;删除代码/文件已获批准。
- Controller 不含业务逻辑;路由命名与网关规范一致;统一 Result 返回;参数校验齐全;不使用 PathVariable。
- Service 事务边界清晰;仅更新非空字段;分页/批量/幂等校验到位。
- Repository 使用 LambdaQueryWrapper;避免硬编码字段;SQL 有必要索引;避免 N+1。
- 测试覆盖关键路径与边界;新增功能附带测试;CI 静态扫描无阻断级问题。
- 回滚准备
- 是否具备配置开关/灰度发布策略;是否提供回滚脚本;数据库迁移是否可逆(或给出补偿方案)。
+186 -30
View File
@@ -4,23 +4,27 @@ alwaysApply: true
# 项目开发规则 # 项目开发规则
## 基础设置 ## 基础设置
1. 保持对话语言为中文/英文
2. 系统环境:Mac 1. 保持对话语言为中文
2. 不允许在未经允许的情况下删除代码和文件,不允许破坏已经正常的业务代码
3. 执行终端命令时要关注执行情况,避免无效等待 3. 执行终端命令时要关注执行情况,避免无效等待
## 代码规范 ## 代码规范
4. 生成代码时必须添加类级和函数级注释 4. 生成代码时必须添加类级和函数级注释
5. 使用import导包,禁止使用全限定名称引用类 5. 使用import导包,禁止使用全限定名称引用类
6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型 6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型,禁止以枚举类作为方法的入参,确保接口的通用性和可扩展性
7. 新增数据的id使用已存在的雪花算法生成器生成 7. 新增数据的id使用已存在的雪花算法生成器生成
## 架构规范 ## 架构规范
8. 所有开发必须遵循当前项目规范 8. 所有开发必须遵循当前项目规范
9. Controller层禁止添加业务逻辑 9. Controller层禁止添加业务逻辑
10. 使用全局异常处理,禁止使用try-catch 10. 使用全局异常处理,禁止使用try-catch
11. 前端接口访问尽可能走网关调用 11. 前端接口访问尽可能走网关调用
## 接口设计规范 ## 接口设计规范
12. Controller层接口定义要完整: 12. Controller层接口定义要完整:
- 入参使用request封装传递到service层 - 入参使用request封装传递到service层
- service层的方法命名与controller层定义的接口的方法名称保持一致 - service层的方法命名与controller层定义的接口的方法名称保持一致
@@ -29,46 +33,198 @@ alwaysApply: true
- 使用项目已有的Result做接口返回 - 使用项目已有的Result做接口返回
13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装 13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装
14. Controller层路由禁止添加/api前缀 14. Controller层路由禁止添加/api前缀
15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空 15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空,必须明确指定value属性值且使用驼峰结构命名,避免使用下划线分隔符。所有接口注解必须显式指定value属性,如@GetMapping(value = "/page")、@PostMapping(value = "/create")等,禁止使用@GetMapping()或@PostMapping等空注解形式
16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息 16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息
17. Controller层接口的Mapping注解(PostMapping、GetMapping、PutMapping、DeleteMapping等)的value属性值要简洁明了,与接口作用相关,名称不宜过长,使用驼峰结构命名 17. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误
18. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误 18. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性
19. 所有接口注解必须明确指定value属性,不允许使用空注解 19. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等
20. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性 20. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List
21. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等 21. 接口路径应避免层级过深,建议不超过3级路径结构
22. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List 22. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中
23. 接口路径应避免层级过深,建议不超过3级路径结构 23. Controller层接口应保持简洁,避免为特定字段创建独立的更新方法(如updateStatus等),应只保留一个通用的update方法,具体的业务逻辑在Service层实现
24. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中 24. Service层在执行更新操作时,应对每个字段进行空值检查,只更新非空字段,避免空字段覆盖原有值,确保数据完整性
25. Controller层应避免创建多个特定条件的查询接口,只保留一个分页查询方法,通过在请求对象中包含所有可查询字段来支持不同的查询需求
26. 分页查询接口应支持模糊查询和精确查询,通过在Service层根据字段类型和业务需求判断查询方式
## 环境配置 ## 环境配置
25. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 27. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择
26. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 28. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置
## 数据库规范 ## 数据库规范
27. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 29. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段
28. 删除操作优先使用逻辑删除,添加deleted字段标识 30. 删除操作优先使用逻辑删除,添加deleted字段标识
29. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 31. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名
30. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 32. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名
31. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 33. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全
32. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 34. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰
33. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 35. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误
## 安全规范 ## 安全规范
34. 所有外部输入必须进行参数校验 36. 所有外部输入必须进行参数校验
35. 敏感信息不得在日志中输出 37. 敏感信息不得在日志中输出
36. 数据库操作必须使用参数化查询,防止SQL注入 38. 数据库操作必须使用参数化查询,防止SQL注入
## 性能规范 ## 性能规范
37. 避免N+1查询问题,合理使用批量查询 39. 避免N+1查询问题,合理使用批量查询
38. 大数据量查询必须分页处理 40. 大数据量查询必须分页处理
39. 缓存策略要考虑数据一致性问题 41. 缓存策略要考虑数据一致性问题
## 日志规范 ## 日志规范
40. 关键业务操作必须记录操作日志 42. 关键业务操作必须记录操作日志
41. 异常信息要包含足够的上下文信息 43. 异常信息要包含足够的上下文信息
42. 生产环境禁止输出debug级别日志 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、repositorymapper/dao)、domain/entity、converter、config、clientfeign)、event、facade、task、util、constant。
- DTO/Request/Response 与 Entity 分离;Controller 不做对象互转,统一在 Service/Converter 层完成。
### 二、分层架构规范
- Controller 层职责
- 接收请求、参数校验(Jakarta Validation)、鉴权校验(由网关/拦截器)、调用 Service,统一使用 `Result<T>` 返回。
- 禁止:业务逻辑、事务控制、数据访问、实体与请求/响应的相互转换。
- Service 层职责
- 聚合业务逻辑、领域编排、事务边界控制(@Transactional,读写分离时仅在写方法上标注),幂等与重试策略在此实现。
- 方法命名与 Controller 对应,入参使用 Request 对象,出参使用 Response 对象。
- Repository 层职责
- 仅做数据访问,统一使用 MyBatis-Plus(优先 LambdaQueryWrapper,避免硬编码字段)。
- Entity 规范
- 统一继承 BaseEntity,字段包含:created_by、create_time、updated_by、update_time、logical_delete、remark(与项目约定一致)。
- 禁止使用枚举作为实体/DTO 字段类型;以 String/Integer 存储并在注释中补充取值说明。
### 三、统一异常与返回结果
- 全局异常处理
- 继续使用 common-web 的 GlobalExceptionHandler;禁止在业务代码中大面积 try-catch,异常由全局处理。
- 业务异常一律抛出 BusinessException;校验异常统一转为 400 语义返回。
- 统一返回结构
- 统一使用 common-core 的 Result<T> 与 ResultCode;保证 traceId、timestamp 一致性。
- Controller 层仅返回 Result,不直接返回实体或集合。
- 参数校验
- 使用 jakarta.validation 注解(@NotNull、@Size 等),在 Request 对象上标注;Controller 使用 @Validated。
### 四、数据库操作规范
- 设计规范
- 数据表必须包含 create_time、update_time;删除优先逻辑删除 logical_delete;字段命名下划线风格。
- 建议仅创建必要索引;避免外键约束(由应用层保证一致性)。
- 查询构建
- 优先使用 LambdaQueryWrapper 和链式调用;分页查询必须使用分页插件;大数据量必须分页。
- 避免 N+1 查询;需要时使用批量查询或联表映射。
- 事务管理
- 仅在 Service 层声明事务;方法内仅包含数据库操作或与数据库一致性相关的远程调用;跨服务一致性使用可靠消息/补偿方案。
---
## 二、代码质量保障机制
- 代码审查流程(Code Review
- 所有变更必须走 PR;至少 1 名同组开发+1 名模块 Owner 审核通过方可合并。
- 审查清单:命名/注释/分层边界/异常处理/日志/性能/安全/接口兼容性/测试覆盖率/静态扫描告警。
- PR 模板需包含:改动描述、影响面、回归范围、测试说明、回滚方案。
- 静态代码分析与格式
- 集成 Checkstyle(代码风格)、SpotBugs/PMD(潜在缺陷)、SonarQube(质量门禁),EditorConfig/格式化工具保持一致风格。
- 质量门禁建议:
- 新增代码覆盖率 >= 80%,全局覆盖率 >= 70%
- Blocker/Critical 问题为 0
- 重复代码率 < 3%
- 圈复杂度阈值:方法 < 10,类 < 80。
- 测试规范
- 单元测试:命名 {ClassName}Test,方法名 should_xxx_when_xxx;使用 Mockito/AssertJ;隔离外部依赖。
- 集成测试:使用 @SpringBootTest,测试容器或嵌入式组件替代真实依赖;构造典型场景与边界场景。
- 覆盖率准则:核心业务与关键分支必须覆盖(正常/异常/边界/空数据)。
- 用例分层:Service 层单测覆盖业务规则;Controller 层使用 MockMvcRepository 层覆盖复杂 SQL 与多条件查询。
- 重构指导原则
- 小步快跑、单一职责、先加测试再重构;保持对外行为一致(新增特性使用特性开关/版本控制)。
- 严禁大规模跨模块重构合并在单次 PR;拆分为多个可审查的独立提交。
---
## 三、变更管理流程
- 变更前影响分析
- 维度:接口兼容性(路径/入参/语义)、数据库(表/字段/索引/数据迁移)、配置(Nacos/环境变量)、依赖版本、性能、容量与并发、安全与合规、可回滚性。
- 产出:影响分析文档与回滚方案(包含回滚脚本/开关)。
- 回归测试策略
- 冒烟覆盖主干流程;模块级回归覆盖变更影响域;跨服务联调验证接口契约(通过网关)。
- 回归范围基于依赖关系与影响分析确定;优先自动化回归,必要时补充手工用例。
- 版本管理与发布
- 分支策略建议:
- 主分支:master
- 功能分支:feature/{module}-{short-desc}
- 预发布:release/x.y.z
- 紧急修复:hotfix/x.y.z
- 版本号:语义化版本 SemVerMAJOR.MINOR.PATCH),发布时打 Tag;所有服务端口遵循 280xx 约定(配置文件统一)。
- 紧急修复与常规开发
- 紧急修复:创建 hotfix → 修复 → 快速测试 → 合并 master 与 release → 立即发布 → 追补测试与文档。
- 常规开发:feature → PR 审核 → 合并 → 集成测试 → 发布。
---
## 四、持续改进机制
- 规范评审与更新
- 每月一次质量例会,评审新增问题与改进项;规范变更需在本文件留痕(版本/日期/变更点)。
- 质量指标监控(建议在 Sonar/日志/监控平台看板化)
- 构建成功率、静态问题趋势、单元测试覆盖率、平均修复时长、缺陷密度、接口成功率、P95/P99 响应时间、数据库慢查询比例。
- 团队培训计划
- 新人入项必读规范与代码演练;关键模块轮训;每季度至少一次专题分享(性能调优/安全加固/重构实践)。
- 反馈闭环
- 通过代码评审、事后复盘(Postmortem)、问题工单收敛到规范条款;指定责任人与截止时间;下次评审验证落地结果。
---
## 附:标准检查清单(节选)
- PR 自检
- 命名/注释达标;无魔法值;日志分级正确且无敏感信息;空指针风险已处理;删除代码/文件已获批准。
- Controller 不含业务逻辑;路由命名与网关规范一致;统一 Result 返回;参数校验齐全;不使用 PathVariable。
- Service 事务边界清晰;仅更新非空字段;分页/批量/幂等校验到位。
- Repository 使用 LambdaQueryWrapper;避免硬编码字段;SQL 有必要索引;避免 N+1。
- 测试覆盖关键路径与边界;新增功能附带测试;CI 静态扫描无阻断级问题。
- 回滚准备
- 是否具备配置开关/灰度发布策略;是否提供回滚脚本;数据库迁移是否可逆(或给出补偿方案)。
+188 -30
View File
@@ -2,26 +2,31 @@
trigger: always_on trigger: always_on
alwaysApply: true alwaysApply: true
--- ---
# 项目开发规则 # 项目开发规则
## 基础设置 ## 基础设置
1. 保持对话语言为中文/英文
2. 系统环境:Mac 1. 保持对话语言为中文
2. 不允许在未经允许的情况下删除代码和文件,不允许破坏已经正常的业务代码
3. 执行终端命令时要关注执行情况,避免无效等待 3. 执行终端命令时要关注执行情况,避免无效等待
## 代码规范 ## 代码规范
4. 生成代码时必须添加类级和函数级注释 4. 生成代码时必须添加类级和函数级注释
5. 使用import导包,禁止使用全限定名称引用类 5. 使用import导包,禁止使用全限定名称引用类
6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型 6. 禁止使用枚举类型作为entity、request、response、dto对象的字段类型,禁止以枚举类作为方法的入参,确保接口的通用性和可扩展性
7. 新增数据的id使用已存在的雪花算法生成器生成 7. 新增数据的id使用已存在的雪花算法生成器生成
## 架构规范 ## 架构规范
8. 所有开发必须遵循当前项目规范 8. 所有开发必须遵循当前项目规范
9. Controller层禁止添加业务逻辑 9. Controller层禁止添加业务逻辑
10. 使用全局异常处理,禁止使用try-catch 10. 使用全局异常处理,禁止使用try-catch
11. 前端接口访问尽可能走网关调用 11. 前端接口访问尽可能走网关调用
## 接口设计规范 ## 接口设计规范
12. Controller层接口定义要完整: 12. Controller层接口定义要完整:
- 入参使用request封装传递到service层 - 入参使用request封装传递到service层
- service层的方法命名与controller层定义的接口的方法名称保持一致 - service层的方法命名与controller层定义的接口的方法名称保持一致
@@ -30,46 +35,199 @@ alwaysApply: true
- 使用项目已有的Result做接口返回 - 使用项目已有的Result做接口返回
13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装 13. 接口和方法参数不允许超过两个,超过时使用request或DTO对象封装
14. Controller层路由禁止添加/api前缀 14. Controller层路由禁止添加/api前缀
15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空 15. Controller层接口的Mapping注解value属性值不允许重复且不允许为空,必须明确指定value属性值且使用驼峰结构命名,避免使用下划线分隔符。所有接口注解必须显式指定value属性,如@GetMapping(value = "/page")、@PostMapping(value = "/create")等,禁止使用@GetMapping()或@PostMapping等空注解形式
16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息 16. 用户相关接口禁止直接传递用户id,需要后端根据token获取当前登录用户信息
17. Controller层接口的Mapping注解(PostMapping、GetMapping、PutMapping、DeleteMapping等)的value属性值要简洁明了,与接口作用相关,名称不宜过长,使用驼峰结构命名 17. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误
18. 禁止使用/{param}格式的路径参数,避免网关路由冲突和分发错误 18. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性
19. 所有接口注解必须明确指定value属性,不允许使用空注解 19. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等
20. 路径参数统一使用@RequestParam而非@PathVariable,确保网关分发准确性 20. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List
21. 接口路径命名应具有明确的语义,避免使用通用词汇如/get、/list等 21. 接口路径应避免层级过深,建议不超过3级路径结构
22. 批量操作接口应使用专门的Request对象封装参数,而非直接传递List 22. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中
23. 接口路径应避免层级过深,建议不超过3级路径结构 23. Controller层接口应保持简洁,避免为特定字段创建独立的更新方法(如updateStatus等),应只保留一个通用的update方法,具体的业务逻辑在Service层实现
24. 更新操作应直接使用封装了ID和其他数据的Request对象,而不是单独传递ID参数。Service层方法应保持参数简洁,业务逻辑所需数据应全部包含在Request对象中 24. Service层在执行更新操作时,应对每个字段进行空值检查,只更新非空字段,避免空字段覆盖原有值,确保数据完整性
25. Controller层应避免创建多个特定条件的查询接口,只保留一个分页查询方法,通过在请求对象中包含所有可查询字段来支持不同的查询需求
26. 分页查询接口应支持模糊查询和精确查询,通过在Service层根据字段类型和业务需求判断查询方式
## 环境配置 ## 环境配置
25. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择 27. 为不同环境(local、dev、prod)创建单独配置文件,部署时通过参数选择
26. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置 28. 启动服务时禁止擅自修改端口号,使用配置文件中的端口设置
## 数据库规范 ## 数据库规范
27. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段 29. 所有数据表必须包含创建时间(create_time)和更新时间(update_time)字段
28. 删除操作优先使用逻辑删除,添加deleted字段标识 30. 删除操作优先使用逻辑删除,添加deleted字段标识
29. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名 31. 数据库字段命名使用下划线分隔,Java实体类使用驼峰命名
30. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名 32. 优先使用LambdaQueryWrapper构造条件查询,避免硬编码字段名
31. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全 33. 使用Lambda表达式引用实体类属性,提高代码可维护性和类型安全
32. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰 34. 复杂查询条件应使用LambdaQueryWrapper的链式调用,保持代码清晰
33. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误 35. 避免在查询条件中使用字符串字段名,防止字段名变更导致的运行时错误
## 安全规范 ## 安全规范
34. 所有外部输入必须进行参数校验 36. 所有外部输入必须进行参数校验
35. 敏感信息不得在日志中输出 37. 敏感信息不得在日志中输出
36. 数据库操作必须使用参数化查询,防止SQL注入 38. 数据库操作必须使用参数化查询,防止SQL注入
## 性能规范 ## 性能规范
37. 避免N+1查询问题,合理使用批量查询 39. 避免N+1查询问题,合理使用批量查询
38. 大数据量查询必须分页处理 40. 大数据量查询必须分页处理
39. 缓存策略要考虑数据一致性问题 41. 缓存策略要考虑数据一致性问题
## 日志规范 ## 日志规范
40. 关键业务操作必须记录操作日志 42. 关键业务操作必须记录操作日志
41. 异常信息要包含足够的上下文信息 43. 异常信息要包含足够的上下文信息
42. 生产环境禁止输出debug级别日志 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、repositorymapper/dao)、domain/entity、converter、config、clientfeign)、event、facade、task、util、constant。
- DTO/Request/Response 与 Entity 分离;Controller 不做对象互转,统一在 Service/Converter 层完成。
### 二、分层架构规范
- Controller 层职责
- 接收请求、参数校验(Jakarta Validation)、鉴权校验(由网关/拦截器)、调用 Service,统一使用 `Result<T>` 返回。
- 禁止:业务逻辑、事务控制、数据访问、实体与请求/响应的相互转换。
- Service 层职责
- 聚合业务逻辑、领域编排、事务边界控制(@Transactional,读写分离时仅在写方法上标注),幂等与重试策略在此实现。
- 方法命名与 Controller 对应,入参使用 Request 对象,出参使用 Response 对象。
- Repository 层职责
- 仅做数据访问,统一使用 MyBatis-Plus(优先 LambdaQueryWrapper,避免硬编码字段)。
- Entity 规范
- 统一继承 BaseEntity,字段包含:created_by、create_time、updated_by、update_time、logical_delete、remark(与项目约定一致)。
- 禁止使用枚举作为实体/DTO 字段类型;以 String/Integer 存储并在注释中补充取值说明。
### 三、统一异常与返回结果
- 全局异常处理
- 继续使用 common-web 的 GlobalExceptionHandler;禁止在业务代码中大面积 try-catch,异常由全局处理。
- 业务异常一律抛出 BusinessException;校验异常统一转为 400 语义返回。
- 统一返回结构
- 统一使用 common-core 的 Result<T> 与 ResultCode;保证 traceId、timestamp 一致性。
- Controller 层仅返回 Result,不直接返回实体或集合。
- 参数校验
- 使用 jakarta.validation 注解(@NotNull@Size 等),在 Request 对象上标注;Controller 使用 @Validated
### 四、数据库操作规范
- 设计规范
- 数据表必须包含 create_time、update_time;删除优先逻辑删除 logical_delete;字段命名下划线风格。
- 建议仅创建必要索引;避免外键约束(由应用层保证一致性)。
- 查询构建
- 优先使用 LambdaQueryWrapper 和链式调用;分页查询必须使用分页插件;大数据量必须分页。
- 避免 N+1 查询;需要时使用批量查询或联表映射。
- 事务管理
- 仅在 Service 层声明事务;方法内仅包含数据库操作或与数据库一致性相关的远程调用;跨服务一致性使用可靠消息/补偿方案。
---
## 二、代码质量保障机制
- 代码审查流程(Code Review
- 所有变更必须走 PR;至少 1 名同组开发+1 名模块 Owner 审核通过方可合并。
- 审查清单:命名/注释/分层边界/异常处理/日志/性能/安全/接口兼容性/测试覆盖率/静态扫描告警。
- PR 模板需包含:改动描述、影响面、回归范围、测试说明、回滚方案。
- 静态代码分析与格式
- 集成 Checkstyle(代码风格)、SpotBugs/PMD(潜在缺陷)、SonarQube(质量门禁),EditorConfig/格式化工具保持一致风格。
- 质量门禁建议:
- 新增代码覆盖率 >= 80%,全局覆盖率 >= 70%
- Blocker/Critical 问题为 0
- 重复代码率 < 3%
- 圈复杂度阈值:方法 < 10,类 < 80。
- 测试规范
- 单元测试:命名 {ClassName}Test,方法名 should_xxx_when_xxx;使用 Mockito/AssertJ;隔离外部依赖。
- 集成测试:使用 @SpringBootTest,测试容器或嵌入式组件替代真实依赖;构造典型场景与边界场景。
- 覆盖率准则:核心业务与关键分支必须覆盖(正常/异常/边界/空数据)。
- 用例分层:Service 层单测覆盖业务规则;Controller 层使用 MockMvcRepository 层覆盖复杂 SQL 与多条件查询。
- 重构指导原则
- 小步快跑、单一职责、先加测试再重构;保持对外行为一致(新增特性使用特性开关/版本控制)。
- 严禁大规模跨模块重构合并在单次 PR;拆分为多个可审查的独立提交。
---
## 三、变更管理流程
- 变更前影响分析
- 维度:接口兼容性(路径/入参/语义)、数据库(表/字段/索引/数据迁移)、配置(Nacos/环境变量)、依赖版本、性能、容量与并发、安全与合规、可回滚性。
- 产出:影响分析文档与回滚方案(包含回滚脚本/开关)。
- 回归测试策略
- 冒烟覆盖主干流程;模块级回归覆盖变更影响域;跨服务联调验证接口契约(通过网关)。
- 回归范围基于依赖关系与影响分析确定;优先自动化回归,必要时补充手工用例。
- 版本管理与发布
- 分支策略建议:
- 主分支:master
- 功能分支:feature/{module}-{short-desc}
- 预发布:release/x.y.z
- 紧急修复:hotfix/x.y.z
- 版本号:语义化版本 SemVerMAJOR.MINOR.PATCH),发布时打 Tag;所有服务端口遵循 280xx 约定(配置文件统一)。
- 紧急修复与常规开发
- 紧急修复:创建 hotfix → 修复 → 快速测试 → 合并 master 与 release → 立即发布 → 追补测试与文档。
- 常规开发:feature → PR 审核 → 合并 → 集成测试 → 发布。
---
## 四、持续改进机制
- 规范评审与更新
- 每月一次质量例会,评审新增问题与改进项;规范变更需在本文件留痕(版本/日期/变更点)。
- 质量指标监控(建议在 Sonar/日志/监控平台看板化)
- 构建成功率、静态问题趋势、单元测试覆盖率、平均修复时长、缺陷密度、接口成功率、P95/P99 响应时间、数据库慢查询比例。
- 团队培训计划
- 新人入项必读规范与代码演练;关键模块轮训;每季度至少一次专题分享(性能调优/安全加固/重构实践)。
- 反馈闭环
- 通过代码评审、事后复盘(Postmortem)、问题工单收敛到规范条款;指定责任人与截止时间;下次评审验证落地结果。
---
## 附:标准检查清单(节选)
- PR 自检
- 命名/注释达标;无魔法值;日志分级正确且无敏感信息;空指针风险已处理;删除代码/文件已获批准。
- Controller 不含业务逻辑;路由命名与网关规范一致;统一 Result 返回;参数校验齐全;不使用 PathVariable。
- Service 事务边界清晰;仅更新非空字段;分页/批量/幂等校验到位。
- Repository 使用 LambdaQueryWrapper;避免硬编码字段;SQL 有必要索引;避免 N+1。
- 测试覆盖关键路径与边界;新增功能附带测试;CI 静态扫描无阻断级问题。
- 回滚准备
- 是否具备配置开关/灰度发布策略;是否提供回滚脚本;数据库迁移是否可逆(或给出补偿方案)。
@@ -52,4 +52,12 @@ public class PageResult<T> {
public static <T> PageResult<T> of(IPage<T> page) { public static <T> PageResult<T> of(IPage<T> page) {
return new PageResult<>(page); return new PageResult<>(page);
} }
}
/**
* 设置数据列表
*/
public PageResult<T> setRecords(List<T> records) {
this.records = records;
return this;
}
}
@@ -1,99 +1,88 @@
package com.emotion.controller; package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.PageResult; import com.emotion.common.PageResult;
import com.emotion.common.Result; import com.emotion.common.Result;
import com.emotion.dto.request.PageRequest; import com.emotion.dto.request.achievement.*;
import com.emotion.dto.response.BaseResponse; import com.emotion.dto.response.achievement.AchievementResponse;
import com.emotion.entity.Achievement;
import com.emotion.service.AchievementService; 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.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.time.format.DateTimeFormatter; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 成就控制器 * 成就控制器
* *
* @author emotion-museum * @author emotion-museum
* @date 2025-07-23 * @date 2025-09-08
*/ */
@RestController @RestController
@RequestMapping("/achievement") @RequestMapping("/achievement")
@Tag(name = "成就管理", description = "成就的增删改查功能")
public class AchievementController { public class AchievementController {
@Autowired @Autowired
private AchievementService achievementService; private AchievementService achievementService;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** /**
* 分页查询成就 * 分页查询成就
*/ */
@Operation(summary = "分页查询成就", description = "分页查询成就列表")
@GetMapping("/page") @GetMapping("/page")
public Result<PageResult<AchievementResponse>> getPage(@Validated PageRequest request) { public Result<PageResult<AchievementResponse>> getPage(@Validated AchievementPageRequest request) {
IPage<Achievement> page = achievementService.getPage(request); PageResult<AchievementResponse> pageResult = achievementService.getPageWithResponse(request);
List<AchievementResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<AchievementResponse> 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); return Result.success(pageResult);
} }
/** /**
* 根据ID获取成就 * 根据ID获取成就
*/ */
@GetMapping("/{id}") @Operation(summary = "根据ID获取成就", description = "根据ID获取成就详情")
public Result<AchievementResponse> getById(@PathVariable String id) { @GetMapping("/detail")
Achievement achievement = achievementService.getById(id); public Result<AchievementResponse> getById(@RequestParam String id) {
if (achievement == null) { AchievementResponse response = achievementService.getAchievementResponseById(id);
if (response == null) {
return Result.notFound("成就不存在"); return Result.notFound("成就不存在");
} }
return Result.success(convertToResponse(achievement)); return Result.success(response);
} }
/** /**
* 创建成就 * 创建成就
*/ */
@PostMapping @Operation(summary = "创建成就", description = "创建新的成就")
public Result<AchievementResponse> create(@RequestBody Achievement achievement) { @PostMapping("/create")
boolean saved = achievementService.save(achievement); public Result<AchievementResponse> create(@RequestBody @Validated AchievementCreateRequest request) {
if (!saved) { AchievementResponse response = achievementService.createAchievementWithResponse(request);
if (response == null) {
return Result.error("创建失败"); return Result.error("创建失败");
} }
return Result.success(convertToResponse(achievement)); return Result.success("创建成功", response);
} }
/** /**
* 更新成就 * 更新成就
*/ */
@PutMapping("/{id}") @Operation(summary = "更新成就", description = "更新指定成就")
public Result<AchievementResponse> update(@PathVariable String id, @RequestBody Achievement achievement) { @PutMapping("/update")
achievement.setId(id); public Result<AchievementResponse> update(@RequestBody @Validated AchievementUpdateRequest request) {
boolean updated = achievementService.updateById(achievement); AchievementResponse response = achievementService.updateAchievementWithResponse(request);
if (!updated) { if (response == null) {
return Result.error("更新失败"); return Result.error("更新失败");
} }
Achievement updatedAchievement = achievementService.getById(id); return Result.success("更新成功", response);
return Result.success(convertToResponse(updatedAchievement));
} }
/** /**
* 删除成就 * 删除成就
*/ */
@DeleteMapping("/{id}") @Operation(summary = "删除成就", description = "删除指定成就")
public Result<Void> delete(@PathVariable String id) { @DeleteMapping("/delete")
public Result<Void> delete(@RequestParam String id) {
boolean deleted = achievementService.removeById(id); boolean deleted = achievementService.removeById(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
@@ -104,55 +93,48 @@ public class AchievementController {
/** /**
* 根据分类查询成就 * 根据分类查询成就
*/ */
@GetMapping("/category/{category}") @Operation(summary = "根据分类查询成就", description = "根据分类查询成就列表")
public Result<List<AchievementResponse>> getByCategory(@PathVariable String category) { @GetMapping("/byCategory")
List<Achievement> achievements = achievementService.getByCategory(category); public Result<List<AchievementResponse>> getByCategory(@RequestParam String category) {
List<AchievementResponse> responses = achievements.stream() List<AchievementResponse> responses = achievementService.getByCategoryWithResponse(category);
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 根据稀有度查询成就 * 根据稀有度查询成就
*/ */
@GetMapping("/rarity/{rarity}") @Operation(summary = "根据稀有度查询成就", description = "根据稀有度查询成就列表")
public Result<List<AchievementResponse>> getByRarity(@PathVariable String rarity) { @GetMapping("/byRarity")
List<Achievement> achievements = achievementService.getByRarity(rarity); public Result<List<AchievementResponse>> getByRarity(@RequestParam String rarity) {
List<AchievementResponse> responses = achievements.stream() List<AchievementResponse> responses = achievementService.getByRarityWithResponse(rarity);
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 查询已解锁的成就 * 查询已解锁的成就
*/ */
@Operation(summary = "查询已解锁的成就", description = "查询已解锁的成就列表")
@GetMapping("/unlocked") @GetMapping("/unlocked")
public Result<List<AchievementResponse>> getUnlockedAchievements() { public Result<List<AchievementResponse>> getUnlockedAchievements() {
List<Achievement> achievements = achievementService.getUnlockedAchievements(); List<AchievementResponse> responses = achievementService.getUnlockedAchievementsWithResponse();
List<AchievementResponse> responses = achievements.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 查询未解锁的成就 * 查询未解锁的成就
*/ */
@Operation(summary = "查询未解锁的成就", description = "查询未解锁的成就列表")
@GetMapping("/locked") @GetMapping("/locked")
public Result<List<AchievementResponse>> getLockedAchievements() { public Result<List<AchievementResponse>> getLockedAchievements() {
List<Achievement> achievements = achievementService.getLockedAchievements(); List<AchievementResponse> responses = achievementService.getLockedAchievementsWithResponse();
List<AchievementResponse> responses = achievements.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 统计已解锁成就数量 * 统计已解锁成就数量
*/ */
@GetMapping("/count/unlocked") @Operation(summary = "统计已解锁成就数量", description = "统计已解锁成就数量")
@GetMapping("/unlockedCount")
public Result<Long> countUnlockedAchievements() { public Result<Long> countUnlockedAchievements() {
Long count = achievementService.countUnlockedAchievements(); Long count = achievementService.countUnlockedAchievements();
return Result.success(count); return Result.success(count);
@@ -161,7 +143,8 @@ public class AchievementController {
/** /**
* 统计未解锁成就数量 * 统计未解锁成就数量
*/ */
@GetMapping("/count/locked") @Operation(summary = "统计未解锁成就数量", description = "统计未解锁成就数量")
@GetMapping("/lockedCount")
public Result<Long> countLockedAchievements() { public Result<Long> countLockedAchievements() {
Long count = achievementService.countLockedAchievements(); Long count = achievementService.countLockedAchievements();
return Result.success(count); return Result.success(count);
@@ -170,9 +153,10 @@ public class AchievementController {
/** /**
* 解锁成就 * 解锁成就
*/ */
@PutMapping("/{id}/unlock") @Operation(summary = "解锁成就", description = "解锁指定成就")
public Result<Void> unlockAchievement(@PathVariable String id) { @PutMapping("/unlock")
boolean unlocked = achievementService.unlockAchievement(id, java.time.LocalDateTime.now()); public Result<Void> unlockAchievement(@RequestBody @Validated AchievementUnlockRequest request) {
boolean unlocked = achievementService.unlockAchievement(request.getId(), LocalDateTime.now());
if (!unlocked) { if (!unlocked) {
return Result.error("解锁失败"); return Result.error("解锁失败");
} }
@@ -182,9 +166,10 @@ public class AchievementController {
/** /**
* 更新成就进度 * 更新成就进度
*/ */
@PutMapping("/{id}/progress") @Operation(summary = "更新成就进度", description = "更新指定成就的进度")
public Result<Void> updateProgress(@PathVariable String id, @RequestParam Double progress) { @PutMapping("/updateProgress")
boolean updated = achievementService.updateProgress(id, progress); public Result<Void> updateProgress(@RequestBody @Validated AchievementProgressUpdateRequest request) {
boolean updated = achievementService.updateProgress(request.getId(), request.getProgress());
if (!updated) { if (!updated) {
return Result.error("更新进度失败"); return Result.error("更新进度失败");
} }
@@ -192,36 +177,12 @@ public class AchievementController {
} }
/** /**
* 转换为响应对象 * 查询最近解锁的成就
*/ */
private AchievementResponse convertToResponse(Achievement achievement) { @Operation(summary = "查询最近解锁的成就", description = "查询最近解锁的成就列表")
AchievementResponse response = new AchievementResponse(); @GetMapping("/recent")
BeanUtils.copyProperties(achievement, response); public Result<List<AchievementResponse>> getRecentlyUnlocked(@RequestParam(defaultValue = "10") Integer limit) {
response.setId(achievement.getId()); List<AchievementResponse> responses = achievementService.getRecentlyUnlockedWithResponse(limit);
if (achievement.getCreateTime() != null) { return Result.success(responses);
response.setCreateTime(achievement.getCreateTime().format(DATE_TIME_FORMATTER));
}
if (achievement.getUpdateTime() != null) {
response.setUpdateTime(achievement.getUpdateTime().format(DATE_TIME_FORMATTER));
}
return response;
} }
}
/**
* 成就响应类
*/
@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;
}
}
@@ -5,6 +5,8 @@ import com.emotion.dto.request.AiChatRequest;
import com.emotion.dto.request.AiSummaryRequest; import com.emotion.dto.request.AiSummaryRequest;
import com.emotion.dto.request.GuestChatRequest; import com.emotion.dto.request.GuestChatRequest;
import com.emotion.dto.request.ConversationCreateRequest; 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.AiChatResponse;
import com.emotion.dto.response.AiSummaryResponse; import com.emotion.dto.response.AiSummaryResponse;
import com.emotion.dto.response.AiStatusResponse; 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.GuestChatResponse;
import com.emotion.dto.response.GuestUserInfoResponse; import com.emotion.dto.response.GuestUserInfoResponse;
import com.emotion.dto.response.ConversationResponse; import com.emotion.dto.response.ConversationResponse;
import com.emotion.entity.Conversation;
import com.emotion.service.AiChatService; import com.emotion.service.AiChatService;
import com.emotion.service.MessageService; import com.emotion.util.UserContextUtils;
import com.emotion.service.ConversationService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid; import javax.validation.Valid;
import java.time.format.DateTimeFormatter;
import java.util.Map;
/** /**
* AI聊天控制器 * AI聊天控制器
@@ -40,34 +37,16 @@ public class AiChatController {
@Autowired @Autowired
private AiChatService aiChatService; 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") @PostMapping("/chat")
public Result<AiChatResponse> sendChatMessage(@Valid @RequestBody AiChatRequest request) { public Result<AiChatResponse> sendChatMessage(@Valid @RequestBody AiChatRequest request) {
log.info("收到AI聊天请求: conversationId={}, userId={}, message={}", log.info("收到AI聊天请求: conversationId={}, userId={}, message={}",
request.getConversationId(), request.getUserId(), request.getMessage()); request.getConversationId(), request.getUserId(), request.getMessage());
// 调用AI服务 // 调用AI服务
String aiReply = aiChatService.sendChatMessage(request.getConversationId(), request.getMessage(), AiChatResponse response = aiChatService.sendChatMessage(request);
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());
return Result.success(response); return Result.success(response);
} }
@@ -77,18 +56,9 @@ public class AiChatController {
@PostMapping("/summary") @PostMapping("/summary")
public Result<AiSummaryResponse> generateSummary(@Valid @RequestBody AiSummaryRequest request) { public Result<AiSummaryResponse> generateSummary(@Valid @RequestBody AiSummaryRequest request) {
log.info("收到对话总结请求: conversationId={}, userId={}", request.getConversationId(), request.getUserId()); log.info("收到对话总结请求: conversationId={}, userId={}", request.getConversationId(), request.getUserId());
// 调用AI总结服务 // 调用AI总结服务
String summary = aiChatService.generateConversationSummary(request.getConversationId(), AiSummaryResponse response = aiChatService.generateConversationSummary(request);
request.getUserId());
// 构建响应
AiSummaryResponse response = new AiSummaryResponse();
response.setConversationId(request.getConversationId());
response.setSummary(summary);
response.setUserId(request.getUserId());
response.setTimestamp(System.currentTimeMillis());
return Result.success(response); return Result.success(response);
} }
@@ -99,15 +69,7 @@ public class AiChatController {
public Result<AiStatusResponse> getServiceStatus() { public Result<AiStatusResponse> getServiceStatus() {
log.info("获取AI服务状态"); log.info("获取AI服务状态");
boolean available = aiChatService.isServiceAvailable(); AiStatusResponse response = aiChatService.getServiceStatus();
String status = aiChatService.getServiceStatus();
// 构建响应
AiStatusResponse response = new AiStatusResponse();
response.setAvailable(available);
response.setStatus(status);
response.setTimestamp(System.currentTimeMillis());
return Result.success(response); return Result.success(response);
} }
@@ -115,124 +77,48 @@ public class AiChatController {
* 获取聊天记录统计 * 获取聊天记录统计
*/ */
@GetMapping("/stats") @GetMapping("/stats")
public Result<ChatStatsResponse> getChatStats(@RequestParam(required = false) String userId, public Result<ChatStatsResponse> getChatStats(@Valid ChatStatsRequest request) {
@RequestParam(required = false) String conversationId) { log.info("获取聊天统计: userId={}, conversationId={}", request.getUserId(), request.getConversationId());
log.info("获取聊天统计: userId={}, conversationId={}", userId, conversationId);
// 构建响应 ChatStatsResponse response = aiChatService.getChatStats(request);
ChatStatsResponse response = new ChatStatsResponse(); return Result.success(response);
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<GuestChatResponse> guestChat(@Valid @RequestBody GuestChatRequest request,
HttpServletRequest httpRequest) {
String clientIp = getClientIp(httpRequest);
log.info("访客聊天请求: {}, IP: {}", request.getMessage(), clientIp);
// 调用AI服务
Map<String, Object> 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<GuestUserInfoResponse> getGuestUserInfo(HttpServletRequest request) {
String clientIp = getClientIp(request);
log.info("获取访客用户信息: IP={}", clientIp);
// 调用AI服务
Map<String, Object> 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<ConversationResponse> createConversation(@Valid @RequestBody ConversationCreateRequest request,
HttpServletRequest httpRequest) {
log.info("创建对话请求: userId={}, title={}", request.getUserId(), request.getTitle());
String clientIp = getClientIp(httpRequest);
// 调用AI服务创建对话
Map<String, Object> 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);
} }
/** /**
* 获取客户端IP * 访客聊天(不需要登录)
*/ */
private String getClientIp(HttpServletRequest request) { @PostMapping("/guestChat")
String xForwardedFor = request.getHeader("X-Forwarded-For"); public Result<GuestChatResponse> guestChat(@Valid @RequestBody GuestChatRequest request,
if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) { HttpServletRequest httpRequest) {
return xForwardedFor.split(",")[0].trim(); String clientIp = UserContextUtils.getClientIpAddress(httpRequest);
} log.info("访客聊天请求: {}, IP: {}", request.getMessage(), clientIp);
String xRealIp = request.getHeader("X-Real-IP"); GuestChatResponse response = aiChatService.guestChat(request, clientIp);
if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) { return Result.success("发送成功", response);
return xRealIp;
}
return request.getRemoteAddr();
} }
}
/**
* 获取访客用户信息
*/
@GetMapping("/guestUserInfo")
public Result<GuestUserInfoResponse> 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<ConversationResponse> 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);
}
}
@@ -9,6 +9,7 @@ import com.emotion.dto.response.CaptchaResponse;
import com.emotion.dto.response.UserInfoResponse; import com.emotion.dto.response.UserInfoResponse;
import com.emotion.service.AuthService; import com.emotion.service.AuthService;
import com.emotion.service.TokenService; import com.emotion.service.TokenService;
import com.emotion.util.UserContextUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -52,10 +53,9 @@ public class AuthController {
/** /**
* 获取当前用户信息 * 获取当前用户信息
*/ */
@GetMapping("/user/info") @GetMapping("/userInfo")
public Result<UserInfoResponse> getCurrentUserInfo(HttpServletRequest request) { public Result<UserInfoResponse> getCurrentUserInfo(HttpServletRequest request) {
String token = extractToken(request); UserInfoResponse userInfo = tokenService.getUserInfoByToken(request);
UserInfoResponse userInfo = tokenService.getUserInfoByToken(token);
return Result.success(userInfo); return Result.success(userInfo);
} }
@@ -73,15 +73,14 @@ public class AuthController {
*/ */
@PostMapping("/logout") @PostMapping("/logout")
public Result<Void> logout(HttpServletRequest request) { public Result<Void> logout(HttpServletRequest request) {
String token = extractToken(request); authService.logoutByToken(request);
authService.logoutByToken(token);
return Result.success(); return Result.success();
} }
/** /**
* 刷新访问令牌 * 刷新访问令牌
*/ */
@PostMapping("/refresh") @PostMapping("/refreshToken")
public Result<AuthResponse> refreshToken(@Valid @RequestBody RefreshTokenRequest request) { public Result<AuthResponse> refreshToken(@Valid @RequestBody RefreshTokenRequest request) {
AuthResponse response = authService.refreshToken(request.getRefreshToken()); AuthResponse response = authService.refreshToken(request.getRefreshToken());
return Result.success("令牌刷新成功", response); return Result.success("令牌刷新成功", response);
@@ -90,14 +89,9 @@ public class AuthController {
/** /**
* 验证访问令牌 * 验证访问令牌
*/ */
@GetMapping("/validate") @GetMapping("/validateToken")
public Result<Boolean> validateToken(HttpServletRequest request) { public Result<Boolean> validateToken(HttpServletRequest request) {
String token = extractToken(request); boolean isValid = authService.validateToken(request);
if (token == null) {
return Result.success(false);
}
boolean isValid = authService.validateToken(token);
return Result.success(isValid); return Result.success(isValid);
} }
@@ -106,15 +100,14 @@ public class AuthController {
*/ */
@GetMapping("/username") @GetMapping("/username")
public Result<String> getUsernameFromToken(HttpServletRequest request) { public Result<String> getUsernameFromToken(HttpServletRequest request) {
String token = extractToken(request); String username = tokenService.getUsernameByToken(request);
String username = tokenService.getUsernameByToken(token);
return Result.success(username); return Result.success(username);
} }
/** /**
* 检查账号是否存在 * 检查账号是否存在
*/ */
@GetMapping("/check-account") @GetMapping("/checkAccount")
public Result<Boolean> checkAccount(@RequestParam String account) { public Result<Boolean> checkAccount(@RequestParam String account) {
boolean exists = authService.existsByAccount(account); boolean exists = authService.existsByAccount(account);
return Result.success(exists); return Result.success(exists);
@@ -123,7 +116,7 @@ public class AuthController {
/** /**
* 检查邮箱是否存在 * 检查邮箱是否存在
*/ */
@GetMapping("/check-email") @GetMapping("/checkEmail")
public Result<Boolean> checkEmail(@RequestParam String email) { public Result<Boolean> checkEmail(@RequestParam String email) {
boolean exists = authService.existsByEmail(email); boolean exists = authService.existsByEmail(email);
return Result.success(exists); return Result.success(exists);
@@ -132,27 +125,9 @@ public class AuthController {
/** /**
* 检查手机号是否存在 * 检查手机号是否存在
*/ */
@GetMapping("/check-phone") @GetMapping("/checkPhone")
public Result<Boolean> checkPhone(@RequestParam String phone) { public Result<Boolean> checkPhone(@RequestParam String phone) {
boolean exists = authService.existsByPhone(phone); boolean exists = authService.existsByPhone(phone);
return Result.success(exists); 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;
}
} }
@@ -1,6 +1,6 @@
package com.emotion.controller; 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.dto.websocket.ConnectRequest;
import com.emotion.service.WebSocketService; import com.emotion.service.WebSocketService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -14,14 +14,15 @@ import javax.validation.Valid;
import java.security.Principal; import java.security.Principal;
/** /**
* 优化的WebSocket聊天控制器 * WebSocket聊天控制器
* 使用规范的请求对象封装参数 * 使用规范的请求对象封装参数,符合项目开发规则
* *
* @author emotion-museum * @author emotion-museum
* @date 2025-07-23 * @date 2025-09-08
*/ */
@Slf4j @Slf4j
@Controller @Controller
@MessageMapping("/chat")
public class ChatWebSocketController { public class ChatWebSocketController {
@Autowired @Autowired
@@ -29,111 +30,55 @@ public class ChatWebSocketController {
/** /**
* 处理聊天消息 * 处理聊天消息
* @param chatRequest 聊天请求对象 *
* @param headerAccessor 消息头访问器 * @param webSocketRequest WebSocket请求对象
* @param principal 用户主体 * @param headerAccessor 消息头访问器
* @param principal 用户主体
*/ */
@MessageMapping("/chat.send") @MessageMapping("/send")
public void handleChatMessage(@Valid @Payload ChatRequest chatRequest, public void handleChatMessage(@Valid @Payload WebSocketRequest webSocketRequest,
SimpMessageHeaderAccessor headerAccessor, SimpMessageHeaderAccessor headerAccessor,
Principal principal) { Principal principal) {
try { String sessionId = headerAccessor.getSessionId();
log.info("收到WebSocket聊天消息: {}", chatRequest); webSocketService.handleChatMessage(webSocketRequest, sessionId, principal);
// 获取会话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);
}
} }
/** /**
* 处理用户连接 * 处理用户连接
*
* @param connectRequest 连接请求对象 * @param connectRequest 连接请求对象
* @param headerAccessor 消息头访问器 * @param headerAccessor 消息头访问器
* @param principal 用户主体 * @param principal 用户主体
*/ */
@MessageMapping("/chat.connect") @MessageMapping("/connect")
public void connectUser(@Payload ConnectRequest connectRequest, public void connectUser(@Payload ConnectRequest connectRequest,
SimpMessageHeaderAccessor headerAccessor, SimpMessageHeaderAccessor headerAccessor,
Principal principal) { Principal principal) {
try { String sessionId = headerAccessor.getSessionId();
String sessionId = headerAccessor.getSessionId(); webSocketService.handleUserConnect(connectRequest, sessionId, principal);
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);
}
} }
/** /**
* 处理用户断开连接 * 处理用户断开连接
*
* @param headerAccessor 消息头访问器 * @param headerAccessor 消息头访问器
* @param principal 用户主体 * @param principal 用户主体
*/ */
@MessageMapping("/chat.disconnect") @MessageMapping("/disconnect")
public void disconnectUser(SimpMessageHeaderAccessor headerAccessor, Principal principal) { public void disconnectUser(SimpMessageHeaderAccessor headerAccessor, Principal principal) {
try { String sessionId = headerAccessor.getSessionId();
String sessionId = headerAccessor.getSessionId(); webSocketService.handleUserDisconnect(sessionId, principal);
log.info("用户断开WebSocket连接: sessionId={}, principal={}", sessionId, principal);
// 处理用户断开连接
webSocketService.handleUserDisconnect(sessionId, principal);
} catch (Exception e) {
log.error("处理用户WebSocket断开连接失败", e);
}
} }
/** /**
* 处理心跳消息 * 处理心跳消息
*
* @param headerAccessor 消息头访问器 * @param headerAccessor 消息头访问器
* @param principal 用户主体 * @param principal 用户主体
*/ */
@MessageMapping("/chat.heartbeat") @MessageMapping("/heartbeat")
public void heartbeat(SimpMessageHeaderAccessor headerAccessor, Principal principal) { public void heartbeat(SimpMessageHeaderAccessor headerAccessor, Principal principal) {
try { String sessionId = headerAccessor.getSessionId();
String sessionId = headerAccessor.getSessionId(); webSocketService.handleHeartbeat(sessionId, principal);
// 处理心跳消息
webSocketService.handleHeartbeat(sessionId, principal);
} catch (Exception e) {
log.error("处理WebSocket心跳失败", e);
}
} }
} }
@@ -1,22 +1,17 @@
package com.emotion.controller; package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.PageResult; import com.emotion.common.PageResult;
import com.emotion.common.Result; import com.emotion.common.Result;
import com.emotion.dto.request.PageRequest; import com.emotion.dto.request.comment.CommentCreateRequest;
import com.emotion.dto.response.BaseResponse; import com.emotion.dto.request.comment.CommentUpdateRequest;
import com.emotion.entity.Comment; import com.emotion.dto.request.comment.CommentPageRequest;
import com.emotion.dto.response.comment.CommentResponse;
import com.emotion.service.CommentService; 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.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; 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 @Autowired
private CommentService commentService; private CommentService commentService;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** /**
* 分页查询评论 * 分页查询评论
*/ */
@GetMapping("/page") @GetMapping(value = "/page")
public Result<PageResult<CommentResponse>> getPage(@Validated PageRequest request) { public Result<PageResult<CommentResponse>> getCommentPage(@Validated CommentPageRequest request) {
IPage<Comment> page = commentService.getPage(request); PageResult<CommentResponse> pageResult = commentService.getPage(request);
List<CommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CommentResponse> 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); return Result.success(pageResult);
} }
/**
* 根据帖子ID分页查询评论
*/
@GetMapping("/post/{postId}/page")
public Result<PageResult<CommentResponse>> getPageByPostId(@PathVariable String postId, @Validated PageRequest request) {
IPage<Comment> page = commentService.getPageByPostId(request, postId);
List<CommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CommentResponse> 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<PageResult<CommentResponse>> getPageByUserId(@PathVariable String userId, @Validated PageRequest request) {
IPage<Comment> page = commentService.getPageByUserId(request, userId);
List<CommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CommentResponse> 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<CommentResponse> getById(@PathVariable String id) {
Comment comment = commentService.getById(id);
if (comment == null) {
return Result.notFound("评论不存在");
}
return Result.success(convertToResponse(comment));
}
/** /**
* 创建评论 * 创建评论
*/ */
@PostMapping @PostMapping(value = "/create")
public Result<CommentResponse> create(@RequestBody @Validated CommentCreateRequest request) { public Result<CommentResponse> createComment(@RequestBody @Validated CommentCreateRequest request) {
Comment comment = commentService.createComment( // 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID
request.getPostId(), String currentUserId = UserContextUtils.getCurrentUserId();
request.getUserId(), if (currentUserId != null) {
request.getContent(), request.setUserId(currentUserId);
request.getReplyToId() }
); CommentResponse response = commentService.create(request);
return Result.success(convertToResponse(comment)); return Result.success(response);
} }
/** /**
* 更新评论 * 更新评论
*/ */
@PutMapping("/{id}") @PutMapping(value = "/update")
public Result<CommentResponse> update(@PathVariable String id, @RequestBody Comment comment) { public Result<CommentResponse> updateComment(@RequestBody @Validated CommentUpdateRequest request) {
comment.setId(id); CommentResponse response = commentService.update(request);
boolean updated = commentService.updateById(comment); return Result.success(response);
if (!updated) {
return Result.error("更新失败");
}
Comment updatedComment = commentService.getById(id);
return Result.success(convertToResponse(updatedComment));
} }
/** /**
* 删除评论 * 删除评论
*/ */
@DeleteMapping("/{id}") @DeleteMapping(value = "/delete")
public Result<Void> delete(@PathVariable String id) { public Result<Void> deleteComment(@RequestParam String id) {
boolean deleted = commentService.removeById(id); boolean deleted = commentService.delete(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
} }
@@ -145,105 +70,14 @@ public class CommentController {
} }
/** /**
* 根据帖子ID查询顶级评论 * 根据ID获取评论
*/ */
@GetMapping("/post/{postId}/top-level") @GetMapping(value = "/detail")
public Result<List<CommentResponse>> getTopLevelCommentsByPostId(@PathVariable String postId) { public Result<CommentResponse> getCommentById(@RequestParam String id) {
List<Comment> comments = commentService.getTopLevelCommentsByPostId(postId); CommentResponse response = commentService.getById(id);
List<CommentResponse> responses = comments.stream() if (response == null) {
.map(this::convertToResponse) return Result.notFound("评论不存在");
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 根据评论ID查询回复
*/
@GetMapping("/{commentId}/replies")
public Result<List<CommentResponse>> getRepliesByCommentId(@PathVariable String commentId) {
List<Comment> replies = commentService.getRepliesByCommentId(commentId);
List<CommentResponse> responses = replies.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 统计帖子的评论数量
*/
@GetMapping("/post/{postId}/count")
public Result<Long> countByPostId(@PathVariable String postId) {
Long count = commentService.countByPostId(postId);
return Result.success(count);
}
/**
* 点赞评论
*/
@PutMapping("/{id}/like")
public Result<Void> likeComment(@PathVariable String id) {
boolean updated = commentService.updateLikes(id, 1);
if (!updated) {
return Result.error("点赞失败");
} }
return Result.success(); return Result.success(response);
} }
}
/**
* 取消点赞评论
*/
@PutMapping("/{id}/unlike")
public Result<Void> 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;
}
}
@@ -1,22 +1,16 @@
package com.emotion.controller; package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.PageResult; import com.emotion.common.PageResult;
import com.emotion.common.Result; import com.emotion.common.Result;
import com.emotion.dto.request.PageRequest; import com.emotion.dto.request.community.CommunityPostCreateRequest;
import com.emotion.dto.response.BaseResponse; import com.emotion.dto.request.community.CommunityPostUpdateRequest;
import com.emotion.entity.CommunityPost; import com.emotion.dto.request.community.CommunityPostPageRequest;
import com.emotion.dto.response.community.CommunityPostResponse;
import com.emotion.service.CommunityPostService; import com.emotion.service.CommunityPostService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; 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 * @date 2025-07-23
*/ */
@RestController @RestController
@RequestMapping("/community-post") @RequestMapping("/communityPost")
public class CommunityPostController { public class CommunityPostController {
@Autowired @Autowired
private CommunityPostService communityPostService; private CommunityPostService communityPostService;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** /**
* 分页查询帖子 * 分页查询帖子
*/ */
@GetMapping("/page") @GetMapping(value = "/page")
public Result<PageResult<CommunityPostResponse>> getPage(@Validated PageRequest request) { public Result<PageResult<CommunityPostResponse>> getPage(@Validated CommunityPostPageRequest request) {
IPage<CommunityPost> page = communityPostService.getPage(request); PageResult<CommunityPostResponse> pageResult = communityPostService.getPage(request);
List<CommunityPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CommunityPostResponse> 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<PageResult<CommunityPostResponse>> getPublicPostsPage(@Validated PageRequest request) {
IPage<CommunityPost> page = communityPostService.getPublicPostsPage(request);
List<CommunityPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CommunityPostResponse> 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<PageResult<CommunityPostResponse>> getPageByUserId(@PathVariable String userId, @Validated PageRequest request) {
IPage<CommunityPost> page = communityPostService.getPageByUserId(request, userId);
List<CommunityPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CommunityPostResponse> 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); return Result.success(pageResult);
} }
/** /**
* 根据ID获取帖子 * 根据ID获取帖子
*/ */
@GetMapping("/{id}") @GetMapping(value = "/detail")
public Result<CommunityPostResponse> getById(@PathVariable String id) { public Result<CommunityPostResponse> getById(@RequestParam String id) {
CommunityPost post = communityPostService.getById(id); CommunityPostResponse response = communityPostService.getById(id);
if (post == null) { if (response == null) {
return Result.notFound("帖子不存在"); return Result.notFound("帖子不存在");
} }
// 增加浏览数 return Result.success(response);
communityPostService.incrementViewCount(id);
return Result.success(convertToResponse(post));
} }
/** /**
* 创建帖子 * 创建帖子
*/ */
@PostMapping @PostMapping(value = "/create")
public Result<CommunityPostResponse> create(@RequestBody @Validated CommunityPostCreateRequest request) { public Result<CommunityPostResponse> create(@RequestBody @Validated CommunityPostCreateRequest request) {
CommunityPost post = communityPostService.createPost( CommunityPostResponse response = communityPostService.create(request);
request.getUserId(), return Result.success(response);
request.getTitle(),
request.getContent(),
request.getType(),
request.getLocationId(),
request.getTags(),
request.getIsPrivate()
);
return Result.success(convertToResponse(post));
} }
/** /**
* 更新帖子 * 更新帖子
*/ */
@PutMapping("/{id}") @PutMapping(value = "/update")
public Result<CommunityPostResponse> update(@PathVariable String id, @RequestBody CommunityPost post) { public Result<CommunityPostResponse> update(@RequestBody @Validated CommunityPostUpdateRequest request) {
post.setId(id); CommunityPostResponse response = communityPostService.update(request);
boolean updated = communityPostService.updateById(post); return Result.success(response);
if (!updated) {
return Result.error("更新失败");
}
CommunityPost updatedPost = communityPostService.getById(id);
return Result.success(convertToResponse(updatedPost));
} }
/** /**
* 删除帖子 * 删除帖子
*/ */
@DeleteMapping("/{id}") @DeleteMapping(value = "/delete")
public Result<Void> delete(@PathVariable String id) { public Result<Void> delete(@RequestParam String id) {
boolean deleted = communityPostService.removeById(id); boolean deleted = communityPostService.delete(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
} }
return Result.success(); return Result.success();
} }
}
/**
* 根据类型查询帖子
*/
@GetMapping("/type/{type}")
public Result<List<CommunityPostResponse>> getByType(@PathVariable String type) {
List<CommunityPost> posts = communityPostService.getByType(type);
List<CommunityPostResponse> responses = posts.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 根据地点ID查询帖子
*/
@GetMapping("/location/{locationId}")
public Result<List<CommunityPostResponse>> getByLocationId(@PathVariable String locationId) {
List<CommunityPost> posts = communityPostService.getByLocationId(locationId);
List<CommunityPostResponse> responses = posts.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 查询最受欢迎的帖子
*/
@GetMapping("/popular")
public Result<List<CommunityPostResponse>> getMostLikedPosts(@RequestParam(defaultValue = "10") Integer limit) {
List<CommunityPost> posts = communityPostService.getMostLikedPosts(limit);
List<CommunityPostResponse> responses = posts.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 查询最新的帖子
*/
@GetMapping("/latest")
public Result<List<CommunityPostResponse>> getLatestPosts(@RequestParam(defaultValue = "10") Integer limit) {
List<CommunityPost> posts = communityPostService.getLatestPosts(limit);
List<CommunityPostResponse> responses = posts.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 点赞帖子
*/
@PutMapping("/{id}/like")
public Result<Void> likePost(@PathVariable String id) {
boolean updated = communityPostService.updateLikes(id, 1);
if (!updated) {
return Result.error("点赞失败");
}
return Result.success();
}
/**
* 取消点赞帖子
*/
@PutMapping("/{id}/unlike")
public Result<Void> unlikePost(@PathVariable String id) {
boolean updated = communityPostService.updateLikes(id, -1);
if (!updated) {
return Result.error("取消点赞失败");
}
return Result.success();
}
/**
* 根据标签搜索帖子
*/
@GetMapping("/search/tag")
public Result<List<CommunityPostResponse>> getByTag(@RequestParam String tag) {
List<CommunityPost> posts = communityPostService.getByTag(tag);
List<CommunityPostResponse> 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;
}
}
@@ -1,22 +1,17 @@
package com.emotion.controller; package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.PageResult; import com.emotion.common.PageResult;
import com.emotion.common.Result; import com.emotion.common.Result;
import com.emotion.dto.request.PageRequest;
import com.emotion.dto.request.ConversationCreateRequest; import com.emotion.dto.request.ConversationCreateRequest;
import com.emotion.dto.request.ConversationPageRequest;
import com.emotion.dto.response.ConversationResponse; import com.emotion.dto.response.ConversationResponse;
import com.emotion.entity.Conversation;
import com.emotion.service.ConversationService; import com.emotion.service.ConversationService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid; import javax.validation.Valid;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 对话控制器 * 对话控制器
@@ -31,95 +26,51 @@ public class ConversationController {
@Autowired @Autowired
private ConversationService conversationService; private ConversationService conversationService;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** /**
* 分页查询对话 * 分页查询对话
*/ */
@GetMapping("/page") @GetMapping(value = "/page")
public Result<PageResult<ConversationResponse>> getPage(@Valid PageRequest request) { public Result<PageResult<ConversationResponse>> getPage(@Valid ConversationPageRequest request) {
IPage<Conversation> page = conversationService.getPage(request); PageResult<ConversationResponse> pageResult = conversationService.getPageWithResponse(request);
List<ConversationResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<ConversationResponse> 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<PageResult<ConversationResponse>> getPageByUserId(@PathVariable String userId,
@Valid PageRequest request) {
IPage<Conversation> page = conversationService.getPageByUserId(request, userId);
List<ConversationResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<ConversationResponse> 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); return Result.success(pageResult);
} }
/** /**
* 根据ID获取对话 * 根据ID获取对话
*/ */
@GetMapping("/{id}") @GetMapping(value = "/detail")
public Result<ConversationResponse> getById(@PathVariable String id) { public Result<ConversationResponse> getById(@RequestParam String id) {
Conversation conversation = conversationService.getById(id); ConversationResponse response = conversationService.getConversationResponseById(id);
if (conversation == null) { if (response == null) {
return Result.notFound("对话不存在"); return Result.notFound("对话不存在");
} }
return Result.success(convertToResponse(conversation)); return Result.success(response);
} }
/** /**
* 创建对话 * 创建对话
*/ */
@PostMapping @PostMapping(value = "/create")
public Result<ConversationResponse> create(@Valid @RequestBody ConversationCreateRequest request, public Result<ConversationResponse> create(@Valid @RequestBody ConversationCreateRequest request,
HttpServletRequest httpRequest) { HttpServletRequest httpRequest) {
String clientIp = getClientIp(httpRequest); ConversationResponse response = conversationService.createConversationWithResponse(request, httpRequest);
Conversation conversation = conversationService.createConversation( return Result.success(response);
request.getUserId(),
request.getTitle(),
null // cozeConversationId
);
return Result.success(convertToResponse(conversation));
} }
/** /**
* 更新对话 * 更新对话
*/ */
@PutMapping("/{id}") @PutMapping(value = "/update")
public Result<ConversationResponse> update(@PathVariable String id, @RequestBody Conversation conversation) { public Result<ConversationResponse> update(@RequestBody ConversationCreateRequest request) {
conversation.setId(id); ConversationResponse response = conversationService.updateConversationWithResponse(request);
boolean updated = conversationService.updateById(conversation); return Result.success(response);
if (!updated) {
return Result.error("更新失败");
}
Conversation updatedConversation = conversationService.getById(id);
return Result.success(convertToResponse(updatedConversation));
} }
/** /**
* 删除对话 * 删除对话
*/ */
@DeleteMapping("/{id}") @DeleteMapping(value = "/delete")
public Result<Void> delete(@PathVariable String id) { public Result<Void> delete(@RequestParam String id) {
boolean deleted = conversationService.removeById(id); boolean deleted = conversationService.removeById(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
@@ -127,93 +78,35 @@ public class ConversationController {
return Result.success(); return Result.success();
} }
/**
* 根据用户ID查询对话
*/
@GetMapping("/user/{userId}")
public Result<List<ConversationResponse>> getByUserId(@PathVariable String userId) {
List<Conversation> conversations = conversationService.getByUserId(userId);
List<ConversationResponse> responses = conversations.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/** /**
* 获取活跃对话 * 获取活跃对话
*/ */
@GetMapping("/active") @GetMapping(value = "/active")
public Result<List<ConversationResponse>> getActiveConversations() { public Result<List<ConversationResponse>> getActiveConversations() {
// 暂时返回空列表,因为接口中没有这个方法 List<ConversationResponse> responses = conversationService.getActiveConversationsWithResponse();
return Result.success(); return Result.success(responses);
} }
/** /**
* 获取归档对话 * 获取归档对话
*/ */
@GetMapping("/archived") @GetMapping(value = "/archived")
public Result<List<ConversationResponse>> getArchivedConversations() { public Result<List<ConversationResponse>> getArchivedConversations() {
// 暂时返回空列表,因为接口中没有这个方法 List<ConversationResponse> responses = conversationService.getArchivedConversationsWithResponse();
return Result.success(responses);
}
/**
* 更新对话状态
*/
@PutMapping(value = "/status")
public Result<Void> updateConversationStatus(
@RequestParam String id,
@RequestParam String status) {
boolean updated = conversationService.updateConversationStatus(id, status);
if (!updated) {
return Result.error("更新状态失败");
}
return Result.success(); return Result.success();
} }
}
/**
* 归档对话
*/
@PutMapping("/{id}/archive")
public Result<Void> archiveConversation(@PathVariable String id) {
// 暂时返回成功,因为接口中没有这个方法
return Result.success();
}
/**
* 激活对话
*/
@PutMapping("/{id}/activate")
public Result<Void> activateConversation(@PathVariable String id) {
// 暂时返回成功,因为接口中没有这个方法
return Result.success();
}
/**
* 统计用户对话数量
*/
@GetMapping("/user/{userId}/count")
public Result<Long> 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;
}
}
@@ -1,20 +1,17 @@
package com.emotion.controller; package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.PageResult; import com.emotion.common.PageResult;
import com.emotion.common.Result; import com.emotion.common.Result;
import com.emotion.dto.request.PageRequest; import com.emotion.dto.request.coze.CozeApiCallPageRequest;
import com.emotion.dto.response.BaseResponse; import com.emotion.dto.request.coze.CozeApiCallCreateRequest;
import com.emotion.entity.CozeApiCall; import com.emotion.dto.request.coze.CozeApiCallUpdateRequest;
import com.emotion.dto.response.coze.CozeApiCallResponse;
import com.emotion.service.CozeApiCallService; import com.emotion.service.CozeApiCallService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.time.format.DateTimeFormatter; import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
/** /**
* Coze API调用记录控制器 * Coze API调用记录控制器
@@ -29,229 +26,54 @@ public class CozeApiCallController {
@Autowired @Autowired
private CozeApiCallService cozeApiCallService; private CozeApiCallService cozeApiCallService;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** /**
* 分页查询API调用记录 * 分页查询API调用记录
*/ */
@GetMapping("/page") @GetMapping(value = "/page")
public Result<PageResult<CozeApiCallResponse>> getPage(@Validated PageRequest request) { public Result<PageResult<CozeApiCallResponse>> getCozeApiCallPage(@Validated CozeApiCallPageRequest request) {
IPage<CozeApiCall> page = cozeApiCallService.getPage(request); PageResult<CozeApiCallResponse> pageResult = cozeApiCallService.getPage(request);
List<CozeApiCallResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CozeApiCallResponse> 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<PageResult<CozeApiCallResponse>> getPageByConversationId(@PathVariable String conversationId, @Validated PageRequest request) {
IPage<CozeApiCall> page = cozeApiCallService.getPageByConversationId(request, conversationId);
List<CozeApiCallResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CozeApiCallResponse> 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<PageResult<CozeApiCallResponse>> getPageByUserId(@PathVariable String userId, @Validated PageRequest request) {
IPage<CozeApiCall> page = cozeApiCallService.getPageByUserId(request, userId);
List<CozeApiCallResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<CozeApiCallResponse> 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); return Result.success(pageResult);
} }
/** /**
* 根据ID获取API调用记录 * 根据ID获取API调用记录
*/ */
@GetMapping("/{id}") @GetMapping(value = "/detail")
public Result<CozeApiCallResponse> getById(@PathVariable String id) { public Result<CozeApiCallResponse> getCozeApiCallById(@RequestParam String id) {
CozeApiCall apiCall = cozeApiCallService.getById(id); CozeApiCallResponse response = cozeApiCallService.getById(id);
if (apiCall == null) { if (response == null) {
return Result.notFound("API调用记录不存在"); return Result.notFound("API调用记录不存在");
} }
return Result.success(convertToResponse(apiCall)); return Result.success(response);
}
/**
* 根据Bot ID查询API调用记录
*/
@GetMapping("/bot/{botId}")
public Result<List<CozeApiCallResponse>> getByBotId(@PathVariable String botId) {
List<CozeApiCall> apiCalls = cozeApiCallService.getByBotId(botId);
List<CozeApiCallResponse> responses = apiCalls.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 根据状态查询API调用记录
*/
@GetMapping("/status/{status}")
public Result<List<CozeApiCallResponse>> getByStatus(@PathVariable String status) {
List<CozeApiCall> apiCalls = cozeApiCallService.getByStatus(status);
List<CozeApiCallResponse> responses = apiCalls.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 根据请求类型查询API调用记录
*/
@GetMapping("/request-type/{requestType}")
public Result<List<CozeApiCallResponse>> getByRequestType(@PathVariable String requestType) {
List<CozeApiCall> apiCalls = cozeApiCallService.getByRequestType(requestType);
List<CozeApiCallResponse> responses = apiCalls.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 统计用户的API调用次数
*/
@GetMapping("/user/{userId}/count")
public Result<Long> countByUserId(@PathVariable String userId) {
Long count = cozeApiCallService.countByUserId(userId);
return Result.success(count);
}
/**
* 统计Bot的API调用次数
*/
@GetMapping("/bot/{botId}/count")
public Result<Long> countByBotId(@PathVariable String botId) {
Long count = cozeApiCallService.countByBotId(botId);
return Result.success(count);
}
/**
* 统计指定状态的API调用次数
*/
@GetMapping("/status/{status}/count")
public Result<Long> countByStatus(@PathVariable String status) {
Long count = cozeApiCallService.countByStatus(status);
return Result.success(count);
}
/**
* 统计用户的Token使用量
*/
@GetMapping("/user/{userId}/tokens")
public Result<Long> sumTokensByUserId(@PathVariable String userId) {
Long totalTokens = cozeApiCallService.sumTokensByUserId(userId);
return Result.success(totalTokens);
}
/**
* 统计用户的API调用费用
*/
@GetMapping("/user/{userId}/cost")
public Result<java.math.BigDecimal> sumCostByUserId(@PathVariable String userId) {
java.math.BigDecimal totalCost = cozeApiCallService.sumCostByUserId(userId);
return Result.success(totalCost);
}
/**
* 查询失败的API调用记录
*/
@GetMapping("/failed")
public Result<List<CozeApiCallResponse>> getFailedCalls() {
List<CozeApiCall> apiCalls = cozeApiCallService.getFailedCalls();
List<CozeApiCallResponse> responses = apiCalls.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 查询超时的API调用记录
*/
@GetMapping("/timeout")
public Result<List<CozeApiCallResponse>> getTimeoutCalls() {
List<CozeApiCall> apiCalls = cozeApiCallService.getTimeoutCalls();
List<CozeApiCallResponse> responses = apiCalls.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses);
}
/**
* 根据追踪ID查询API调用记录
*/
@GetMapping("/trace/{traceId}")
public Result<CozeApiCallResponse> getByTraceId(@PathVariable String traceId) {
CozeApiCall apiCall = cozeApiCallService.getByTraceId(traceId);
if (apiCall == null) {
return Result.notFound("API调用记录不存在");
}
return Result.success(convertToResponse(apiCall));
} }
/** /**
* 创建API调用记录 * 创建API调用记录
*/ */
@PostMapping @PostMapping(value = "/create")
public Result<CozeApiCallResponse> create(@RequestBody CozeApiCall apiCall) { public Result<CozeApiCallResponse> createCozeApiCall(@RequestBody @Validated CozeApiCallCreateRequest request) {
boolean saved = cozeApiCallService.save(apiCall); CozeApiCallResponse response = cozeApiCallService.create(request);
if (!saved) { return Result.success(response);
return Result.error("创建失败");
}
return Result.success(convertToResponse(apiCall));
} }
/** /**
* 更新API调用记录 * 更新API调用记录
*/ */
@PutMapping("/{id}") @PutMapping(value = "/update")
public Result<CozeApiCallResponse> update(@PathVariable String id, @RequestBody CozeApiCall apiCall) { public Result<CozeApiCallResponse> updateCozeApiCall(@RequestBody @Validated CozeApiCallUpdateRequest request) {
apiCall.setId(id); CozeApiCallResponse response = cozeApiCallService.update(request);
boolean updated = cozeApiCallService.updateById(apiCall); if (response == null) {
if (!updated) { return Result.error("更新失败,记录不存在");
return Result.error("更新失败");
} }
CozeApiCall updatedApiCall = cozeApiCallService.getById(id); return Result.success(response);
return Result.success(convertToResponse(updatedApiCall));
} }
/** /**
* 删除API调用记录 * 删除API调用记录
*/ */
@DeleteMapping("/{id}") @DeleteMapping(value = "/delete")
public Result<Void> delete(@PathVariable String id) { public Result<Void> deleteCozeApiCall(@RequestParam String id) {
boolean deleted = cozeApiCallService.removeById(id); boolean deleted = cozeApiCallService.delete(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
} }
@@ -259,49 +81,47 @@ public class CozeApiCallController {
} }
/** /**
* 转换为响应对象 * 统计用户的API调用次数
*/ */
private CozeApiCallResponse convertToResponse(CozeApiCall apiCall) { @GetMapping(value = "/countByUser")
CozeApiCallResponse response = new CozeApiCallResponse(); public Result<Long> countByUserId(@RequestParam String userId) {
BeanUtils.copyProperties(apiCall, response); Long count = cozeApiCallService.countByUserId(userId);
response.setId(apiCall.getId()); return Result.success(count);
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;
} }
/** /**
* API调用记录响应类 * 统计Bot的API调用次数
*/ */
@lombok.Data @GetMapping(value = "/countByBot")
@lombok.EqualsAndHashCode(callSuper = true) public Result<Long> countByBotId(@RequestParam String botId) {
public static class CozeApiCallResponse extends BaseResponse { Long count = cozeApiCallService.countByBotId(botId);
private String conversationId; return Result.success(count);
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;
} }
}
/**
* 统计指定状态的API调用次数
*/
@GetMapping(value = "/countByStatus")
public Result<Long> countByStatus(@RequestParam String status) {
Long count = cozeApiCallService.countByStatus(status);
return Result.success(count);
}
/**
* 统计用户的Token使用量
*/
@GetMapping(value = "/tokensByUser")
public Result<Long> sumTokensByUserId(@RequestParam String userId) {
Long totalTokens = cozeApiCallService.sumTokensByUserId(userId);
return Result.success(totalTokens);
}
/**
* 统计用户的API调用费用
*/
@GetMapping(value = "/costByUser")
public Result<BigDecimal> sumCostByUserId(@RequestParam String userId) {
BigDecimal totalCost = cozeApiCallService.sumCostByUserId(userId);
return Result.success(totalCost);
}
}
@@ -1,27 +1,19 @@
package com.emotion.controller; 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.PageResult;
import com.emotion.common.Result; import com.emotion.common.Result;
import com.emotion.dto.request.DiaryCommentCreateRequest; import com.emotion.dto.request.DiaryCommentCreateRequest;
import com.emotion.dto.request.DiaryCommentPageRequest;
import com.emotion.dto.response.DiaryCommentResponse; import com.emotion.dto.response.DiaryCommentResponse;
import com.emotion.entity.DiaryComment;
import com.emotion.service.DiaryCommentService; import com.emotion.service.DiaryCommentService;
import com.emotion.service.DiaryPostService; import com.emotion.service.DiaryPostService;
import com.emotion.service.UserService; import com.emotion.util.UserContextUtils;
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.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; import javax.validation.Valid;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 日记评论控制器 * 日记评论控制器
@@ -30,7 +22,7 @@ import java.util.stream.Collectors;
* @date 2025-07-23 * @date 2025-07-23
*/ */
@RestController @RestController
@RequestMapping("/diary-comment") @RequestMapping("/diaryComment")
public class DiaryCommentController { public class DiaryCommentController {
@Autowired @Autowired
@@ -39,152 +31,79 @@ public class DiaryCommentController {
@Autowired @Autowired
private DiaryPostService diaryPostService; 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") @GetMapping(value = "/page")
public Result<PageResult<DiaryCommentResponse>> getPage(@Validated BasePageRequest request) { public Result<PageResult<DiaryCommentResponse>> getPage(@Validated DiaryCommentPageRequest request) {
IPage<DiaryComment> page = diaryCommentService.getPage(request); PageResult<DiaryCommentResponse> pageResult = diaryCommentService.getPageWithResponse(request);
List<DiaryCommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryCommentResponse> 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<PageResult<DiaryCommentResponse>> getPageByDiaryId(@PathVariable String diaryId,
@Validated BasePageRequest request) {
IPage<DiaryComment> page = diaryCommentService.getPageByDiaryId(diaryId, request);
List<DiaryCommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryCommentResponse> 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<PageResult<DiaryCommentResponse>> getPageByUserId(@PathVariable String userId,
@Validated BasePageRequest request) {
IPage<DiaryComment> page = diaryCommentService.getPageByUserId(userId, request);
List<DiaryCommentResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryCommentResponse> 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); return Result.success(pageResult);
} }
/** /**
* 获取评论树结构 * 获取评论树结构
*/ */
@GetMapping("/diary/{diaryId}/tree") @GetMapping(value = "/commentTree")
public Result<List<DiaryCommentResponse>> getCommentTree(@PathVariable String diaryId) { public Result<List<DiaryCommentResponse>> getCommentTree(@RequestParam String diaryId) {
List<DiaryComment> comments = diaryCommentService.getCommentTree(diaryId); List<DiaryCommentResponse> responses = diaryCommentService.getCommentTreeWithResponse(diaryId);
List<DiaryCommentResponse> responses = comments.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 根据ID获取评论详情 * 根据ID获取评论详情
*/ */
@GetMapping("/{id}") @GetMapping(value = "/detail")
public Result<DiaryCommentResponse> getById(@PathVariable String id) { public Result<DiaryCommentResponse> getById(@RequestParam String id) {
DiaryComment comment = diaryCommentService.getById(id); DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id);
if (comment == null) { if (response == null) {
return Result.notFound("评论不存在"); return Result.notFound("评论不存在");
} }
return Result.success(convertToResponse(comment)); return Result.success(response);
} }
/** /**
* 创建评论 * 创建评论
*/ */
@PostMapping @PostMapping(value = "/create")
public Result<DiaryCommentResponse> create(@Valid @RequestBody DiaryCommentCreateRequest request) { public Result<DiaryCommentResponse> create(@Valid @RequestBody DiaryCommentCreateRequest request) {
DiaryComment comment = diaryCommentService.createComment( // 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID
request.getDiaryId(), String currentUserId = UserContextUtils.getCurrentUserId();
request.getUserId(), if (currentUserId != null) {
request.getContent(), request.setUserId(currentUserId);
request.getImages(), }
request.getParentCommentId(), DiaryCommentResponse response = diaryCommentService.createCommentWithResponse(request);
request.getIsAnonymous()
);
// 更新日记的评论数 // 更新日记的评论数
diaryPostService.incrementCommentCount(request.getDiaryId()); diaryPostService.incrementCommentCount(request.getDiaryId());
diaryPostService.updateLastCommentTime(request.getDiaryId()); diaryPostService.updateLastCommentTime(request.getDiaryId());
return Result.success(convertToResponse(comment)); return Result.success(response);
} }
/** /**
* 更新评论 * 更新评论
*/ */
@PutMapping("/{id}") @PutMapping(value = "/update")
public Result<DiaryCommentResponse> update(@PathVariable String id, @Valid @RequestBody DiaryCommentCreateRequest request) { public Result<DiaryCommentResponse> update(@Valid @RequestBody DiaryCommentCreateRequest request) {
boolean updated = diaryCommentService.updateComment(id, request.getContent(), request.getImages()); DiaryCommentResponse response = diaryCommentService.updateCommentWithResponse(request);
if (!updated) { return Result.success(response);
return Result.error("更新失败");
}
DiaryComment updatedComment = diaryCommentService.getById(id);
return Result.success(convertToResponse(updatedComment));
} }
/** /**
* 删除评论 * 删除评论
*/ */
@DeleteMapping("/{id}") @DeleteMapping(value = "/delete")
public Result<Void> delete(@PathVariable String id) { public Result<Void> delete(@RequestParam String id) {
DiaryComment comment = diaryCommentService.getById(id);
if (comment == null) {
return Result.error("评论不存在");
}
boolean deleted = diaryCommentService.deleteComment(id); boolean deleted = diaryCommentService.deleteComment(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
} }
// 更新日记的评论数 // 更新日记的评论数
diaryPostService.decrementCommentCount(comment.getDiaryId()); DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id);
if (response != null) {
diaryPostService.decrementCommentCount(response.getDiaryId());
}
return Result.success(); return Result.success();
} }
@@ -192,20 +111,18 @@ public class DiaryCommentController {
/** /**
* 软删除评论 * 软删除评论
*/ */
@DeleteMapping("/{id}/soft") @DeleteMapping(value = "/softDelete")
public Result<Void> softDelete(@PathVariable String id) { public Result<Void> softDelete(@RequestParam String id) {
DiaryComment comment = diaryCommentService.getById(id);
if (comment == null) {
return Result.error("评论不存在");
}
boolean deleted = diaryCommentService.softDeleteComment(id); boolean deleted = diaryCommentService.softDeleteComment(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
} }
// 更新日记的评论数 // 更新日记的评论数
diaryPostService.decrementCommentCount(comment.getDiaryId()); DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id);
if (response != null) {
diaryPostService.decrementCommentCount(response.getDiaryId());
}
return Result.success(); return Result.success();
} }
@@ -213,20 +130,18 @@ public class DiaryCommentController {
/** /**
* 恢复评论 * 恢复评论
*/ */
@PutMapping("/{id}/restore") @PutMapping(value = "/restore")
public Result<Void> restore(@PathVariable String id) { public Result<Void> restore(@RequestParam String id) {
DiaryComment comment = diaryCommentService.getById(id);
if (comment == null) {
return Result.error("评论不存在");
}
boolean restored = diaryCommentService.restoreComment(id); boolean restored = diaryCommentService.restoreComment(id);
if (!restored) { if (!restored) {
return Result.error("恢复失败"); return Result.error("恢复失败");
} }
// 更新日记的评论数 // 更新日记的评论数
diaryPostService.incrementCommentCount(comment.getDiaryId()); DiaryCommentResponse response = diaryCommentService.getCommentResponseById(id);
if (response != null) {
diaryPostService.incrementCommentCount(response.getDiaryId());
}
return Result.success(); return Result.success();
} }
@@ -234,8 +149,8 @@ public class DiaryCommentController {
/** /**
* 点赞评论 * 点赞评论
*/ */
@PostMapping("/{id}/like") @PostMapping(value = "/like")
public Result<Void> like(@PathVariable String id) { public Result<Void> like(@RequestParam String id) {
boolean liked = diaryCommentService.incrementLikeCount(id); boolean liked = diaryCommentService.incrementLikeCount(id);
if (!liked) { if (!liked) {
return Result.error("点赞失败"); return Result.error("点赞失败");
@@ -246,8 +161,8 @@ public class DiaryCommentController {
/** /**
* 取消点赞评论 * 取消点赞评论
*/ */
@DeleteMapping("/{id}/like") @DeleteMapping(value = "/unlike")
public Result<Void> unlike(@PathVariable String id) { public Result<Void> unlike(@RequestParam String id) {
boolean unliked = diaryCommentService.decrementLikeCount(id); boolean unliked = diaryCommentService.decrementLikeCount(id);
if (!unliked) { if (!unliked) {
return Result.error("取消点赞失败"); return Result.error("取消点赞失败");
@@ -258,75 +173,12 @@ public class DiaryCommentController {
/** /**
* 设置置顶状态 * 设置置顶状态
*/ */
@PutMapping("/{id}/top/{isTop}") @PutMapping(value = "/setTop")
public Result<Void> setTop(@PathVariable String id, @PathVariable Integer isTop) { public Result<Void> setTop(@RequestParam String id, @RequestParam Integer isTop) {
boolean set = diaryCommentService.setTop(id, isTop); boolean set = diaryCommentService.setTop(id, isTop);
if (!set) { if (!set) {
return Result.error("设置置顶状态失败"); return Result.error("设置置顶状态失败");
} }
return Result.success(); return Result.success();
} }
}
/**
* 统计日记评论数量
*/
@GetMapping("/diary/{diaryId}/count")
public Result<Long> countByDiaryId(@PathVariable String diaryId) {
Long count = diaryCommentService.countByDiaryId(diaryId);
return Result.success(count);
}
/**
* 统计用户评论数量
*/
@GetMapping("/user/{userId}/count")
public Result<Long> countByUserId(@PathVariable String userId) {
Long count = diaryCommentService.countByUserId(userId);
return Result.success(count);
}
/**
* 统计回复数量
*/
@GetMapping("/parent/{parentCommentId}/replies/count")
public Result<Long> 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<List<String>>() {}));
}
if (comment.getMetadata() != null) {
response.setMetadata(objectMapper.readValue(comment.getMetadata(), Object.class));
}
} catch (JsonProcessingException e) {
// 忽略JSON解析错误
}
return response;
}
}
@@ -1,30 +1,18 @@
package com.emotion.controller; 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.PageResult;
import com.emotion.common.Result; import com.emotion.common.Result;
import com.emotion.dto.request.DiaryPostCreateRequest; import com.emotion.dto.request.DiaryPostCreateRequest;
import com.emotion.dto.request.DiaryPostPageRequest;
import com.emotion.dto.request.DiaryPostUpdateRequest; import com.emotion.dto.request.DiaryPostUpdateRequest;
import com.emotion.dto.response.DiaryPostResponse; import com.emotion.dto.response.DiaryPostResponse;
import com.emotion.entity.DiaryPost;
import com.emotion.service.DiaryPostService; import com.emotion.service.DiaryPostService;
import com.emotion.service.DiaryCommentService; import com.emotion.util.UserContextUtils;
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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; 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 * @date 2025-07-23
*/ */
@RestController @RestController
@RequestMapping("/diary-post") @RequestMapping("/diaryPost")
public class DiaryPostController { public class DiaryPostController {
@Autowired @Autowired
private DiaryPostService diaryPostService; 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") @GetMapping(value = "/page")
public Result<PageResult<DiaryPostResponse>> getPage(@Validated BasePageRequest request) { public Result<PageResult<DiaryPostResponse>> getPage(@Validated DiaryPostPageRequest request) {
IPage<DiaryPost> page = diaryPostService.getPage(request); return Result.success(diaryPostService.getPageWithResponse(request));
List<DiaryPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryPostResponse> 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<PageResult<DiaryPostResponse>> getPageByUserId(@PathVariable String userId,
@Validated BasePageRequest request) {
IPage<DiaryPost> page = diaryPostService.getPageByUserId(userId, request);
List<DiaryPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryPostResponse> 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<PageResult<DiaryPostResponse>> getPublicPageByUserId(@PathVariable String userId,
@Validated BasePageRequest request) {
IPage<DiaryPost> page = diaryPostService.getPublicPageByUserId(userId, request);
List<DiaryPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryPostResponse> 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<PageResult<DiaryPostResponse>> getFeaturedPage(@Validated BasePageRequest request) {
IPage<DiaryPost> page = diaryPostService.getFeaturedPage(request);
List<DiaryPostResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<DiaryPostResponse> 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获取日记详情 * 根据ID获取日记详情
*/ */
@GetMapping("/{id}") @GetMapping(value = "/detail")
public Result<DiaryPostResponse> getById(@PathVariable String id) { public Result<DiaryPostResponse> getById(@RequestParam String id) {
DiaryPost diaryPost = diaryPostService.getById(id); DiaryPostResponse response = diaryPostService.getDiaryPostResponseById(id);
if (diaryPost == null) { if (response == null) {
return Result.notFound("日记不存在"); return Result.notFound("日记不存在");
} }
return Result.success(response);
// 增加浏览数
diaryPostService.incrementViewCount(id);
return Result.success(convertToResponse(diaryPost));
} }
/** /**
* 创建日记 * 创建日记
*/ */
@PostMapping @PostMapping(value = "/create")
public Result<DiaryPostResponse> create(@Valid @RequestBody DiaryPostCreateRequest request) { public Result<DiaryPostResponse> create(@Valid @RequestBody DiaryPostCreateRequest request) {
DiaryPost diaryPost = diaryPostService.createDiaryPost(request); // 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID
return Result.success(convertToResponse(diaryPost)); String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId != null) {
request.setUserId(currentUserId);
}
return Result.success(diaryPostService.createDiaryPostWithResponse(request));
} }
/** /**
* 发表日记并生成AI评论 * 发表日记并生成AI评论
*/ */
@PostMapping("/publish") @PostMapping(value = "/publish")
public Result<DiaryPostResponse> publish(@Valid @RequestBody DiaryPostCreateRequest request) { public Result<DiaryPostResponse> publish(@Valid @RequestBody DiaryPostCreateRequest request) {
// 从上下文中获取当前用户ID,而不是直接使用请求中的用户ID
String currentUserId = UserContextUtils.getCurrentUserId();
if (currentUserId != null) {
request.setUserId(currentUserId);
}
return Result.success(diaryPostService.publishDiaryWithAiComment(request)); return Result.success(diaryPostService.publishDiaryWithAiComment(request));
} }
/** /**
* 更新日记 * 更新日记
*/ */
@PutMapping("/{id}") @PutMapping(value = "/update")
public Result<DiaryPostResponse> update(@PathVariable String id, @Valid @RequestBody DiaryPostUpdateRequest request) { public Result<DiaryPostResponse> update(@Valid @RequestBody DiaryPostUpdateRequest request) {
boolean updated = diaryPostService.updateDiaryPost(id, request); DiaryPostResponse response = diaryPostService.updateDiaryPostWithResponse(request);
if (response == null) {
if (!updated) {
return Result.error("更新失败"); return Result.error("更新失败");
} }
return Result.success(response);
DiaryPost updatedDiaryPost = diaryPostService.getById(id);
return Result.success(convertToResponse(updatedDiaryPost));
} }
/** /**
* 删除日记 * 删除日记
*/ */
@DeleteMapping("/{id}") @DeleteMapping(value = "/delete")
public Result<Void> delete(@PathVariable String id) { public Result<Void> delete(@RequestParam String id) {
boolean deleted = diaryPostService.deleteDiaryPost(id); boolean deleted = diaryPostService.deleteDiaryPost(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
@@ -198,8 +100,8 @@ public class DiaryPostController {
/** /**
* 软删除日记 * 软删除日记
*/ */
@DeleteMapping("/{id}/soft") @DeleteMapping(value = "/softDelete")
public Result<Void> softDelete(@PathVariable String id) { public Result<Void> softDelete(@RequestParam String id) {
boolean deleted = diaryPostService.softDeleteDiaryPost(id); boolean deleted = diaryPostService.softDeleteDiaryPost(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
@@ -210,8 +112,8 @@ public class DiaryPostController {
/** /**
* 恢复日记 * 恢复日记
*/ */
@PutMapping("/{id}/restore") @PutMapping(value = "/restore")
public Result<Void> restore(@PathVariable String id) { public Result<Void> restore(@RequestParam String id) {
boolean restored = diaryPostService.restoreDiaryPost(id); boolean restored = diaryPostService.restoreDiaryPost(id);
if (!restored) { if (!restored) {
return Result.error("恢复失败"); return Result.error("恢复失败");
@@ -222,8 +124,8 @@ public class DiaryPostController {
/** /**
* 点赞日记 * 点赞日记
*/ */
@PostMapping("/{id}/like") @PostMapping(value = "/like")
public Result<Void> like(@PathVariable String id) { public Result<Void> like(@RequestParam String id) {
boolean liked = diaryPostService.incrementLikeCount(id); boolean liked = diaryPostService.incrementLikeCount(id);
if (!liked) { if (!liked) {
return Result.error("点赞失败"); return Result.error("点赞失败");
@@ -234,8 +136,8 @@ public class DiaryPostController {
/** /**
* 取消点赞日记 * 取消点赞日记
*/ */
@DeleteMapping("/{id}/like") @DeleteMapping(value = "/unlike")
public Result<Void> unlike(@PathVariable String id) { public Result<Void> unlike(@RequestParam String id) {
boolean unliked = diaryPostService.decrementLikeCount(id); boolean unliked = diaryPostService.decrementLikeCount(id);
if (!unliked) { if (!unliked) {
return Result.error("取消点赞失败"); return Result.error("取消点赞失败");
@@ -246,8 +148,8 @@ public class DiaryPostController {
/** /**
* 分享日记 * 分享日记
*/ */
@PostMapping("/{id}/share") @PostMapping(value = "/share")
public Result<Void> share(@PathVariable String id) { public Result<Void> share(@RequestParam String id) {
boolean shared = diaryPostService.incrementShareCount(id); boolean shared = diaryPostService.incrementShareCount(id);
if (!shared) { if (!shared) {
return Result.error("分享失败"); return Result.error("分享失败");
@@ -258,8 +160,8 @@ public class DiaryPostController {
/** /**
* 设置精选状态 * 设置精选状态
*/ */
@PutMapping("/{id}/featured/{featured}") @PutMapping(value = "/setFeatured")
public Result<Void> setFeatured(@PathVariable String id, @PathVariable Integer featured) { public Result<Void> setFeatured(@RequestParam String id, @RequestParam Integer featured) {
boolean set = diaryPostService.setFeatured(id, featured); boolean set = diaryPostService.setFeatured(id, featured);
if (!set) { if (!set) {
return Result.error("设置精选状态失败"); return Result.error("设置精选状态失败");
@@ -270,90 +172,12 @@ public class DiaryPostController {
/** /**
* 设置置顶优先级 * 设置置顶优先级
*/ */
@PutMapping("/{id}/priority/{priority}") @PutMapping(value = "/setPriority")
public Result<Void> setPriority(@PathVariable String id, @PathVariable Integer priority) { public Result<Void> setPriority(@RequestParam String id, @RequestParam Integer priority) {
boolean set = diaryPostService.setPriority(id, priority); boolean set = diaryPostService.setPriority(id, priority);
if (!set) { if (!set) {
return Result.error("设置优先级失败"); return Result.error("设置优先级失败");
} }
return Result.success(); return Result.success();
} }
}
/**
* 统计用户日记数量
*/
@GetMapping("/user/{userId}/count")
public Result<Long> countByUserId(@PathVariable String userId) {
Long count = diaryPostService.countByUserId(userId);
return Result.success(count);
}
/**
* 统计用户公开日记数量
*/
@GetMapping("/user/{userId}/public/count")
public Result<Long> countPublicByUserId(@PathVariable String userId) {
Long count = diaryPostService.countPublicByUserId(userId);
return Result.success(count);
}
/**
* 统计精选日记数量
*/
@GetMapping("/featured/count")
public Result<Long> 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<List<String>>() {}));
}
if (diaryPost.getVideos() != null) {
response.setVideos(objectMapper.readValue(diaryPost.getVideos(), new TypeReference<List<String>>() {}));
}
if (diaryPost.getTags() != null) {
response.setTags(objectMapper.readValue(diaryPost.getTags(), new TypeReference<List<String>>() {}));
}
if (diaryPost.getAiKeywords() != null) {
response.setAiKeywords(objectMapper.readValue(diaryPost.getAiKeywords(), new TypeReference<List<String>>() {}));
}
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;
}
}
@@ -1,24 +1,22 @@
package com.emotion.controller; package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.emotion.common.PageResult; import com.emotion.common.PageResult;
import com.emotion.common.Result; import com.emotion.common.Result;
import com.emotion.dto.request.PageRequest; import com.emotion.dto.request.EmotionAnalysisCreateRequest;
import com.emotion.dto.response.BaseResponse; import com.emotion.dto.request.EmotionAnalysisPageRequest;
import com.emotion.entity.EmotionAnalysis; import com.emotion.dto.request.EmotionAnalysisUpdateRequest;
import com.emotion.dto.response.EmotionAnalysisResponse;
import com.emotion.service.EmotionAnalysisService; import com.emotion.service.EmotionAnalysisService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 情绪分析控制器 * 情绪分析控制器
@@ -33,108 +31,74 @@ public class EmotionAnalysisController {
@Autowired @Autowired
private EmotionAnalysisService emotionAnalysisService; private EmotionAnalysisService emotionAnalysisService;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** /**
* 分页查询情绪分析记录 * 分页查询情绪分析记录
*/ */
@GetMapping("/page") @GetMapping("/page")
public Result<PageResult<EmotionAnalysisResponse>> getPage(@Validated PageRequest request) { public Result<PageResult<EmotionAnalysisResponse>> getPage(@Validated EmotionAnalysisPageRequest request) {
IPage<EmotionAnalysis> page = emotionAnalysisService.getPage(request); return Result.success(emotionAnalysisService.getPageWithResponse(request));
List<EmotionAnalysisResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<EmotionAnalysisResponse> 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分页查询情绪分析记录 * 根据用户ID分页查询情绪分析记录
*/ */
@GetMapping("/user/{userId}/page") @GetMapping("/user/page")
public Result<PageResult<EmotionAnalysisResponse>> getPageByUserId(@PathVariable String userId, @Validated PageRequest request) { public Result<PageResult<EmotionAnalysisResponse>> getPageByUserId(
IPage<EmotionAnalysis> page = emotionAnalysisService.getPageByUserId(request, userId); @RequestParam String userId,
List<EmotionAnalysisResponse> responses = page.getRecords().stream() @Validated EmotionAnalysisPageRequest request) {
.map(this::convertToResponse) return Result.success(emotionAnalysisService.getPageByUserIdWithResponse(userId, request));
.collect(Collectors.toList());
PageResult<EmotionAnalysisResponse> 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获取情绪分析记录 * 根据ID获取情绪分析记录
*/ */
@GetMapping("/{id}") @GetMapping
public Result<EmotionAnalysisResponse> getById(@PathVariable String id) { public Result<EmotionAnalysisResponse> getById(@RequestParam String id) {
EmotionAnalysis analysis = emotionAnalysisService.getById(id); EmotionAnalysisResponse response = emotionAnalysisService.getEmotionAnalysisResponseById(id);
if (analysis == null) { if (response == null) {
return Result.notFound("情绪分析记录不存在"); return Result.notFound("情绪分析记录不存在");
} }
return Result.success(convertToResponse(analysis)); return Result.success(response);
} }
/** /**
* 根据消息ID获取情绪分析记录 * 根据消息ID获取情绪分析记录
*/ */
@GetMapping("/message/{messageId}") @GetMapping("/message")
public Result<EmotionAnalysisResponse> getByMessageId(@PathVariable String messageId) { public Result<EmotionAnalysisResponse> getByMessageId(@RequestParam String messageId) {
EmotionAnalysis analysis = emotionAnalysisService.getByMessageId(messageId); EmotionAnalysisResponse response = emotionAnalysisService.getEmotionAnalysisResponseByMessageId(messageId);
if (analysis == null) { if (response == null) {
return Result.notFound("情绪分析记录不存在"); return Result.notFound("情绪分析记录不存在");
} }
return Result.success(convertToResponse(analysis)); return Result.success(response);
} }
/** /**
* 创建情绪分析记录 * 创建情绪分析记录
*/ */
@PostMapping @PostMapping
public Result<EmotionAnalysisResponse> create(@RequestBody @Validated EmotionAnalysisCreateRequest request) { public Result<EmotionAnalysisResponse> create(@RequestBody @Valid EmotionAnalysisCreateRequest request) {
EmotionAnalysis analysis = emotionAnalysisService.createEmotionAnalysis( return Result.success(emotionAnalysisService.createEmotionAnalysisWithResponse(request));
request.getMessageId(),
request.getUserId(),
request.getPrimaryEmotion(),
request.getPolarity(),
request.getIntensity(),
request.getConfidence()
);
return Result.success(convertToResponse(analysis));
} }
/** /**
* 更新情绪分析记录 * 更新情绪分析记录
*/ */
@PutMapping("/{id}") @PutMapping
public Result<EmotionAnalysisResponse> update(@PathVariable String id, @RequestBody EmotionAnalysis analysis) { public Result<EmotionAnalysisResponse> update(@RequestBody @Valid EmotionAnalysisUpdateRequest request) {
analysis.setId(id); EmotionAnalysisResponse response = emotionAnalysisService.updateEmotionAnalysisWithResponse(request);
boolean updated = emotionAnalysisService.updateById(analysis); if (response == null) {
if (!updated) {
return Result.error("更新失败"); return Result.error("更新失败");
} }
EmotionAnalysis updatedAnalysis = emotionAnalysisService.getById(id); return Result.success(response);
return Result.success(convertToResponse(updatedAnalysis));
} }
/** /**
* 删除情绪分析记录 * 删除情绪分析记录
*/ */
@DeleteMapping("/{id}") @DeleteMapping
public Result<Void> delete(@PathVariable String id) { public Result<Void> delete(@RequestParam String id) {
boolean deleted = emotionAnalysisService.removeById(id); boolean deleted = emotionAnalysisService.deleteEmotionAnalysis(id);
if (!deleted) { if (!deleted) {
return Result.error("删除失败"); return Result.error("删除失败");
} }
@@ -144,59 +108,53 @@ public class EmotionAnalysisController {
/** /**
* 根据主要情绪查询分析记录 * 根据主要情绪查询分析记录
*/ */
@GetMapping("/emotion/{primaryEmotion}") @GetMapping("/emotion")
public Result<List<EmotionAnalysisResponse>> getByPrimaryEmotion(@PathVariable String primaryEmotion) { public Result<List<EmotionAnalysisResponse>> getByPrimaryEmotion(@RequestParam String primaryEmotion) {
List<EmotionAnalysis> analyses = emotionAnalysisService.getByPrimaryEmotion(primaryEmotion); List<EmotionAnalysisResponse> responses = emotionAnalysisService
List<EmotionAnalysisResponse> responses = analyses.stream() .getEmotionAnalysisResponsesByPrimaryEmotion(primaryEmotion);
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 根据情绪极性查询分析记录 * 根据情绪极性查询分析记录
*/ */
@GetMapping("/polarity/{polarity}") @GetMapping("/polarity")
public Result<List<EmotionAnalysisResponse>> getByPolarity(@PathVariable String polarity) { public Result<List<EmotionAnalysisResponse>> getByPolarity(@RequestParam String polarity) {
List<EmotionAnalysis> analyses = emotionAnalysisService.getByPolarity(polarity); List<EmotionAnalysisResponse> responses = emotionAnalysisService
List<EmotionAnalysisResponse> responses = analyses.stream() .getEmotionAnalysisResponsesByPolarity(polarity);
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 根据用户ID和情绪类型查询分析记录 * 根据用户ID和情绪类型查询分析记录
*/ */
@GetMapping("/user/{userId}/emotion/{primaryEmotion}") @GetMapping("/user/emotion")
public Result<List<EmotionAnalysisResponse>> getByUserIdAndEmotion(@PathVariable String userId, @PathVariable String primaryEmotion) { public Result<List<EmotionAnalysisResponse>> getByUserIdAndEmotion(
List<EmotionAnalysis> analyses = emotionAnalysisService.getByUserIdAndEmotion(userId, primaryEmotion); @RequestParam String userId,
List<EmotionAnalysisResponse> responses = analyses.stream() @RequestParam String primaryEmotion) {
.map(this::convertToResponse) List<EmotionAnalysisResponse> responses = emotionAnalysisService
.collect(Collectors.toList()); .getEmotionAnalysisResponsesByUserIdAndEmotion(userId, primaryEmotion);
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 根据时间范围查询用户情绪分析记录 * 根据时间范围查询用户情绪分析记录
*/ */
@GetMapping("/user/{userId}/time-range") @GetMapping("/user/time-range")
public Result<List<EmotionAnalysisResponse>> getByUserIdAndTimeRange( public Result<List<EmotionAnalysisResponse>> getByUserIdAndTimeRange(
@PathVariable String userId, @RequestParam String userId,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) { @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
List<EmotionAnalysis> analyses = emotionAnalysisService.getByUserIdAndTimeRange(userId, startTime, endTime); List<EmotionAnalysisResponse> responses = emotionAnalysisService
List<EmotionAnalysisResponse> responses = analyses.stream() .getEmotionAnalysisResponsesByUserIdAndTimeRange(userId, startTime, endTime);
.map(this::convertToResponse)
.collect(Collectors.toList());
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 统计用户的情绪分析记录数量 * 统计用户的情绪分析记录数量
*/ */
@GetMapping("/user/{userId}/count") @GetMapping("/user/count")
public Result<Long> countByUserId(@PathVariable String userId) { public Result<Long> countByUserId(@RequestParam String userId) {
Long count = emotionAnalysisService.countByUserId(userId); Long count = emotionAnalysisService.countByUserId(userId);
return Result.success(count); return Result.success(count);
} }
@@ -204,20 +162,20 @@ public class EmotionAnalysisController {
/** /**
* 查询用户最近的情绪分析记录 * 查询用户最近的情绪分析记录
*/ */
@GetMapping("/user/{userId}/recent") @GetMapping("/user/recent")
public Result<List<EmotionAnalysisResponse>> getRecentByUserId(@PathVariable String userId, @RequestParam(defaultValue = "10") Integer limit) { public Result<List<EmotionAnalysisResponse>> getRecentByUserId(
List<EmotionAnalysis> analyses = emotionAnalysisService.getRecentByUserId(userId, limit); @RequestParam String userId,
List<EmotionAnalysisResponse> responses = analyses.stream() @RequestParam(defaultValue = "10") Integer limit) {
.map(this::convertToResponse) List<EmotionAnalysisResponse> responses = emotionAnalysisService
.collect(Collectors.toList()); .getEmotionAnalysisResponsesRecentByUserId(userId, limit);
return Result.success(responses); return Result.success(responses);
} }
/** /**
* 查询用户的平均情绪强度 * 查询用户的平均情绪强度
*/ */
@GetMapping("/user/{userId}/avg-intensity") @GetMapping("/user/avg-intensity")
public Result<Double> getAvgIntensityByUserId(@PathVariable String userId) { public Result<Double> getAvgIntensityByUserId(@RequestParam String userId) {
Double avgIntensity = emotionAnalysisService.getAvgIntensityByUserId(userId); Double avgIntensity = emotionAnalysisService.getAvgIntensityByUserId(userId);
return Result.success(avgIntensity); return Result.success(avgIntensity);
} }
@@ -225,63 +183,9 @@ public class EmotionAnalysisController {
/** /**
* 查询用户最常见的情绪类型 * 查询用户最常见的情绪类型
*/ */
@GetMapping("/user/{userId}/most-frequent-emotion") @GetMapping("/user/most-frequent-emotion")
public Result<String> getMostFrequentEmotionByUserId(@PathVariable String userId) { public Result<String> getMostFrequentEmotionByUserId(@RequestParam String userId) {
String emotion = emotionAnalysisService.getMostFrequentEmotionByUserId(userId); String emotion = emotionAnalysisService.getMostFrequentEmotionByUserId(userId);
return Result.success(emotion); 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;
}
}
@@ -1,8 +1,11 @@
package com.emotion.controller; package com.emotion.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.emotion.common.PageResult;
import com.emotion.common.Result; 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.service.EmotionRecordService;
import com.emotion.util.CurrentUserUtil; import com.emotion.util.CurrentUserUtil;
import io.swagger.v3.oas.annotations.Operation; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.time.LocalDateTime; import javax.validation.Valid;
import java.util.*; 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 @PostMapping
public Result<Map<String, Object>> createRecord(@RequestBody Map<String, Object> request) { @Operation(summary = "创建情绪记录", description = "创建新的情绪记录")
log.info("创建情绪记录: {}", request); public Result<EmotionRecordResponse> createRecord(@RequestBody @Valid EmotionRecordCreateRequest request) {
log.info("创建情绪记录: userId={}", request.getUserId());
try { // 从上下文中获取当前用户ID
Map<String, Object> record = new HashMap<>(); String userId = CurrentUserUtil.requireCurrentUserId();
record.put("id", "record-" + System.currentTimeMillis()); request.setUserId(userId);
record.put("userId", request.get("userId"));
record.put("recordDate", request.get("recordDate")); EmotionRecordResponse response = emotionRecordService.createEmotionRecordWithResponse(request);
record.put("emotionType", request.get("emotionType")); return Result.success("创建成功", response);
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("创建失败");
}
} }
/** /**
@@ -66,30 +60,20 @@ public class EmotionRecordController {
*/ */
@Operation(summary = "获取用户情绪记录列表", description = "分页获取当前用户的情绪记录,按创建时间倒序") @Operation(summary = "获取用户情绪记录列表", description = "分页获取当前用户的情绪记录,按创建时间倒序")
@GetMapping("/user") @GetMapping("/user")
public Result<IPage<EmotionRecord>> getRecordList( public Result<PageResult<EmotionRecordResponse>> getRecordList(
@Parameter(description = "页码,从1开始") @RequestParam(defaultValue = "1") Integer current, @Validated EmotionRecordPageRequest request) {
@Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer size) {
try { // 从上下文中获取当前用户ID
// 从上下文中获取当前用户ID String userId = CurrentUserUtil.requireCurrentUserId();
String userId = CurrentUserUtil.requireCurrentUserId();
log.info("获取用户情绪记录列表: userId={}, current={}, size={}", userId, current, size); log.info("获取用户情绪记录列表: userId={}, current={}, size={}", userId, request.getCurrent(), request.getSize());
IPage<EmotionRecord> page = emotionRecordService.getByUserIdWithPage(userId, current, size); PageResult<EmotionRecordResponse> page = emotionRecordService.getPageByUserIdWithResponse(userId, request);
log.info("获取用户情绪记录成功: userId={}, total={}, records={}", log.info("获取用户情绪记录成功: userId={}, total={}, records={}",
userId, page.getTotal(), page.getRecords().size()); userId, page.getTotal(), page.getRecords().size());
return Result.success(page); 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());
}
} }
/** /**
@@ -97,138 +81,82 @@ public class EmotionRecordController {
*/ */
@Operation(summary = "获取用户最近情绪记录", description = "获取当前用户最近的情绪记录列表") @Operation(summary = "获取用户最近情绪记录", description = "获取当前用户最近的情绪记录列表")
@GetMapping("/user/recent") @GetMapping("/user/recent")
public Result<List<EmotionRecord>> getRecentRecords( public Result<List<EmotionRecordResponse>> getRecentRecords(
@Parameter(description = "限制数量") @RequestParam(defaultValue = "5") Integer limit) { @Parameter(description = "限制数量") @RequestParam(defaultValue = "5") Integer limit) {
try { // 从上下文中获取当前用户ID
// 从上下文中获取当前用户ID String userId = CurrentUserUtil.requireCurrentUserId();
String userId = CurrentUserUtil.requireCurrentUserId();
log.info("获取用户最近情绪记录: userId={}, limit={}", userId, limit); log.info("获取用户最近情绪记录: userId={}, limit={}", userId, limit);
List<EmotionRecord> records = emotionRecordService.getRecentByUserId(userId, limit); List<EmotionRecordResponse> records = emotionRecordService.getRecentByUserIdWithResponse(userId, limit);
log.info("获取用户最近情绪记录成功: userId={}, records={}", userId, records.size()); log.info("获取用户最近情绪记录成功: userId={}, records={}", userId, records.size());
return Result.success(records); 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());
}
} }
/** /**
* 获取情绪记录详情 * 获取情绪记录详情
*/ */
@GetMapping("/{recordId}") @Operation(summary = "获取情绪记录详情", description = "根据ID获取情绪记录详情")
public Result<Map<String, Object>> getRecord(@PathVariable String recordId) { @GetMapping
log.info("获取情绪记录详情: {}", recordId); public Result<EmotionRecordResponse> getRecord(@RequestParam String id) {
log.info("获取情绪记录详情: {}", id);
try { EmotionRecordResponse response = emotionRecordService.getEmotionRecordResponseById(id);
Map<String, Object> record = new HashMap<>(); if (response == null) {
record.put("id", recordId); return Result.notFound("情绪记录不存在");
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("获取详情失败");
} }
return Result.success(response);
} }
/** /**
* 更新情绪记录 * 更新情绪记录
*/ */
@PutMapping("/{recordId}") @Operation(summary = "更新情绪记录", description = "更新指定的情绪记录")
public Result<Map<String, Object>> updateRecord(@PathVariable String recordId, @PutMapping
@RequestBody Map<String, Object> request) { public Result<EmotionRecordResponse> updateRecord(@RequestBody @Valid EmotionRecordUpdateRequest request) {
log.info("更新情绪记录: {}", recordId); log.info("更新情绪记录: {}", request.getId());
try { EmotionRecordResponse response = emotionRecordService.updateEmotionRecordWithResponse(request);
Map<String, Object> record = new HashMap<>(); if (response == null) {
record.put("id", recordId); return Result.notFound("情绪记录不存在");
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("更新失败");
} }
return Result.success("更新成功", response);
} }
/** /**
* 删除情绪记录 * 删除情绪记录
*/ */
@DeleteMapping("/{recordId}") @Operation(summary = "删除情绪记录", description = "删除指定的情绪记录")
public Result<String> deleteRecord(@PathVariable String recordId) { @DeleteMapping
log.info("删除情绪记录: {}", recordId); public Result<String> deleteRecord(@RequestParam String id) {
log.info("删除情绪记录: {}", id);
try { boolean deleted = emotionRecordService.deleteEmotionRecord(id);
return Result.success("删除成功"); if (!deleted) {
} catch (Exception e) { return Result.notFound("情绪记录不存在");
log.error("删除情绪记录失败: {}", e.getMessage());
return Result.error("删除失败");
} }
return Result.success("删除成功");
} }
/** /**
* 获取情绪统计 * 获取情绪统计
*/ */
@GetMapping("/stats/{userId}") @Operation(summary = "获取情绪统计", description = "获取当前用户的情绪统计信息")
public Result<Map<String, Object>> getEmotionStats(@PathVariable String userId, @GetMapping("/stats")
@RequestParam(required = false) String startDate, public Result<Map<String, Object>> getEmotionStats(
@RequestParam(required = false) String endDate) { @RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
// 从上下文中获取当前用户ID
String userId = CurrentUserUtil.requireCurrentUserId();
log.info("获取情绪统计: userId={}, startDate={}, endDate={}", userId, startDate, endDate); log.info("获取情绪统计: userId={}, startDate={}, endDate={}", userId, startDate, endDate);
try { Map<String, Object> stats = emotionRecordService.getEmotionStats(userId, startDate, endDate);
Map<String, Object> stats = new HashMap<>();
return Result.success(stats);
// 情绪类型分布
Map<String, Integer> 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("获取统计失败");
}
} }
}
/**
* 获取随机情绪类型
*/
private String getRandomEmotion() {
String[] emotions = {"joy", "sadness", "anger", "fear", "surprise", "neutral"};
return emotions[new Random().nextInt(emotions.length)];
}
}
@@ -22,4 +22,4 @@ public class ChatStatsRequest extends BaseRequest {
* 会话ID * 会话ID
*/ */
private String conversationId; private String conversationId;
} }
@@ -15,6 +15,11 @@ import javax.validation.constraints.NotBlank;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ConversationCreateRequest extends BaseRequest { public class ConversationCreateRequest extends BaseRequest {
/**
* 对话ID(更新时使用)
*/
private String id;
/** /**
* 用户ID * 用户ID
*/ */
@@ -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;
}
@@ -4,7 +4,6 @@ import lombok.Data;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List; import java.util.List;
/** /**
@@ -16,6 +15,11 @@ import java.util.List;
@Data @Data
public class DiaryCommentCreateRequest { public class DiaryCommentCreateRequest {
/**
* 评论ID (用于更新操作)
*/
private String id;
/** /**
* 日记ID * 日记ID
*/ */
@@ -49,4 +53,4 @@ public class DiaryCommentCreateRequest {
*/ */
@NotNull(message = "是否匿名不能为空") @NotNull(message = "是否匿名不能为空")
private Integer isAnonymous; private Integer isAnonymous;
} }
@@ -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;
}
@@ -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;
}
@@ -2,6 +2,7 @@ package com.emotion.dto.request;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
@@ -15,6 +16,12 @@ import java.util.List;
@Data @Data
public class DiaryPostUpdateRequest { public class DiaryPostUpdateRequest {
/**
* 日记ID
*/
@NotBlank(message = "日记ID不能为空")
private String id;
/** /**
* 日记标题 * 日记标题
*/ */
@@ -88,4 +95,4 @@ public class DiaryPostUpdateRequest {
* 状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除 * 状态: draft-草稿, published-已发布, hidden-隐藏, deleted-已删除
*/ */
private String status; private String status;
} }
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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<String> tags;
/**
* 天气
*/
private String weather;
/**
* 地点
*/
private String location;
/**
* 活动
*/
private String activity;
/**
* 相关人物
*/
private String people;
/**
* 备注
*/
private String notes;
}
@@ -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;
}
@@ -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<String> tags;
/**
* 天气
*/
private String weather;
/**
* 地点
*/
private String location;
/**
* 活动
*/
private String activity;
/**
* 相关人物
*/
private String people;
/**
* 备注
*/
private String notes;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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<String> tags;
/**
* 天气
*/
private String weather;
/**
* 地点
*/
private String location;
/**
* 活动
*/
private String activity;
/**
* 相关人物
*/
private String people;
/**
* 备注
*/
private String notes;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -35,14 +35,14 @@ public class ChatRequest {
/** /**
* 发送者类型 * 发送者类型
*/ */
@NotNull(message = "发送者类型不能为空") @NotBlank(message = "发送者类型不能为空")
private SenderType senderType; private String senderType;
/** /**
* 消息类型 * 消息类型
*/ */
@NotNull(message = "消息类型不能为空") @NotBlank(message = "消息类型不能为空")
private MessageType messageType; private String messageType;
/** /**
* 会话ID(可选) * 会话ID(可选)
@@ -53,45 +53,4 @@ public class ChatRequest {
* 发送时间戳 * 发送时间戳
*/ */
private Long timestamp; 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;
}
}
} }
@@ -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; 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;
}
}
} }
@@ -2,7 +2,9 @@ package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; 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 com.emotion.entity.Achievement;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -12,14 +14,14 @@ import java.util.List;
* 成就服务接口 * 成就服务接口
* *
* @author emotion-museum * @author emotion-museum
* @date 2025-07-23 * @date 2025-09-08
*/ */
public interface AchievementService extends IService<Achievement> { public interface AchievementService extends IService<Achievement> {
/** /**
* 分页查询成就 * 分页查询成就
*/ */
IPage<Achievement> getPage(BasePageRequest request); IPage<Achievement> getPage(AchievementPageRequest request);
/** /**
* 根据分类查询成就 * 根据分类查询成就
@@ -87,32 +89,32 @@ public interface AchievementService extends IService<Achievement> {
Long countByRarity(String rarity); Long countByRarity(String rarity);
/** /**
* 查询平均进度 * 获取平均进度
*/ */
Double getAvgProgress(); Double getAvgProgress();
/** /**
* 查询指定分类的平均进度 * 根据分类获取平均进度
*/ */
Double getAvgProgressByCategory(String category); Double getAvgProgressByCategory(String category);
/** /**
* 查询最近解锁的成就 * 获取最近解锁的成就
*/ */
List<Achievement> getRecentlyUnlocked(Integer limit); List<Achievement> getRecentlyUnlocked(Integer limit);
/** /**
* 查询即将完成的成就(进度>80% * 获取接近完成的成就
*/ */
List<Achievement> getNearCompletion(); List<Achievement> getNearCompletion();
/** /**
* 查询稀有成就(稀有度为legendary或epic * 获取稀有成就
*/ */
List<Achievement> getRareAchievements(); List<Achievement> getRareAchievements();
/** /**
* 更新成就解锁状态 * 解锁成就
*/ */
boolean unlockAchievement(String id, LocalDateTime unlockedTime); boolean unlockAchievement(String id, LocalDateTime unlockedTime);
@@ -122,12 +124,59 @@ public interface AchievementService extends IService<Achievement> {
boolean updateProgress(String id, Double progress); boolean updateProgress(String id, Double progress);
/** /**
* 更新成就隐藏状态 * 更新隐藏状态
*/ */
boolean updateHiddenStatus(String id, Integer isHidden); boolean updateHiddenStatus(String id, Integer isHidden);
/** /**
* 查询推荐成就(基于分类和稀有度) * 获取推荐成就
*/ */
List<Achievement> getRecommendedAchievements(String category, String rarity, Integer limit); List<Achievement> getRecommendedAchievements(String category, String rarity, Integer limit);
}
// 新增的Response相关方法
/**
* 分页查询成就响应
*/
PageResult<AchievementResponse> getPageWithResponse(AchievementPageRequest request);
/**
* 根据ID获取成就响应
*/
AchievementResponse getAchievementResponseById(String id);
/**
* 创建成就并返回响应
*/
AchievementResponse createAchievementWithResponse(AchievementCreateRequest request);
/**
* 更新成就并返回响应
*/
AchievementResponse updateAchievementWithResponse(AchievementUpdateRequest request);
/**
* 根据分类查询成就响应
*/
List<AchievementResponse> getByCategoryWithResponse(String category);
/**
* 根据稀有度查询成就响应
*/
List<AchievementResponse> getByRarityWithResponse(String rarity);
/**
* 查询已解锁的成就响应
*/
List<AchievementResponse> getUnlockedAchievementsWithResponse();
/**
* 查询未解锁的成就响应
*/
List<AchievementResponse> getLockedAchievementsWithResponse();
/**
* 查询最近解锁的成就响应
*/
List<AchievementResponse> getRecentlyUnlockedWithResponse(Integer limit);
}
@@ -1,5 +1,7 @@
package com.emotion.service; package com.emotion.service;
import com.emotion.dto.request.*;
import com.emotion.dto.response.*;
/** /**
* AI聊天服务接口 * AI聊天服务接口
@@ -10,85 +12,93 @@ package com.emotion.service;
public interface AiChatService { public interface AiChatService {
/** /**
* 发送聊天消息(保存用户消息和AI回复) * 发送聊天消息
* @param conversationId 会话ID *
* @param message 用户消息内容 * @param request AI聊天请求
* @param userId 用户ID * @return 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回复) * WebSocket方式发送聊天消息(只保存AI回复)
*
* @param conversationId 会话ID * @param conversationId 会话ID
* @param message 用户消息内容 * @param message 用户消息内容
* @param userId 用户ID * @param userId 用户ID
* @return AI回复内容 * @return AI回复内容
*/ */
String sendChatMessageForWebSocket(String conversationId, String message, String userId); String sendChatMessageForWebSocket(String conversationId, String message, String userId);
/** /**
* WebSocket方式发送聊天消息(只保存AI回复,带messageId * WebSocket方式发送聊天消息(只保存AI回复,带messageId
*
* @param conversationId 会话ID * @param conversationId 会话ID
* @param messageId 用户消息ID * @param messageId 用户消息ID
* @param message 用户消息内容 * @param message 用户消息内容
* @param userId 用户ID * @param userId 用户ID
* @return AI回复内容 * @return AI回复内容
*/ */
String sendChatMessageForWebSocket(String conversationId, String messageId, String message, String userId); 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交互) * 发送消息到Coze AI(不保存消息,仅AI交互)
*
* @param conversationId 会话ID * @param conversationId 会话ID
* @param userMessage 用户消息内容 * @param userMessage 用户消息内容
* @param userId 用户ID * @param userId 用户ID
* @return AI回复内容 * @return AI回复内容
*/ */
String sendMessage(String conversationId, String userMessage, String userId); String sendMessage(String conversationId, String userMessage, String userId);
/**
* 访客聊天(不登录情况下)
* @param message 用户消息内容
* @param clientIp 客户端IP
* @return 包含AI回复等信息的Map
*/
java.util.Map<String, Object> guestChat(String message, String clientIp);
/**
* 创建新对话
* @param userId 用户ID
* @param title 对话标题
* @return 包含对话信息的Map
*/
java.util.Map<String, Object> createConversation(String userId, String title);
/**
* 获取访客用户信息
* @param clientIp 客户端IP
* @return 包含访客信息的Map
*/
java.util.Map<String, Object> getGuestUserInfo(String clientIp);
/** /**
* 流式聊天(暂时降级为普通聊天) * 流式聊天(暂时降级为普通聊天)
* @param conversationId 会话ID * @param conversationId 会话ID
@@ -126,4 +136,4 @@ public interface AiChatService {
* @return AI评论内容 * @return AI评论内容
*/ */
String sendSummaryMessage(String conversationId, String userMessage, String userId); String sendSummaryMessage(String conversationId, String userMessage, String userId);
} }
@@ -6,6 +6,8 @@ import com.emotion.dto.response.AuthResponse;
import com.emotion.dto.response.CaptchaResponse; import com.emotion.dto.response.CaptchaResponse;
import com.emotion.dto.response.UserInfoResponse; import com.emotion.dto.response.UserInfoResponse;
import javax.servlet.http.HttpServletRequest;
/** /**
* 认证服务接口 * 认证服务接口
* *
@@ -64,12 +66,12 @@ public interface AuthService {
boolean logout(String userId, String token); boolean logout(String userId, String token);
/** /**
* 用户登出(通过令牌 * 用户登出(通过请求
* *
* @param token 访问令牌 * @param request HTTP请求
* @return 是否登出成功 * @return 是否登出成功
*/ */
boolean logoutByToken(String token); boolean logoutByToken(HttpServletRequest request);
/** /**
* 刷新访问令牌 * 刷新访问令牌
@@ -79,6 +81,14 @@ public interface AuthService {
*/ */
AuthResponse refreshToken(String refreshToken); AuthResponse refreshToken(String refreshToken);
/**
* 验证访问令牌
*
* @param request HTTP请求
* @return 是否有效
*/
boolean validateToken(HttpServletRequest request);
/** /**
* 验证访问令牌 * 验证访问令牌
* *
@@ -126,4 +136,4 @@ public interface AuthService {
* @return 是否存在 * @return 是否存在
*/ */
boolean existsByPhone(String phone); boolean existsByPhone(String phone);
} }
@@ -1,13 +1,13 @@
package com.emotion.service; package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; 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 com.emotion.entity.Comment;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 评论服务接口 * 评论服务接口
* *
@@ -19,110 +19,25 @@ public interface CommentService extends IService<Comment> {
/** /**
* 分页查询评论 * 分页查询评论
*/ */
IPage<Comment> getPage(BasePageRequest request); PageResult<CommentResponse> getPage(CommentPageRequest request);
/** /**
* 根据帖子ID分页查询评论 * 根据ID获取评论响应
*/ */
IPage<Comment> getPageByPostId(BasePageRequest request, String postId); CommentResponse getById(String id);
/**
* 根据用户ID分页查询评论
*/
IPage<Comment> getPageByUserId(BasePageRequest request, String userId);
/**
* 根据帖子ID查询所有评论
*/
List<Comment> getByPostId(String postId);
/**
* 根据用户ID查询所有评论
*/
List<Comment> getByUserId(String userId);
/**
* 根据回复的评论ID查询回复
*/
List<Comment> getRepliesByCommentId(String replyToId);
/**
* 查询顶级评论(非回复的评论)
*/
List<Comment> getTopLevelCommentsByPostId(String postId);
/**
* 根据点赞数范围查询评论
*/
List<Comment> getByLikesRange(Integer minLikes, Integer maxLikes);
/**
* 根据时间范围查询评论
*/
List<Comment> getByTimeRange(LocalDateTime startTime, LocalDateTime endTime);
/**
* 统计帖子的评论数量
*/
Long countByPostId(String postId);
/**
* 统计用户的评论数量
*/
Long countByUserId(String userId);
/**
* 统计评论的回复数量
*/
Long countRepliesByCommentId(String commentId);
/**
* 统计帖子的顶级评论数量
*/
Long countTopLevelCommentsByPostId(String postId);
/**
* 查询最受欢迎的评论(按点赞数排序)
*/
List<Comment> getMostLikedCommentsByPostId(String postId, Integer limit);
/**
* 查询最新的评论
*/
List<Comment> getLatestCommentsByPostId(String postId, Integer limit);
/**
* 查询用户最近的评论
*/
List<Comment> getRecentByUserId(String userId, Integer limit);
/**
* 查询热门评论(按点赞数和回复数综合排序)
*/
List<Comment> getPopularCommentsByPostId(String postId, Integer limit);
/**
* 根据关键词搜索评论内容
*/
List<Comment> searchByKeyword(String keyword);
/**
* 根据帖子ID和关键词搜索评论
*/
List<Comment> searchByPostIdAndKeyword(String postId, String keyword);
/**
* 查询用户在指定帖子下的评论
*/
List<Comment> getByPostIdAndUserId(String postId, String userId);
/**
* 更新评论点赞数
*/
boolean updateLikes(String id, Integer increment);
/** /**
* 创建评论 * 创建评论
*/ */
Comment createComment(String postId, String userId, String content, String replyToId); CommentResponse create(CommentCreateRequest request);
}
/**
* 更新评论
*/
CommentResponse update(CommentUpdateRequest request);
/**
* 删除评论
*/
boolean delete(String id);
}
@@ -1,13 +1,13 @@
package com.emotion.service; package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; 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 com.emotion.entity.CommunityPost;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* 社区帖子服务接口 * 社区帖子服务接口
* *
@@ -19,141 +19,25 @@ public interface CommunityPostService extends IService<CommunityPost> {
/** /**
* 分页查询帖子 * 分页查询帖子
*/ */
IPage<CommunityPost> getPage(BasePageRequest request); PageResult<CommunityPostResponse> getPage(CommunityPostPageRequest request);
/** /**
* 分页查询公开帖子 * 根据ID获取帖子响应
*/ */
IPage<CommunityPost> getPublicPostsPage(BasePageRequest request); CommunityPostResponse getById(String id);
/**
* 根据用户ID分页查询帖子
*/
IPage<CommunityPost> getPageByUserId(BasePageRequest request, String userId);
/**
* 根据地点ID查询帖子
*/
List<CommunityPost> getByLocationId(String locationId);
/**
* 根据帖子类型查询帖子
*/
List<CommunityPost> getByType(String type);
/**
* 查询用户的私密帖子
*/
List<CommunityPost> getPrivatePostsByUserId(String userId);
/**
* 根据点赞数范围查询帖子
*/
List<CommunityPost> getByLikesRange(Integer minLikes, Integer maxLikes);
/**
* 根据浏览数范围查询帖子
*/
List<CommunityPost> getByViewRange(Integer minViews, Integer maxViews);
/**
* 根据评论数范围查询帖子
*/
List<CommunityPost> getByCommentRange(Integer minComments, Integer maxComments);
/**
* 根据时间范围查询帖子
*/
List<CommunityPost> 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<CommunityPost> getMostLikedPosts(Integer limit);
/**
* 查询最热门的帖子(按浏览数排序)
*/
List<CommunityPost> getMostViewedPosts(Integer limit);
/**
* 查询最新的帖子
*/
List<CommunityPost> getLatestPosts(Integer limit);
/**
* 查询热门帖子(综合点赞、浏览、评论)
*/
List<CommunityPost> getPopularPosts(Integer limit);
/**
* 根据标签搜索帖子
*/
List<CommunityPost> getByTag(String tag);
/**
* 根据关键词搜索帖子
*/
List<CommunityPost> searchByKeyword(String keyword);
/**
* 查询用户最近的帖子
*/
List<CommunityPost> 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<CommunityPost> getRecommendedPosts(String type, String locationId, Integer limit);
/** /**
* 创建帖子 * 创建帖子
*/ */
CommunityPost createPost(String userId, String title, String content, String type, CommunityPostResponse create(CommunityPostCreateRequest request);
String locationId, String tags, Integer isPrivate);
} /**
* 更新帖子
*/
CommunityPostResponse update(CommunityPostUpdateRequest request);
/**
* 删除帖子
*/
boolean delete(String id);
}
@@ -2,9 +2,13 @@ package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; 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 com.emotion.entity.Conversation;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@@ -19,12 +23,12 @@ public interface ConversationService extends IService<Conversation> {
/** /**
* 分页查询会话 * 分页查询会话
*/ */
IPage<Conversation> getPage(BasePageRequest request); IPage<Conversation> getPage(ConversationPageRequest request);
/** /**
* 根据用户ID分页查询会话 * 根据用户ID分页查询会话
*/ */
IPage<Conversation> getPageByUserId(BasePageRequest request, String userId); IPage<Conversation> getPageByUserId(ConversationPageRequest request);
/** /**
* 根据用户ID查询会话列表 * 根据用户ID查询会话列表
@@ -85,4 +89,60 @@ public interface ConversationService extends IService<Conversation> {
* 结束会话 * 结束会话
*/ */
boolean endConversation(String conversationId); boolean endConversation(String conversationId);
/**
* 分页查询会话响应
*/
PageResult<ConversationResponse> getPageWithResponse(ConversationPageRequest request);
/**
* 根据用户ID分页查询会话响应
*/
PageResult<ConversationResponse> getPageByUserIdWithResponse(ConversationPageRequest request);
/**
* 根据ID获取会话响应
*/
ConversationResponse getConversationResponseById(String id);
/**
* 根据用户ID查询会话响应列表
*/
List<ConversationResponse> getByUserIdWithResponse(String userId);
/**
* 获取活跃会话响应列表
*/
List<ConversationResponse> getActiveConversationsWithResponse();
/**
* 获取归档会话响应列表
*/
List<ConversationResponse> 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);
} }
@@ -1,8 +1,11 @@
package com.emotion.service; package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; 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 com.emotion.entity.CozeApiCall;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -20,37 +23,27 @@ public interface CozeApiCallService extends IService<CozeApiCall> {
/** /**
* 分页查询API调用记录 * 分页查询API调用记录
*/ */
IPage<CozeApiCall> getPage(BasePageRequest request); PageResult<CozeApiCallResponse> getPage(CozeApiCallPageRequest request);
/** /**
* 根据会话ID分页查询API调用记录 * 根据ID获取API调用记录
*/ */
IPage<CozeApiCall> getPageByConversationId(BasePageRequest request, String conversationId); CozeApiCallResponse getById(String id);
/** /**
* 根据用户ID分页查询API调用记录 * 创建API调用记录
*/ */
IPage<CozeApiCall> getPageByUserId(BasePageRequest request, String userId); CozeApiCallResponse create(CozeApiCallCreateRequest request);
/** /**
* 根据Bot ID查询API调用记录 * 更新API调用记录
*/ */
List<CozeApiCall> getByBotId(String botId); CozeApiCallResponse update(CozeApiCallUpdateRequest request);
/** /**
* 根据状态查询API调用记录 * 删除API调用记录
*/ */
List<CozeApiCall> getByStatus(String status); boolean delete(String id);
/**
* 根据请求类型查询API调用记录
*/
List<CozeApiCall> getByRequestType(String requestType);
/**
* 根据时间范围查询API调用记录
*/
List<CozeApiCall> getByTimeRange(LocalDateTime startTime, LocalDateTime endTime);
/** /**
* 统计用户的API调用次数 * 统计用户的API调用次数
@@ -76,42 +69,4 @@ public interface CozeApiCallService extends IService<CozeApiCall> {
* 统计用户的API调用费用 * 统计用户的API调用费用
*/ */
BigDecimal sumCostByUserId(String userId); BigDecimal sumCostByUserId(String userId);
}
/**
* 查询失败的API调用记录
*/
List<CozeApiCall> getFailedCalls();
/**
* 查询超时的API调用记录
*/
List<CozeApiCall> getTimeoutCalls();
/**
* 根据追踪ID查询API调用记录
*/
CozeApiCall getByTraceId(String traceId);
/**
* 根据会话ID和请求类型查询API调用记录
*/
List<CozeApiCall> 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);
}
@@ -2,10 +2,12 @@ package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; 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 com.emotion.entity.DiaryComment;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
/** /**
@@ -19,100 +21,28 @@ public interface DiaryCommentService extends IService<DiaryComment> {
/** /**
* 分页查询评论 * 分页查询评论
*/ */
IPage<DiaryComment> getPage(BasePageRequest request); PageResult<DiaryCommentResponse> getPageWithResponse(DiaryCommentPageRequest request);
/** /**
* 根据日记ID分页查询评论 * 根据ID获取评论响应
*/ */
IPage<DiaryComment> getPageByDiaryId(String diaryId, BasePageRequest request); DiaryCommentResponse getCommentResponseById(String id);
/** /**
* 根据用户ID分页查询评论 * 获取评论树结构响应
*/ */
IPage<DiaryComment> getPageByUserId(String userId, BasePageRequest request); List<DiaryCommentResponse> getCommentTreeWithResponse(String diaryId);
/** /**
* 根据父评论ID查询回复列表 * 创建评论并返回响应对象
*/ */
List<DiaryComment> getRepliesByParentId(String parentCommentId); DiaryCommentResponse createCommentWithResponse(DiaryCommentCreateRequest request);
/** /**
* 根据评论类型查询评论列表 * 更新评论并返回响应对象
*/ */
List<DiaryComment> getByCommentType(String commentType); DiaryCommentResponse updateCommentWithResponse(DiaryCommentCreateRequest request);
/**
* 根据日记ID和评论类型查询评论列表
*/
List<DiaryComment> 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<String> 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<String> images);
/** /**
* 删除评论 * 删除评论
*/ */
@@ -129,7 +59,17 @@ public interface DiaryCommentService extends IService<DiaryComment> {
boolean restoreComment(String commentId); boolean restoreComment(String commentId);
/** /**
* 获取评论树结构 * 增加点赞数
*/ */
List<DiaryComment> getCommentTree(String diaryId); boolean incrementLikeCount(String commentId);
}
/**
* 减少点赞数
*/
boolean decrementLikeCount(String commentId);
/**
* 设置置顶状态
*/
boolean setTop(String commentId, Integer isTop);
}
@@ -1,8 +1,11 @@
package com.emotion.service; package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; 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 com.emotion.entity.DiaryPost;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -19,47 +22,37 @@ public interface DiaryPostService extends IService<DiaryPost> {
/** /**
* 分页查询日记 * 分页查询日记
*/ */
IPage<DiaryPost> getPage(BasePageRequest request); PageResult<DiaryPostResponse> getPageWithResponse(DiaryPostPageRequest request);
/**
* 根据ID获取日记响应
*/
DiaryPostResponse getDiaryPostResponseById(String id);
/**
* 创建日记并返回响应对象
*/
DiaryPostResponse createDiaryPostWithResponse(DiaryPostCreateRequest request);
/**
* 更新日记并返回响应对象
*/
DiaryPostResponse updateDiaryPostWithResponse(DiaryPostUpdateRequest request);
/**
* 删除日记
*/
boolean deleteDiaryPost(String diaryId);
/** /**
* 根据用户ID分页查询日记 * 软删除日记
*/ */
IPage<DiaryPost> getPageByUserId(String userId, BasePageRequest request); boolean softDeleteDiaryPost(String diaryId);
/** /**
* 根据用户ID查询公开日记 * 恢复日记
*/ */
IPage<DiaryPost> getPublicPageByUserId(String userId, BasePageRequest request); boolean restoreDiaryPost(String diaryId);
/**
* 查询精选日记
*/
IPage<DiaryPost> getFeaturedPage(BasePageRequest request);
/**
* 根据状态查询日记列表
*/
List<DiaryPost> getByStatus(String status);
/**
* 根据用户ID和状态查询日记列表
*/
List<DiaryPost> getByUserIdAndStatus(String userId, String status);
/**
* 根据心情状态查询日记列表
*/
List<DiaryPost> getByMood(String mood);
/**
* 根据标签查询日记列表
*/
List<DiaryPost> getByTags(List<String> tags);
/**
* 根据地点查询日记列表
*/
List<DiaryPost> getByLocation(String location);
/** /**
* 增加浏览数 * 增加浏览数
@@ -120,47 +113,9 @@ public interface DiaryPostService extends IService<DiaryPost> {
* 统计精选日记数量 * 统计精选日记数量
*/ */
Long countFeatured(); 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<String> aiKeywords, String aiSuggestions);
/** /**
* 发表日记并生成AI评论 * 发表日记并生成AI评论
* @param request 日记创建请求
* @return 日记响应对象,包含AI评论
*/ */
com.emotion.dto.response.DiaryPostResponse publishDiaryWithAiComment(com.emotion.dto.request.DiaryPostCreateRequest request); DiaryPostResponse publishDiaryWithAiComment(DiaryPostCreateRequest request);
} }
@@ -3,6 +3,11 @@ package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.emotion.common.BasePageRequest; 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.entity.EmotionAnalysis;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -96,4 +101,67 @@ public interface EmotionAnalysisService extends IService<EmotionAnalysis> {
*/ */
EmotionAnalysis createEmotionAnalysis(String messageId, String userId, String primaryEmotion, EmotionAnalysis createEmotionAnalysis(String messageId, String userId, String primaryEmotion,
String polarity, Double intensity, Double confidence); String polarity, Double intensity, Double confidence);
}
// 新增的方法
/**
* 分页查询情绪分析记录响应
*/
PageResult<EmotionAnalysisResponse> getPageWithResponse(EmotionAnalysisPageRequest request);
/**
* 根据用户ID分页查询情绪分析记录响应
*/
PageResult<EmotionAnalysisResponse> 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<EmotionAnalysisResponse> getEmotionAnalysisResponsesByPrimaryEmotion(String primaryEmotion);
/**
* 根据情绪极性查询分析记录响应
*/
List<EmotionAnalysisResponse> getEmotionAnalysisResponsesByPolarity(String polarity);
/**
* 根据用户ID和情绪类型查询分析记录响应
*/
List<EmotionAnalysisResponse> getEmotionAnalysisResponsesByUserIdAndEmotion(String userId, String primaryEmotion);
/**
* 根据时间范围查询用户情绪分析记录响应
*/
List<EmotionAnalysisResponse> getEmotionAnalysisResponsesByUserIdAndTimeRange(String userId,
LocalDateTime startTime, LocalDateTime endTime);
/**
* 查询用户最近的情绪分析记录响应
*/
List<EmotionAnalysisResponse> getEmotionAnalysisResponsesRecentByUserId(String userId, Integer limit);
}
@@ -3,10 +3,16 @@ package com.emotion.service;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.emotion.common.BasePageRequest; 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.entity.EmotionRecord;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 情绪记录服务接口 * 情绪记录服务接口
@@ -111,4 +117,46 @@ public interface EmotionRecordService extends IService<EmotionRecord> {
*/ */
EmotionRecord createEmotionRecord(String userId, String emotionType, Double intensity, EmotionRecord createEmotionRecord(String userId, String emotionType, Double intensity,
String trigger, String location, String notes); String trigger, String location, String notes);
}
// 新增的方法
/**
* 分页查询情绪记录响应
*/
PageResult<EmotionRecordResponse> getPageWithResponse(EmotionRecordPageRequest request);
/**
* 根据用户ID分页查询情绪记录响应
*/
PageResult<EmotionRecordResponse> getPageByUserIdWithResponse(String userId, EmotionRecordPageRequest request);
/**
* 根据ID获取情绪记录响应
*/
EmotionRecordResponse getEmotionRecordResponseById(String id);
/**
* 创建情绪记录并返回响应
*/
EmotionRecordResponse createEmotionRecordWithResponse(EmotionRecordCreateRequest request);
/**
* 更新情绪记录并返回响应
*/
EmotionRecordResponse updateEmotionRecordWithResponse(EmotionRecordUpdateRequest request);
/**
* 删除情绪记录
*/
boolean deleteEmotionRecord(String id);
/**
* 获取用户情绪统计
*/
Map<String, Object> getEmotionStats(String userId, String startDate, String endDate);
/**
* 查询用户最近的情绪记录并返回响应
*/
List<EmotionRecordResponse> getRecentByUserIdWithResponse(String userId, Integer limit);
}
@@ -2,6 +2,8 @@ package com.emotion.service;
import com.emotion.dto.response.UserInfoResponse; import com.emotion.dto.response.UserInfoResponse;
import javax.servlet.http.HttpServletRequest;
/** /**
* 令牌服务接口 * 令牌服务接口
* *
@@ -13,24 +15,24 @@ public interface TokenService {
/** /**
* 从请求中提取并验证令牌,获取用户信息 * 从请求中提取并验证令牌,获取用户信息
* *
* @param token 访问令牌 * @param request HTTP请求
* @return 用户信息响应 * @return 用户信息响应
*/ */
UserInfoResponse getUserInfoByToken(String token); UserInfoResponse getUserInfoByToken(HttpServletRequest request);
/** /**
* 从请求中提取并验证令牌,获取用户名 * 从请求中提取并验证令牌,获取用户名
* *
* @param token 访问令牌 * @param request HTTP请求
* @return 用户名 * @return 用户名
*/ */
String getUsernameByToken(String token); String getUsernameByToken(HttpServletRequest request);
/** /**
* 验证令牌并返回用户ID * 验证令牌并返回用户ID
* *
* @param token 访问令牌 * @param request HTTP请求
* @return 用户ID * @return 用户ID
*/ */
String validateTokenAndGetUserId(String token); String validateTokenAndGetUserId(HttpServletRequest request);
} }
@@ -1,6 +1,6 @@
package com.emotion.service; package com.emotion.service;
import com.emotion.dto.websocket.ChatRequest; import com.emotion.dto.request.WebSocketRequest;
import com.emotion.dto.websocket.ConnectRequest; import com.emotion.dto.websocket.ConnectRequest;
import java.security.Principal; import java.security.Principal;
@@ -9,32 +9,48 @@ import java.security.Principal;
* WebSocket服务接口 * WebSocket服务接口
* *
* @author emotion-museum * @author emotion-museum
* @date 2025-07-25 * @date 2025-09-08
*/ */
public interface WebSocketService { 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); void handleUserConnect(ConnectRequest request, String sessionId, Principal principal);
/** /**
* 处理用户断开连接 * 处理用户断开连接
*
* @param sessionId 会话ID
* @param principal 用户主体
*/ */
void handleUserDisconnect(String sessionId, Principal principal); void handleUserDisconnect(String sessionId, Principal principal);
/** /**
* 处理心跳消息 * 处理心跳消息
*
* @param sessionId 会话ID
* @param principal 用户主体
*/ */
void handleHeartbeat(String sessionId, Principal principal); void handleHeartbeat(String sessionId, Principal principal);
/** /**
* 获取在线用户数量 * 获取在线用户数量
*
* @return 在线用户数量
*/ */
int getOnlineUserCount(); int getOnlineUserCount();
} }
@@ -5,27 +5,41 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.entity.Achievement;
import com.emotion.mapper.AchievementMapper; import com.emotion.mapper.AchievementMapper;
import com.emotion.service.AchievementService; 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.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 成就服务实现类 * 成就服务实现类
* *
* @author emotion-museum * @author emotion-museum
* @date 2025-07-23 * @date 2025-09-08
*/ */
@Service @Service
public class AchievementServiceImpl extends ServiceImpl<AchievementMapper, Achievement> implements AchievementService { public class AchievementServiceImpl extends ServiceImpl<AchievementMapper, Achievement> implements AchievementService {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<Achievement> getPage(BasePageRequest request) { public IPage<Achievement> getPage(AchievementPageRequest request) {
Page<Achievement> page = new Page<>(request.getCurrent(), request.getSize()); Page<Achievement> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<Achievement> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Achievement> wrapper = new LambdaQueryWrapper<>();
@@ -35,6 +49,30 @@ public class AchievementServiceImpl extends ServiceImpl<AchievementMapper, Achie
.or().like(Achievement::getDescription, request.getKeyword())); .or().like(Achievement::getDescription, request.getKeyword()));
} }
// 分类筛选
if (StringUtils.hasText(request.getCategory())) {
wrapper.eq(Achievement::getCategory, request.getCategory());
}
// 稀有度筛选
if (StringUtils.hasText(request.getRarity())) {
wrapper.eq(Achievement::getRarity, request.getRarity());
}
// 解锁状态筛选
if (request.getUnlocked() != null) {
if (request.getUnlocked()) {
wrapper.isNotNull(Achievement::getUnlockedTime);
} else {
wrapper.isNull(Achievement::getUnlockedTime);
}
}
// 隐藏状态筛选
if (request.getHidden() != null) {
wrapper.eq(Achievement::getIsHidden, request.getHidden() ? 1 : 0);
}
wrapper.eq(Achievement::getIsDeleted, 0); wrapper.eq(Achievement::getIsDeleted, 0);
// 排序 // 排序
@@ -256,4 +294,147 @@ public class AchievementServiceImpl extends ServiceImpl<AchievementMapper, Achie
.last("LIMIT " + limit); .last("LIMIT " + limit);
return this.list(wrapper); return this.list(wrapper);
} }
}
// 新增的Response相关方法实现
@Override
public PageResult<AchievementResponse> getPageWithResponse(AchievementPageRequest request) {
IPage<Achievement> page = getPage(request);
List<AchievementResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<AchievementResponse> 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<AchievementResponse> getByCategoryWithResponse(String category) {
List<Achievement> achievements = getByCategory(category);
return achievements.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<AchievementResponse> getByRarityWithResponse(String rarity) {
List<Achievement> achievements = getByRarity(rarity);
return achievements.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<AchievementResponse> getUnlockedAchievementsWithResponse() {
List<Achievement> achievements = getUnlockedAchievements();
return achievements.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<AchievementResponse> getLockedAchievementsWithResponse() {
List<Achievement> achievements = getLockedAchievements();
return achievements.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<AchievementResponse> getRecentlyUnlockedWithResponse(Integer limit) {
List<Achievement> 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;
}
}
File diff suppressed because it is too large Load Diff
@@ -13,6 +13,7 @@ import com.emotion.exception.TokenException;
import com.emotion.service.AuthService; import com.emotion.service.AuthService;
import com.emotion.service.UserService; import com.emotion.service.UserService;
import com.emotion.util.JwtUtil; import com.emotion.util.JwtUtil;
import com.emotion.util.TokenUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +23,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -55,6 +57,9 @@ public class AuthServiceImpl implements AuthService {
@Autowired @Autowired
private JwtUtil jwtUtil; private JwtUtil jwtUtil;
@Autowired
private TokenUtil tokenUtil;
private static final String CAPTCHA_PREFIX = "captcha:"; private static final String CAPTCHA_PREFIX = "captcha:";
private static final String TOKEN_PREFIX = "token:"; private static final String TOKEN_PREFIX = "token:";
private static final String REFRESH_TOKEN_PREFIX = "refresh_token:"; private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
@@ -239,7 +244,8 @@ public class AuthServiceImpl implements AuthService {
} }
@Override @Override
public boolean logoutByToken(String token) { public boolean logoutByToken(HttpServletRequest request) {
String token = tokenUtil.extractToken(request);
String userId = validateTokenAndGetUserId(token); String userId = validateTokenAndGetUserId(token);
return logout(userId, token); return logout(userId, token);
} }
@@ -248,7 +254,7 @@ public class AuthServiceImpl implements AuthService {
* 验证令牌并获取用户ID * 验证令牌并获取用户ID
*/ */
private String validateTokenAndGetUserId(String token) { private String validateTokenAndGetUserId(String token) {
if (!StringUtils.hasText(token)) { if (!tokenUtil.isValidToken(token)) {
throw new TokenException("未提供访问令牌"); throw new TokenException("未提供访问令牌");
} }
@@ -293,9 +299,15 @@ public class AuthServiceImpl implements AuthService {
return response; return response;
} }
@Override
public boolean validateToken(HttpServletRequest request) {
String token = tokenUtil.extractToken(request);
return validateToken(token);
}
@Override @Override
public boolean validateToken(String token) { public boolean validateToken(String token) {
if (!StringUtils.hasText(token)) { if (!tokenUtil.isValidToken(token)) {
return false; return false;
} }
@@ -314,7 +326,7 @@ public class AuthServiceImpl implements AuthService {
@Override @Override
public String getUserIdFromToken(String token) { public String getUserIdFromToken(String token) {
if (!StringUtils.hasText(token)) { if (!tokenUtil.isValidToken(token)) {
return null; return null;
} }
@@ -336,7 +348,7 @@ public class AuthServiceImpl implements AuthService {
@Override @Override
public String getUsernameFromToken(String token) { public String getUsernameFromToken(String token) {
if (!StringUtils.hasText(token)) { if (!tokenUtil.isValidToken(token)) {
return null; return null;
} }
@@ -498,4 +510,4 @@ public class AuthServiceImpl implements AuthService {
User user = userService.getByPhone(phone); User user = userService.getByPhone(phone);
return user != null; return user != null;
} }
} }
@@ -1,18 +1,27 @@
package com.emotion.service.impl; package com.emotion.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.entity.Comment;
import com.emotion.mapper.CommentMapper; import com.emotion.mapper.CommentMapper;
import com.emotion.service.CommentService; 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.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 评论服务实现类 * 评论服务实现类
@@ -23,219 +32,125 @@ import java.util.List;
@Service @Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService { public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<Comment> getPage(BasePageRequest request) { public PageResult<CommentResponse> getPage(CommentPageRequest request) {
Page<Comment> page = new Page<>(request.getCurrent(), request.getSize()); Page<Comment> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Comment> 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())) { if (StringUtils.hasText(request.getKeyword())) {
wrapper.like(Comment::getContent, request.getKeyword()); wrapper.like(Comment::getContent, request.getKeyword());
} }
wrapper.eq(Comment::getIsDeleted, 0).orderByDesc(Comment::getCreateTime); wrapper.eq(Comment::getIsDeleted, 0).orderByDesc(Comment::getCreateTime);
return this.page(page, wrapper); page = this.page(page, wrapper);
return convertPageToPageResult(page);
} }
@Override @Override
public IPage<Comment> getPageByPostId(BasePageRequest request, String postId) { public CommentResponse getById(String id) {
Page<Comment> page = new Page<>(request.getCurrent(), request.getSize()); Comment comment = this.getBaseMapper().selectById(id);
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>(); if (comment == null) {
wrapper.eq(Comment::getPostId, postId) return null;
.eq(Comment::getIsDeleted, 0) }
.orderByDesc(Comment::getCreateTime); return convertToResponse(comment);
return this.page(page, wrapper);
} }
@Override @Override
public IPage<Comment> getPageByUserId(BasePageRequest request, String userId) { public CommentResponse create(CommentCreateRequest request) {
Page<Comment> page = new Page<>(request.getCurrent(), request.getSize()); Comment comment = new Comment();
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>(); comment.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID
wrapper.eq(Comment::getUserId, userId) comment.setPostId(request.getPostId());
.eq(Comment::getIsDeleted, 0)
.orderByDesc(Comment::getCreateTime); // 从上下文中获取当前用户ID
return this.page(page, wrapper); 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 @Override
public List<Comment> getByPostId(String postId) { public CommentResponse update(CommentUpdateRequest request) {
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>(); Comment comment = this.getBaseMapper().selectById(request.getId());
wrapper.eq(Comment::getPostId, postId) if (comment == null) {
.eq(Comment::getIsDeleted, 0) return null;
.orderByAsc(Comment::getCreateTime); }
return this.list(wrapper);
// 只更新非空字段
if (StringUtils.hasText(request.getContent())) {
comment.setContent(request.getContent());
}
this.updateById(comment);
return convertToResponse(comment);
} }
@Override @Override
public List<Comment> getByUserId(String userId) { public boolean delete(String id) {
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>(); Comment comment = this.getBaseMapper().selectById(id);
wrapper.eq(Comment::getUserId, userId)
.eq(Comment::getIsDeleted, 0)
.orderByDesc(Comment::getCreateTime);
return this.list(wrapper);
}
@Override
public List<Comment> getRepliesByCommentId(String replyToId) {
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Comment::getReplyToId, replyToId)
.eq(Comment::getIsDeleted, 0)
.orderByAsc(Comment::getCreateTime);
return this.list(wrapper);
}
@Override
public List<Comment> getTopLevelCommentsByPostId(String postId) {
LambdaQueryWrapper<Comment> 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<Comment> getByLikesRange(Integer minLikes, Integer maxLikes) {
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
wrapper.between(Comment::getLikes, minLikes, maxLikes)
.eq(Comment::getIsDeleted, 0)
.orderByDesc(Comment::getLikes);
return this.list(wrapper);
}
@Override
public List<Comment> getByTimeRange(LocalDateTime startTime, LocalDateTime endTime) {
LambdaQueryWrapper<Comment> 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<Comment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Comment::getPostId, postId)
.eq(Comment::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public Long countByUserId(String userId) {
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Comment::getUserId, userId)
.eq(Comment::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public Long countRepliesByCommentId(String commentId) {
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Comment::getReplyToId, commentId)
.eq(Comment::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public Long countTopLevelCommentsByPostId(String postId) {
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Comment::getPostId, postId)
.isNull(Comment::getReplyToId)
.eq(Comment::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public List<Comment> getMostLikedCommentsByPostId(String postId, Integer limit) {
LambdaQueryWrapper<Comment> 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<Comment> getLatestCommentsByPostId(String postId, Integer limit) {
LambdaQueryWrapper<Comment> 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<Comment> getRecentByUserId(String userId, Integer limit) {
LambdaQueryWrapper<Comment> 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<Comment> getPopularCommentsByPostId(String postId, Integer limit) {
// 这里需要自定义SQL查询,暂时返回最新评论
LambdaQueryWrapper<Comment> 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<Comment> searchByKeyword(String keyword) {
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
wrapper.like(Comment::getContent, keyword)
.eq(Comment::getIsDeleted, 0)
.orderByDesc(Comment::getCreateTime);
return this.list(wrapper);
}
@Override
public List<Comment> searchByPostIdAndKeyword(String postId, String keyword) {
LambdaQueryWrapper<Comment> 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<Comment> getByPostIdAndUserId(String postId, String userId) {
LambdaQueryWrapper<Comment> 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);
if (comment == null) { if (comment == null) {
return false; return false;
} }
Integer newLikes = comment.getLikes() + increment; // 逻辑删除
comment.setLikes(newLikes); comment.setIsDeleted(1);
return this.updateById(comment); return this.updateById(comment);
} }
@Override /**
public Comment createComment(String postId, String userId, String content, String replyToId) { * 转换为响应对象
Comment comment = new Comment(); */
comment.setPostId(postId); private CommentResponse convertToResponse(Comment comment) {
comment.setUserId(userId); CommentResponse response = new CommentResponse();
comment.setContent(content); BeanUtils.copyProperties(comment, response);
comment.setReplyToId(replyToId); response.setId(comment.getId());
comment.setLikes(0); 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<CommentResponse> convertPageToPageResult(Page<Comment> page) {
PageResult<CommentResponse> 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;
} }
} }
@@ -1,18 +1,26 @@
package com.emotion.service.impl; package com.emotion.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.entity.CommunityPost;
import com.emotion.mapper.CommunityPostMapper; import com.emotion.mapper.CommunityPostMapper;
import com.emotion.service.CommunityPostService; 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.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 社区帖子服务实现类 * 社区帖子服务实现类
@@ -23,296 +31,149 @@ import java.util.List;
@Service @Service
public class CommunityPostServiceImpl extends ServiceImpl<CommunityPostMapper, CommunityPost> implements CommunityPostService { public class CommunityPostServiceImpl extends ServiceImpl<CommunityPostMapper, CommunityPost> implements CommunityPostService {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<CommunityPost> getPage(BasePageRequest request) { public PageResult<CommunityPostResponse> getPage(CommunityPostPageRequest request) {
Page<CommunityPost> page = new Page<>(request.getCurrent(), request.getSize()); Page<CommunityPost> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CommunityPost> 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())) { if (StringUtils.hasText(request.getKeyword())) {
wrapper.and(w -> w.like(CommunityPost::getTitle, request.getKeyword()) wrapper.and(w -> w.like(CommunityPost::getTitle, request.getKeyword())
.or().like(CommunityPost::getContent, request.getKeyword())); .or().like(CommunityPost::getContent, request.getKeyword()));
} }
wrapper.eq(CommunityPost::getIsDeleted, 0).orderByDesc(CommunityPost::getCreateTime); wrapper.eq(CommunityPost::getIsDeleted, 0).orderByDesc(CommunityPost::getCreateTime);
return this.page(page, wrapper); page = this.page(page, wrapper);
return convertPageToResponse(page);
} }
@Override @Override
public IPage<CommunityPost> getPublicPostsPage(BasePageRequest request) { public CommunityPostResponse getById(String id) {
Page<CommunityPost> page = new Page<>(request.getCurrent(), request.getSize()); CommunityPost post = this.getBaseMapper().selectById(id);
LambdaQueryWrapper<CommunityPost> 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<CommunityPost> getPageByUserId(BasePageRequest request, String userId) {
Page<CommunityPost> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getUserId, userId)
.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getCreateTime);
return this.page(page, wrapper);
}
@Override
public List<CommunityPost> getByLocationId(String locationId) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getLocationId, locationId)
.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getCreateTime);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getByType(String type) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getType, type)
.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getCreateTime);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getPrivatePostsByUserId(String userId) {
LambdaQueryWrapper<CommunityPost> 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<CommunityPost> getByLikesRange(Integer minLikes, Integer maxLikes) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.between(CommunityPost::getLikes, minLikes, maxLikes)
.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getLikes);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getByViewRange(Integer minViews, Integer maxViews) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.between(CommunityPost::getViewCount, minViews, maxViews)
.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getViewCount);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getByCommentRange(Integer minComments, Integer maxComments) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.between(CommunityPost::getCommentCount, minComments, maxComments)
.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getCommentCount);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getByTimeRange(LocalDateTime startTime, LocalDateTime endTime) {
LambdaQueryWrapper<CommunityPost> 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<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getUserId, userId)
.eq(CommunityPost::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public Long countPublicPostsByUserId(String userId) {
LambdaQueryWrapper<CommunityPost> 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<CommunityPost> 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<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getType, type)
.eq(CommunityPost::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public Long countByLocationId(String locationId) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getLocationId, locationId)
.eq(CommunityPost::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public List<CommunityPost> getMostLikedPosts(Integer limit) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getLikes)
.last("LIMIT " + limit);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getMostViewedPosts(Integer limit) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getViewCount)
.last("LIMIT " + limit);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getLatestPosts(Integer limit) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getCreateTime)
.last("LIMIT " + limit);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getPopularPosts(Integer limit) {
// 这里需要自定义SQL查询,暂时返回最新帖子
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getCreateTime)
.last("LIMIT " + limit);
return this.list(wrapper);
}
@Override
public List<CommunityPost> getByTag(String tag) {
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>();
wrapper.like(CommunityPost::getTags, tag)
.eq(CommunityPost::getIsDeleted, 0)
.orderByDesc(CommunityPost::getCreateTime);
return this.list(wrapper);
}
@Override
public List<CommunityPost> searchByKeyword(String keyword) {
LambdaQueryWrapper<CommunityPost> 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<CommunityPost> getRecentByUserId(String userId, Integer limit) {
LambdaQueryWrapper<CommunityPost> 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);
if (post == null) { 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; return convertToResponse(post);
post.setLikes(newLikes);
return this.updateById(post);
} }
@Override @Override
public boolean incrementViewCount(String id) { public CommunityPostResponse create(CommunityPostCreateRequest request) {
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) {
CommunityPost post = new CommunityPost(); CommunityPost post = new CommunityPost();
post.setId(id); post.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID
post.setIsPrivate(isPrivate); post.setUserId(request.getUserId());
return this.updateById(post); post.setTitle(request.getTitle());
} post.setContent(request.getContent());
post.setType(request.getType());
@Override post.setLocationId(request.getLocationId());
public List<CommunityPost> getRecommendedPosts(String type, String locationId, Integer limit) { post.setTags(request.getTags());
LambdaQueryWrapper<CommunityPost> wrapper = new LambdaQueryWrapper<>(); post.setIsPrivate(request.getIsPrivate() != null ? request.getIsPrivate() : 0); // 默认公开
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.setLikes(0); post.setLikes(0);
post.setViewCount(0); post.setViewCount(0);
post.setCommentCount(0); post.setCommentCount(0);
this.save(post); 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<CommunityPostResponse> convertPageToResponse(Page<CommunityPost> page) {
PageResult<CommunityPostResponse> 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;
} }
} }
@@ -4,15 +4,25 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.entity.Conversation;
import com.emotion.mapper.ConversationMapper; import com.emotion.mapper.ConversationMapper;
import com.emotion.service.ConversationService; 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.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 会话服务实现类 * 会话服务实现类
@@ -23,11 +33,29 @@ import java.util.List;
@Service @Service
public class ConversationServiceImpl extends ServiceImpl<ConversationMapper, Conversation> implements ConversationService { public class ConversationServiceImpl extends ServiceImpl<ConversationMapper, Conversation> implements ConversationService {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<Conversation> getPage(BasePageRequest request) { public IPage<Conversation> getPage(ConversationPageRequest request) {
Page<Conversation> page = new Page<>(request.getCurrent(), request.getSize()); Page<Conversation> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<Conversation> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Conversation> 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())) { if (StringUtils.hasText(request.getKeyword())) {
wrapper.and(w -> w.like(Conversation::getTitle, request.getKeyword()) wrapper.and(w -> w.like(Conversation::getTitle, request.getKeyword())
.or().like(Conversation::getSummary, request.getKeyword())); .or().like(Conversation::getSummary, request.getKeyword()));
@@ -38,10 +66,16 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationMapper, Con
} }
@Override @Override
public IPage<Conversation> getPageByUserId(BasePageRequest request, String userId) { public IPage<Conversation> getPageByUserId(ConversationPageRequest request) {
Page<Conversation> page = new Page<>(request.getCurrent(), request.getSize()); Page<Conversation> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<Conversation> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Conversation> 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) .eq(Conversation::getIsDeleted, 0)
.orderByDesc(Conversation::getCreateTime); .orderByDesc(Conversation::getCreateTime);
return this.page(page, wrapper); return this.page(page, wrapper);
@@ -148,6 +182,7 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationMapper, Con
@Override @Override
public Conversation createConversation(String userId, String title, String cozeConversationId) { public Conversation createConversation(String userId, String title, String cozeConversationId) {
Conversation conversation = new Conversation(); Conversation conversation = new Conversation();
conversation.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID
conversation.setUserId(userId); conversation.setUserId(userId);
conversation.setTitle(title); conversation.setTitle(title);
conversation.setCozeConversationId(cozeConversationId); conversation.setCozeConversationId(cozeConversationId);
@@ -172,4 +207,157 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationMapper, Con
conversation.setEndTime(LocalDateTime.now()); conversation.setEndTime(LocalDateTime.now());
return this.updateById(conversation); return this.updateById(conversation);
} }
@Override
public PageResult<ConversationResponse> getPageWithResponse(ConversationPageRequest request) {
IPage<Conversation> page = this.getPage(request);
return convertPageToResponse(page);
}
@Override
public PageResult<ConversationResponse> getPageByUserIdWithResponse(ConversationPageRequest request) {
IPage<Conversation> 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<ConversationResponse> getByUserIdWithResponse(String userId) {
List<Conversation> conversations = this.getByUserId(userId);
return conversations.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<ConversationResponse> getActiveConversationsWithResponse() {
// 实现获取活跃对话的逻辑
LambdaQueryWrapper<Conversation> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Conversation::getConversationStatus, "active")
.eq(Conversation::getIsDeleted, 0)
.orderByDesc(Conversation::getLastActiveTime);
List<Conversation> conversations = this.list(wrapper);
return conversations.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<ConversationResponse> getArchivedConversationsWithResponse() {
// 实现获取归档对话的逻辑
LambdaQueryWrapper<Conversation> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Conversation::getConversationStatus, "archived")
.eq(Conversation::getIsDeleted, 0)
.orderByDesc(Conversation::getCreateTime);
List<Conversation> 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<ConversationResponse> convertPageToResponse(IPage<Conversation> page) {
PageResult<ConversationResponse> 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;
}
} }
@@ -1,20 +1,27 @@
package com.emotion.service.impl; package com.emotion.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.entity.CozeApiCall;
import com.emotion.mapper.CozeApiCallMapper; import com.emotion.mapper.CozeApiCallMapper;
import com.emotion.service.CozeApiCallService; 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.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* Coze API调用记录服务实现类 * Coze API调用记录服务实现类
@@ -25,15 +32,46 @@ import java.util.List;
@Service @Service
public class CozeApiCallServiceImpl extends ServiceImpl<CozeApiCallMapper, CozeApiCall> implements CozeApiCallService { public class CozeApiCallServiceImpl extends ServiceImpl<CozeApiCallMapper, CozeApiCall> implements CozeApiCallService {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<CozeApiCall> getPage(BasePageRequest request) { public PageResult<CozeApiCallResponse> getPage(CozeApiCallPageRequest request) {
Page<CozeApiCall> page = new Page<>(request.getCurrent(), request.getSize()); Page<CozeApiCall> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CozeApiCall> 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())) { if (StringUtils.hasText(request.getKeyword())) {
wrapper.like(CozeApiCall::getRequestUrl, request.getKeyword()) wrapper.and(w -> w.like(CozeApiCall::getRequestUrl, request.getKeyword())
.or().like(CozeApiCall::getAiReply, request.getKeyword()); .or().like(CozeApiCall::getAiReply, request.getKeyword())
.or().like(CozeApiCall::getUserMessage, request.getKeyword()));
} }
wrapper.eq(CozeApiCall::getIsDeleted, 0); wrapper.eq(CozeApiCall::getIsDeleted, 0);
@@ -49,77 +87,193 @@ public class CozeApiCallServiceImpl extends ServiceImpl<CozeApiCallMapper, CozeA
wrapper.orderByDesc(CozeApiCall::getStartTime); wrapper.orderByDesc(CozeApiCall::getStartTime);
} }
return this.page(page, wrapper); page = this.page(page, wrapper);
return convertPageToPageResult(page);
} }
@Override @Override
public IPage<CozeApiCall> getPageByConversationId(BasePageRequest request, String conversationId) { public CozeApiCallResponse getById(String id) {
Page<CozeApiCall> page = new Page<>(request.getCurrent(), request.getSize()); CozeApiCall apiCall = this.getBaseMapper().selectById(id);
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>(); if (apiCall == null || apiCall.getIsDeleted() == 1) {
wrapper.eq(CozeApiCall::getConversationId, conversationId) return null;
.eq(CozeApiCall::getIsDeleted, 0); }
return convertToResponse(apiCall);
}
// 关键词搜索 @Override
if (StringUtils.hasText(request.getKeyword())) { public CozeApiCallResponse create(CozeApiCallCreateRequest request) {
wrapper.like(CozeApiCall::getRequestUrl, request.getKeyword()) CozeApiCall apiCall = new CozeApiCall();
.or().like(CozeApiCall::getAiReply, request.getKeyword()); 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); if (request.getConversationId() != null) {
} apiCall.setConversationId(request.getConversationId());
}
@Override if (request.getMessageId() != null) {
public IPage<CozeApiCall> getPageByUserId(BasePageRequest request, String userId) { apiCall.setMessageId(request.getMessageId());
Page<CozeApiCall> page = new Page<>(request.getCurrent(), request.getSize()); }
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>(); if (request.getCozeChatId() != null) {
wrapper.eq(CozeApiCall::getUserId, userId) apiCall.setCozeChatId(request.getCozeChatId());
.eq(CozeApiCall::getIsDeleted, 0); }
if (request.getCozeConversationId() != null) {
// 关键词搜索 apiCall.setCozeConversationId(request.getCozeConversationId());
if (StringUtils.hasText(request.getKeyword())) { }
wrapper.like(CozeApiCall::getRequestUrl, request.getKeyword()) if (request.getBotId() != null) {
.or().like(CozeApiCall::getAiReply, request.getKeyword()); 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); this.updateById(apiCall);
return this.page(page, wrapper); return convertToResponse(apiCall);
} }
@Override @Override
public List<CozeApiCall> getByBotId(String botId) { public boolean delete(String id) {
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>(); CozeApiCall apiCall = this.getBaseMapper().selectById(id);
wrapper.eq(CozeApiCall::getBotId, botId) if (apiCall == null) {
.eq(CozeApiCall::getIsDeleted, 0) return false;
.orderByDesc(CozeApiCall::getStartTime); }
return this.list(wrapper);
}
@Override // 逻辑删除
public List<CozeApiCall> getByStatus(String status) { apiCall.setIsDeleted(1);
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>(); return this.updateById(apiCall);
wrapper.eq(CozeApiCall::getStatus, status)
.eq(CozeApiCall::getIsDeleted, 0)
.orderByDesc(CozeApiCall::getStartTime);
return this.list(wrapper);
}
@Override
public List<CozeApiCall> getByRequestType(String requestType) {
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CozeApiCall::getRequestType, requestType)
.eq(CozeApiCall::getIsDeleted, 0)
.orderByDesc(CozeApiCall::getStartTime);
return this.list(wrapper);
}
@Override
public List<CozeApiCall> getByTimeRange(LocalDateTime startTime, LocalDateTime endTime) {
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>();
wrapper.between(CozeApiCall::getStartTime, startTime, endTime)
.eq(CozeApiCall::getIsDeleted, 0)
.orderByDesc(CozeApiCall::getStartTime);
return this.list(wrapper);
} }
@Override @Override
@@ -167,87 +321,46 @@ public class CozeApiCallServiceImpl extends ServiceImpl<CozeApiCallMapper, CozeA
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
} }
@Override /**
public List<CozeApiCall> getFailedCalls() { * 转换为响应对象
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>(); */
wrapper.and(w -> w.eq(CozeApiCall::getStatus, "failed").or().eq(CozeApiCall::getFinalStatus, "failed")) private CozeApiCallResponse convertToResponse(CozeApiCall apiCall) {
.eq(CozeApiCall::getIsDeleted, 0) CozeApiCallResponse response = new CozeApiCallResponse();
.orderByDesc(CozeApiCall::getStartTime); BeanUtils.copyProperties(apiCall, response);
return this.list(wrapper); 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<CozeApiCall> getTimeoutCalls() { * 转换分页对象为PageResult对象
LambdaQueryWrapper<CozeApiCall> wrapper = new LambdaQueryWrapper<>(); */
wrapper.and(w -> w.eq(CozeApiCall::getStatus, "timeout").or().eq(CozeApiCall::getFinalStatus, "timeout")) private PageResult<CozeApiCallResponse> convertPageToPageResult(Page<CozeApiCall> page) {
.eq(CozeApiCall::getIsDeleted, 0) PageResult<CozeApiCallResponse> pageResult = new PageResult<>();
.orderByDesc(CozeApiCall::getStartTime); pageResult.setCurrent(page.getCurrent());
return this.list(wrapper); 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<CozeApiCall> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CozeApiCall::getTraceId, traceId)
.eq(CozeApiCall::getIsDeleted, 0);
return this.getOne(wrapper);
}
@Override
public List<CozeApiCall> getByConversationIdAndRequestType(String conversationId, String requestType) {
LambdaQueryWrapper<CozeApiCall> 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<CozeApiCall> 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<CozeApiCall> 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;
}
}
@@ -5,18 +5,24 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.entity.DiaryComment;
import com.emotion.mapper.DiaryCommentMapper; import com.emotion.mapper.DiaryCommentMapper;
import com.emotion.service.DiaryCommentService; import com.emotion.service.DiaryCommentService;
import com.emotion.util.SnowflakeIdGenerator;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -29,207 +35,105 @@ import java.util.stream.Collectors;
@Service @Service
public class DiaryCommentServiceImpl extends ServiceImpl<DiaryCommentMapper, DiaryComment> implements DiaryCommentService { public class DiaryCommentServiceImpl extends ServiceImpl<DiaryCommentMapper, DiaryComment> implements DiaryCommentService {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<DiaryComment> getPage(BasePageRequest request) { public PageResult<DiaryCommentResponse> getPageWithResponse(DiaryCommentPageRequest request) {
Page<DiaryComment> page = new Page<>(request.getCurrent(), request.getSize()); Page<DiaryComment> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<DiaryComment> 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); .orderByDesc(DiaryComment::getPublishTime);
return this.page(page, wrapper);
IPage<DiaryComment> resultPage = this.page(page, wrapper);
return convertPageToResponse(resultPage);
} }
@Override @Override
public IPage<DiaryComment> getPageByDiaryId(String diaryId, BasePageRequest request) { public DiaryCommentResponse getCommentResponseById(String id) {
Page<DiaryComment> page = new Page<>(request.getCurrent(), request.getSize()); DiaryComment comment = this.getById(id);
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>(); if (comment == null) {
wrapper.eq(DiaryComment::getDiaryId, diaryId) return null;
.eq(DiaryComment::getIsDeleted, 0) }
.orderByDesc(DiaryComment::getIsTop) return convertToResponse(comment);
.orderByDesc(DiaryComment::getPublishTime);
return this.page(page, wrapper);
} }
@Override @Override
public IPage<DiaryComment> getPageByUserId(String userId, BasePageRequest request) { public List<DiaryCommentResponse> getCommentTreeWithResponse(String diaryId) {
Page<DiaryComment> page = new Page<>(request.getCurrent(), request.getSize()); List<DiaryComment> comments = this.getCommentTree(diaryId);
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>(); return comments.stream()
wrapper.eq(DiaryComment::getUserId, userId) .map(this::convertToResponse)
.eq(DiaryComment::getIsDeleted, 0) .collect(Collectors.toList());
.orderByDesc(DiaryComment::getPublishTime);
return this.page(page, wrapper);
} }
@Override @Override
public List<DiaryComment> getRepliesByParentId(String parentCommentId) { public DiaryCommentResponse createCommentWithResponse(DiaryCommentCreateRequest request) {
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DiaryComment::getParentCommentId, parentCommentId)
.eq(DiaryComment::getIsDeleted, 0)
.orderByAsc(DiaryComment::getPublishTime);
return this.list(wrapper);
}
@Override
public List<DiaryComment> getByCommentType(String commentType) {
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DiaryComment::getCommentType, commentType)
.eq(DiaryComment::getIsDeleted, 0)
.orderByDesc(DiaryComment::getPublishTime);
return this.list(wrapper);
}
@Override
public List<DiaryComment> getByDiaryIdAndCommentType(String diaryId, String commentType) {
LambdaQueryWrapper<DiaryComment> 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<DiaryComment> 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<DiaryComment> 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<DiaryComment> 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<DiaryComment> 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<DiaryComment> 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<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryComment::getId, commentId)
.set(DiaryComment::getIsTop, isTop);
return this.update(wrapper);
}
@Override
public Long countByDiaryId(String diaryId) {
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DiaryComment::getDiaryId, diaryId)
.eq(DiaryComment::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public Long countByUserId(String userId) {
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DiaryComment::getUserId, userId)
.eq(DiaryComment::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public Long countByCommentType(String commentType) {
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DiaryComment::getCommentType, commentType)
.eq(DiaryComment::getIsDeleted, 0);
return this.count(wrapper);
}
@Override
public Long countReplies(String parentCommentId) {
LambdaQueryWrapper<DiaryComment> 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<String> images,
String parentCommentId, Integer isAnonymous) {
DiaryComment comment = DiaryComment.builder() DiaryComment comment = DiaryComment.builder()
.diaryId(diaryId) .id(snowflakeIdGenerator.nextIdAsString()) // 使用雪花算法生成ID
.userId(userId) .diaryId(request.getDiaryId())
.content(content) .userId(request.getUserId())
.images(convertListToJson(images)) .content(request.getContent())
.parentCommentId(parentCommentId) .images(convertListToJson(request.getImages()))
.parentCommentId(request.getParentCommentId())
.commentType("user") .commentType("user")
.likeCount(0) .likeCount(0)
.replyCount(0) .replyCount(0)
.isAnonymous(isAnonymous) .isAnonymous(request.getIsAnonymous())
.isTop(0) .isTop(0)
.status("published") .status("published")
.publishTime(LocalDateTime.now()) .publishTime(LocalDateTime.now())
.build(); .build();
this.save(comment); this.save(comment);
// 如果有父评论,更新父评论的回复数 // 如果有父评论,更新父评论的回复数
if (StringUtils.hasText(parentCommentId)) { if (StringUtils.hasText(request.getParentCommentId())) {
this.incrementReplyCount(parentCommentId); this.incrementReplyCount(request.getParentCommentId());
this.updateLastReplyTime(parentCommentId); this.updateLastReplyTime(request.getParentCommentId());
} }
return comment; return convertToResponse(comment);
} }
@Override @Override
public DiaryComment createAiComment(String diaryId, String content, String aiCommentSource, public DiaryCommentResponse updateCommentWithResponse(DiaryCommentCreateRequest request) {
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<String> images) {
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryComment::getId, commentId) wrapper.eq(DiaryComment::getId, request.getId())
.set(StringUtils.hasText(content), DiaryComment::getContent, content) .set(StringUtils.hasText(request.getContent()), DiaryComment::getContent, request.getContent())
.set(images != null, DiaryComment::getImages, convertListToJson(images)); .set(request.getImages() != null, DiaryComment::getImages, convertListToJson(request.getImages()))
return this.update(wrapper); .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 @Override
@@ -241,7 +145,8 @@ public class DiaryCommentServiceImpl extends ServiceImpl<DiaryCommentMapper, Dia
public boolean softDeleteComment(String commentId) { public boolean softDeleteComment(String commentId) {
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryComment::getId, commentId) wrapper.eq(DiaryComment::getId, commentId)
.set(DiaryComment::getIsDeleted, 1); .set(DiaryComment::getIsDeleted, 1)
.set(DiaryComment::getUpdateTime, LocalDateTime.now());
return this.update(wrapper); return this.update(wrapper);
} }
@@ -249,22 +154,52 @@ public class DiaryCommentServiceImpl extends ServiceImpl<DiaryCommentMapper, Dia
public boolean restoreComment(String commentId) { public boolean restoreComment(String commentId) {
LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryComment::getId, commentId) wrapper.eq(DiaryComment::getId, commentId)
.set(DiaryComment::getIsDeleted, 0); .set(DiaryComment::getIsDeleted, 0)
.set(DiaryComment::getUpdateTime, LocalDateTime.now());
return this.update(wrapper); return this.update(wrapper);
} }
@Override @Override
public List<DiaryComment> getCommentTree(String diaryId) { public boolean incrementLikeCount(String commentId) {
LambdaUpdateWrapper<DiaryComment> 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<DiaryComment> 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<DiaryComment> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryComment::getId, commentId)
.set(DiaryComment::getIsTop, isTop)
.set(DiaryComment::getUpdateTime, LocalDateTime.now());
return this.update(wrapper);
}
/**
* 获取评论树结构
*/
private List<DiaryComment> getCommentTree(String diaryId) {
// 获取所有顶级评论(没有父评论的评论) // 获取所有顶级评论(没有父评论的评论)
LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<DiaryComment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DiaryComment::getDiaryId, diaryId) wrapper.eq(DiaryComment::getDiaryId, diaryId)
.isNull(DiaryComment::getParentCommentId) .isNull(DiaryComment::getParentCommentId)
.eq(DiaryComment::getIsDeleted, 0) .eq(DiaryComment::getIsDeleted, 0)
.orderByDesc(DiaryComment::getIsTop) .orderByDesc(DiaryComment::getIsTop)
.orderByDesc(DiaryComment::getPublishTime); .orderByDesc(DiaryComment::getPublishTime);
List<DiaryComment> topComments = this.list(wrapper); List<DiaryComment> topComments = this.list(wrapper);
// 为每个顶级评论加载回复 // 为每个顶级评论加载回复
return topComments.stream() return topComments.stream()
.peek(comment -> { .peek(comment -> {
@@ -274,6 +209,103 @@ public class DiaryCommentServiceImpl extends ServiceImpl<DiaryCommentMapper, Dia
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* 根据父评论ID查询回复列表
*/
private List<DiaryComment> getRepliesByParentId(String parentCommentId) {
LambdaQueryWrapper<DiaryComment> 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<DiaryComment> 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<DiaryComment> 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<DiaryComment> 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<List<String>>() {
}));
}
if (comment.getMetadata() != null) {
response.setMetadata(objectMapper.readValue(comment.getMetadata(), Object.class));
}
} catch (JsonProcessingException e) {
// 忽略JSON解析错误
}
return response;
}
/**
* 转换分页对象为响应对象
*/
private PageResult<DiaryCommentResponse> convertPageToResponse(IPage<DiaryComment> page) {
PageResult<DiaryCommentResponse> 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字符串 * 将List转换为JSON字符串
*/ */
@@ -287,4 +319,4 @@ public class DiaryCommentServiceImpl extends ServiceImpl<DiaryCommentMapper, Dia
return null; return null;
} }
} }
} }
@@ -5,24 +5,30 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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 com.emotion.entity.DiaryPost;
import com.emotion.mapper.DiaryPostMapper; import com.emotion.mapper.DiaryPostMapper;
import com.emotion.service.DiaryPostService; import com.emotion.service.DiaryPostService;
import com.emotion.service.DiaryCommentService; import com.emotion.service.DiaryCommentService;
import com.emotion.service.AiChatService; import com.emotion.service.AiChatService;
import com.emotion.dto.request.DiaryPostCreateRequest; import com.emotion.util.SnowflakeIdGenerator;
import com.emotion.dto.response.DiaryPostResponse;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 用户日记服务实现类 * 用户日记服务实现类
@@ -39,112 +45,153 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
private DiaryCommentService diaryCommentService; private DiaryCommentService diaryCommentService;
@Autowired @Autowired
private ObjectMapper objectMapper; private ObjectMapper objectMapper;
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<DiaryPost> getPage(BasePageRequest request) { public PageResult<DiaryPostResponse> getPageWithResponse(DiaryPostPageRequest request) {
Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize()); Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize());
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<DiaryPost> 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); .orderByDesc(DiaryPost::getPublishTime);
return this.page(page, wrapper);
IPage<DiaryPost> resultPage = this.page(page, wrapper);
List<DiaryPostResponse> responses = resultPage.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return createPageResult(resultPage, responses);
} }
@Override @Override
public IPage<DiaryPost> getPageByUserId(String userId, BasePageRequest request) { public DiaryPostResponse getDiaryPostResponseById(String id) {
Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize()); DiaryPost diaryPost = this.getById(id);
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>(); if (diaryPost == null) {
wrapper.eq(DiaryPost::getUserId, userId) return null;
.eq(DiaryPost::getIsDeleted, 0) }
.orderByDesc(DiaryPost::getPriority) // 增加浏览数
.orderByDesc(DiaryPost::getPublishTime); incrementViewCount(id);
return this.page(page, wrapper); return convertToResponse(diaryPost);
} }
@Override @Override
public IPage<DiaryPost> getPublicPageByUserId(String userId, BasePageRequest request) { public DiaryPostResponse createDiaryPostWithResponse(DiaryPostCreateRequest request) {
Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize()); // 处理标题:如果为空,则设置为null或生成默认标题
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>(); String title = request.getTitle();
wrapper.eq(DiaryPost::getUserId, userId) if (title == null || title.trim().isEmpty()) {
.eq(DiaryPost::getIsPublic, 1) // 可以选择设置为null,或者生成一个默认标题
.eq(DiaryPost::getStatus, "published") // 这里我们设置为null,让数据库使用默认值
.eq(DiaryPost::getIsDeleted, 0) title = null;
.orderByDesc(DiaryPost::getPriority) }
.orderByDesc(DiaryPost::getPublishTime);
return this.page(page, wrapper); 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 @Override
public IPage<DiaryPost> getFeaturedPage(BasePageRequest request) { public DiaryPostResponse updateDiaryPostWithResponse(DiaryPostUpdateRequest request) {
Page<DiaryPost> page = new Page<>(request.getCurrent(), request.getSize()); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(DiaryPost::getId, request.getId())
wrapper.eq(DiaryPost::getFeatured, 1) .set(StringUtils.hasText(request.getTitle()), DiaryPost::getTitle, request.getTitle())
.eq(DiaryPost::getIsPublic, 1) .set(StringUtils.hasText(request.getContent()), DiaryPost::getContent, request.getContent())
.eq(DiaryPost::getStatus, "published") .set(request.getImages() != null, DiaryPost::getImages, convertListToJson(request.getImages()))
.eq(DiaryPost::getIsDeleted, 0) .set(request.getVideos() != null, DiaryPost::getVideos, convertListToJson(request.getVideos()))
.orderByDesc(DiaryPost::getPriority) .set(StringUtils.hasText(request.getLocation()), DiaryPost::getLocation, request.getLocation())
.orderByDesc(DiaryPost::getPublishTime); .set(StringUtils.hasText(request.getWeather()), DiaryPost::getWeather, request.getWeather())
return this.page(page, wrapper); .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 @Override
public List<DiaryPost> getByStatus(String status) { public boolean deleteDiaryPost(String diaryId) {
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>(); return this.removeById(diaryId);
wrapper.eq(DiaryPost::getStatus, status)
.eq(DiaryPost::getIsDeleted, 0)
.orderByDesc(DiaryPost::getPublishTime);
return this.list(wrapper);
} }
@Override @Override
public List<DiaryPost> getByUserIdAndStatus(String userId, String status) { public boolean softDeleteDiaryPost(String diaryId) {
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getUserId, userId) wrapper.eq(DiaryPost::getId, diaryId)
.eq(DiaryPost::getStatus, status) .set(DiaryPost::getIsDeleted, 1)
.eq(DiaryPost::getIsDeleted, 0) .set(DiaryPost::getUpdateTime, LocalDateTime.now());
.orderByDesc(DiaryPost::getPublishTime); return this.update(wrapper);
return this.list(wrapper);
} }
@Override @Override
public List<DiaryPost> getByMood(String mood) { public boolean restoreDiaryPost(String diaryId) {
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getMood, mood) wrapper.eq(DiaryPost::getId, diaryId)
.eq(DiaryPost::getIsPublic, 1) .set(DiaryPost::getIsDeleted, 0)
.eq(DiaryPost::getStatus, "published") .set(DiaryPost::getUpdateTime, LocalDateTime.now());
.eq(DiaryPost::getIsDeleted, 0) return this.update(wrapper);
.orderByDesc(DiaryPost::getPublishTime);
return this.list(wrapper);
}
@Override
public List<DiaryPost> getByTags(List<String> tags) {
// 这里需要根据实际需求实现标签查询逻辑
// 由于tags字段是JSON格式,可能需要使用数据库的JSON查询功能
LambdaQueryWrapper<DiaryPost> 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<DiaryPost> getByLocation(String location) {
LambdaQueryWrapper<DiaryPost> 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);
} }
@Override @Override
public boolean incrementViewCount(String diaryId) { public boolean incrementViewCount(String diaryId) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) 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); return this.update(wrapper);
} }
@@ -152,7 +199,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
public boolean incrementLikeCount(String diaryId) { public boolean incrementLikeCount(String diaryId) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) 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); return this.update(wrapper);
} }
@@ -160,7 +208,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
public boolean decrementLikeCount(String diaryId) { public boolean decrementLikeCount(String diaryId) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) 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); return this.update(wrapper);
} }
@@ -168,7 +217,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
public boolean incrementCommentCount(String diaryId) { public boolean incrementCommentCount(String diaryId) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) 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); return this.update(wrapper);
} }
@@ -176,7 +226,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
public boolean decrementCommentCount(String diaryId) { public boolean decrementCommentCount(String diaryId) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) 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); return this.update(wrapper);
} }
@@ -184,7 +235,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
public boolean incrementShareCount(String diaryId) { public boolean incrementShareCount(String diaryId) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) 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); return this.update(wrapper);
} }
@@ -192,7 +244,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
public boolean updateLastCommentTime(String diaryId) { public boolean updateLastCommentTime(String diaryId) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) wrapper.eq(DiaryPost::getId, diaryId)
.set(DiaryPost::getLastCommentTime, LocalDateTime.now()); .set(DiaryPost::getLastCommentTime, LocalDateTime.now())
.set(DiaryPost::getUpdateTime, LocalDateTime.now());
return this.update(wrapper); return this.update(wrapper);
} }
@@ -200,7 +253,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
public boolean setFeatured(String diaryId, Integer featured) { public boolean setFeatured(String diaryId, Integer featured) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) wrapper.eq(DiaryPost::getId, diaryId)
.set(DiaryPost::getFeatured, featured); .set(DiaryPost::getFeatured, featured)
.set(DiaryPost::getUpdateTime, LocalDateTime.now());
return this.update(wrapper); return this.update(wrapper);
} }
@@ -208,7 +262,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
public boolean setPriority(String diaryId, Integer priority) { public boolean setPriority(String diaryId, Integer priority) {
LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>(); LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId) wrapper.eq(DiaryPost::getId, diaryId)
.set(DiaryPost::getPriority, priority); .set(DiaryPost::getPriority, priority)
.set(DiaryPost::getUpdateTime, LocalDateTime.now());
return this.update(wrapper); return this.update(wrapper);
} }
@@ -239,26 +294,12 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
} }
@Override @Override
public Long countByStatus(String status) { public DiaryPostResponse publishDiaryWithAiComment(DiaryPostCreateRequest request) {
LambdaQueryWrapper<DiaryPost> wrapper = new LambdaQueryWrapper<>(); // 1. 保存日记
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;
}
DiaryPost diaryPost = DiaryPost.builder() DiaryPost diaryPost = DiaryPost.builder()
.id(snowflakeIdGenerator.nextIdAsString()) // 使用雪花算法生成ID
.userId(request.getUserId()) .userId(request.getUserId())
.title(title) .title(request.getTitle())
.content(request.getContent()) .content(request.getContent())
.images(convertListToJson(request.getImages())) .images(convertListToJson(request.getImages()))
.videos(convertListToJson(request.getVideos())) .videos(convertListToJson(request.getVideos()))
@@ -277,69 +318,8 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
.priority(0) .priority(0)
.featured(0) .featured(0)
.build(); .build();
this.save(diaryPost); this.save(diaryPost);
return diaryPost;
}
@Override
public boolean updateDiaryPost(String diaryId, com.emotion.dto.request.DiaryPostUpdateRequest request) {
LambdaUpdateWrapper<DiaryPost> 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<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(DiaryPost::getId, diaryId)
.set(DiaryPost::getIsDeleted, 1);
return this.update(wrapper);
}
@Override
public boolean restoreDiaryPost(String diaryId) {
LambdaUpdateWrapper<DiaryPost> 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<String> aiKeywords, String aiSuggestions) {
LambdaUpdateWrapper<DiaryPost> 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评论 // 2. 生成AI评论
String aiComment = null; String aiComment = null;
try { try {
@@ -350,13 +330,14 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
} }
// 3. 写入AI评论到diary_comment表 // 3. 写入AI评论到diary_comment表
if (aiComment != null) { if (aiComment != null) {
diaryCommentService.createAiComment( // 使用createCommentWithResponse方法创建AI评论
diaryPost.getId(), com.emotion.dto.request.DiaryCommentCreateRequest commentRequest = new com.emotion.dto.request.DiaryCommentCreateRequest();
aiComment, commentRequest.setDiaryId(diaryPost.getId());
"diary_ai_summary", commentRequest.setUserId("system"); // AI评论使用system用户ID
null, commentRequest.setContent(aiComment);
null commentRequest.setIsAnonymous(0);
); diaryCommentService.createCommentWithResponse(commentRequest);
addAiComment( addAiComment(
diaryPost.getId(), diaryPost.getId(),
aiComment, aiComment,
@@ -367,53 +348,37 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
); );
} }
// 4. 返回日记详情(含AI评论) // 4. 返回日记详情(含AI评论)
com.emotion.dto.response.DiaryPostResponse response = new com.emotion.dto.response.DiaryPostResponse(); return convertToResponse(diaryPost);
org.springframework.beans.BeanUtils.copyProperties(diaryPost, response); }
// 查询AI评论
List<com.emotion.entity.DiaryComment> aiComments = diaryCommentService.getByDiaryIdAndCommentType(diaryPost.getId(), "ai"); /**
if (!aiComments.isEmpty()) { * 添加AI评论
response.setAiComment(aiComments.get(0).getContent()); */
} private boolean addAiComment(String diaryId, String aiComment, Object aiEmotionAnalysis,
// 转换时间格式 BigDecimal aiSentimentScore, List<String> aiKeywords, String aiSuggestions) {
if (diaryPost.getPublishTime() != null) { LambdaUpdateWrapper<DiaryPost> wrapper = new LambdaUpdateWrapper<>();
response.setPublishTime(diaryPost.getPublishTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); wrapper.eq(DiaryPost::getId, diaryId)
} .set(DiaryPost::getAiComment, aiComment)
if (diaryPost.getLastCommentTime() != null) { .set(DiaryPost::getAiCommentTime, LocalDateTime.now())
response.setLastCommentTime(diaryPost.getLastCommentTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); .set(DiaryPost::getAiEmotionAnalysis, convertObjectToJson(aiEmotionAnalysis))
} .set(DiaryPost::getAiSentimentScore, aiSentimentScore)
if (diaryPost.getAiCommentTime() != null) { .set(DiaryPost::getAiKeywords, convertListToJson(aiKeywords))
response.setAiCommentTime(diaryPost.getAiCommentTime().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); .set(DiaryPost::getAiSuggestions, aiSuggestions)
} .set(DiaryPost::getUpdateTime, LocalDateTime.now());
if (diaryPost.getCreateTime() != null) { return this.update(wrapper);
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"))); * 创建PageResult对象
} */
// 转换JSON字段 private <T> PageResult<T> createPageResult(IPage<?> page, List<T> records) {
try { PageResult<T> pageResult = new PageResult<>();
if (diaryPost.getImages() != null) { pageResult.setCurrent(page.getCurrent());
response.setImages(objectMapper.readValue(diaryPost.getImages(), new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {})); pageResult.setSize(page.getSize());
} pageResult.setTotal(page.getTotal());
if (diaryPost.getVideos() != null) { pageResult.setPages(page.getPages());
response.setVideos(objectMapper.readValue(diaryPost.getVideos(), new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {})); pageResult.setRecords(records);
} return pageResult;
if (diaryPost.getTags() != null) {
response.setTags(objectMapper.readValue(diaryPost.getTags(), new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {}));
}
if (diaryPost.getAiKeywords() != null) {
response.setAiKeywords(objectMapper.readValue(diaryPost.getAiKeywords(), new com.fasterxml.jackson.core.type.TypeReference<List<String>>() {}));
}
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;
} }
/** /**
@@ -443,4 +408,60 @@ public class DiaryPostServiceImpl extends ServiceImpl<DiaryPostMapper, DiaryPost
return null; return null;
} }
} }
}
/**
* 转换实体为响应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<List<String>>() {
}));
}
if (diaryPost.getVideos() != null) {
response.setVideos(objectMapper.readValue(diaryPost.getVideos(), new TypeReference<List<String>>() {
}));
}
if (diaryPost.getTags() != null) {
response.setTags(objectMapper.readValue(diaryPost.getTags(), new TypeReference<List<String>>() {
}));
}
if (diaryPost.getAiKeywords() != null) {
response.setAiKeywords(
objectMapper.readValue(diaryPost.getAiKeywords(), new TypeReference<List<String>>() {
}));
}
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;
}
}
@@ -5,15 +5,25 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.emotion.common.BasePageRequest; 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.entity.EmotionAnalysis;
import com.emotion.mapper.EmotionAnalysisMapper; import com.emotion.mapper.EmotionAnalysisMapper;
import com.emotion.service.EmotionAnalysisService; 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.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* 情绪分析服务实现类 * 情绪分析服务实现类
@@ -24,6 +34,11 @@ import java.util.List;
@Service @Service
public class EmotionAnalysisServiceImpl extends ServiceImpl<EmotionAnalysisMapper, EmotionAnalysis> implements EmotionAnalysisService { public class EmotionAnalysisServiceImpl extends ServiceImpl<EmotionAnalysisMapper, EmotionAnalysis> implements EmotionAnalysisService {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<EmotionAnalysis> getPage(BasePageRequest request) { public IPage<EmotionAnalysis> getPage(BasePageRequest request) {
Page<EmotionAnalysis> page = new Page<>(request.getCurrent(), request.getSize()); Page<EmotionAnalysis> page = new Page<>(request.getCurrent(), request.getSize());
@@ -173,6 +188,7 @@ public class EmotionAnalysisServiceImpl extends ServiceImpl<EmotionAnalysisMappe
public EmotionAnalysis createEmotionAnalysis(String messageId, String userId, String primaryEmotion, public EmotionAnalysis createEmotionAnalysis(String messageId, String userId, String primaryEmotion,
String polarity, Double intensity, Double confidence) { String polarity, Double intensity, Double confidence) {
EmotionAnalysis analysis = new EmotionAnalysis(); EmotionAnalysis analysis = new EmotionAnalysis();
analysis.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID
analysis.setMessageId(messageId); analysis.setMessageId(messageId);
analysis.setCreateBy(userId); analysis.setCreateBy(userId);
analysis.setPrimaryEmotion(primaryEmotion); analysis.setPrimaryEmotion(primaryEmotion);
@@ -183,4 +199,162 @@ public class EmotionAnalysisServiceImpl extends ServiceImpl<EmotionAnalysisMappe
this.save(analysis); this.save(analysis);
return analysis; return analysis;
} }
// 新增的方法实现
@Override
public PageResult<EmotionAnalysisResponse> getPageWithResponse(EmotionAnalysisPageRequest request) {
IPage<EmotionAnalysis> page = getPage(request);
List<EmotionAnalysisResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
return createPageResult(page, responses);
}
@Override
public PageResult<EmotionAnalysisResponse> getPageByUserIdWithResponse(String userId,
EmotionAnalysisPageRequest request) {
IPage<EmotionAnalysis> page = getPageByUserId(request, userId);
List<EmotionAnalysisResponse> 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<EmotionAnalysisResponse> getEmotionAnalysisResponsesByPrimaryEmotion(String primaryEmotion) {
List<EmotionAnalysis> analyses = getByPrimaryEmotion(primaryEmotion);
return analyses.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<EmotionAnalysisResponse> getEmotionAnalysisResponsesByPolarity(String polarity) {
List<EmotionAnalysis> analyses = getByPolarity(polarity);
return analyses.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<EmotionAnalysisResponse> getEmotionAnalysisResponsesByUserIdAndEmotion(String userId,
String primaryEmotion) {
List<EmotionAnalysis> analyses = getByUserIdAndEmotion(userId, primaryEmotion);
return analyses.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<EmotionAnalysisResponse> getEmotionAnalysisResponsesByUserIdAndTimeRange(String userId,
LocalDateTime startTime, LocalDateTime endTime) {
List<EmotionAnalysis> analyses = getByUserIdAndTimeRange(userId, startTime, endTime);
return analyses.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
@Override
public List<EmotionAnalysisResponse> getEmotionAnalysisResponsesRecentByUserId(String userId, Integer limit) {
List<EmotionAnalysis> analyses = getRecentByUserId(userId, limit);
return analyses.stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
}
/**
* 创建PageResult对象
*/
private <T> PageResult<T> createPageResult(IPage<?> page, List<T> records) {
PageResult<T> 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;
}
} }
@@ -5,17 +5,29 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.emotion.common.BasePageRequest; 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.entity.EmotionRecord;
import com.emotion.mapper.EmotionRecordMapper; import com.emotion.mapper.EmotionRecordMapper;
import com.emotion.service.EmotionRecordService; 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.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections; import java.util.Collections;
import java.util.stream.Collectors;
/** /**
* 情绪记录服务实现类 * 情绪记录服务实现类
@@ -26,6 +38,11 @@ import java.util.Collections;
@Service @Service
public class EmotionRecordServiceImpl extends ServiceImpl<EmotionRecordMapper, EmotionRecord> implements EmotionRecordService { public class EmotionRecordServiceImpl extends ServiceImpl<EmotionRecordMapper, EmotionRecord> implements EmotionRecordService {
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override @Override
public IPage<EmotionRecord> getPage(BasePageRequest request) { public IPage<EmotionRecord> getPage(BasePageRequest request) {
Page<EmotionRecord> page = new Page<>(request.getCurrent(), request.getSize()); Page<EmotionRecord> page = new Page<>(request.getCurrent(), request.getSize());
@@ -134,20 +151,59 @@ public class EmotionRecordServiceImpl extends ServiceImpl<EmotionRecordMapper, E
@Override @Override
public Double getAvgIntensityByUserId(String userId) { public Double getAvgIntensityByUserId(String userId) {
// 这里需要自定义SQL查询平均值,暂时返回0 LambdaQueryWrapper<EmotionRecord> wrapper = new LambdaQueryWrapper<>();
return 0.0; wrapper.eq(EmotionRecord::getUserId, userId)
.eq(EmotionRecord::getIsDeleted, 0);
List<EmotionRecord> 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 @Override
public Double getAvgIntensityByUserIdAndTimeRange(String userId, LocalDateTime startTime, LocalDateTime endTime) { public Double getAvgIntensityByUserIdAndTimeRange(String userId, LocalDateTime startTime, LocalDateTime endTime) {
// 这里需要自定义SQL查询平均值,暂时返回0 LambdaQueryWrapper<EmotionRecord> wrapper = new LambdaQueryWrapper<>();
return 0.0; wrapper.eq(EmotionRecord::getUserId, userId)
.between(EmotionRecord::getCreateTime, startTime, endTime)
.eq(EmotionRecord::getIsDeleted, 0);
List<EmotionRecord> 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 @Override
public String getMostFrequentEmotionByUserId(String userId) { public String getMostFrequentEmotionByUserId(String userId) {
// 这里需要自定义SQL查询最常见的情绪类型,暂时返回null LambdaQueryWrapper<EmotionRecord> wrapper = new LambdaQueryWrapper<>();
return null; wrapper.eq(EmotionRecord::getUserId, userId)
.eq(EmotionRecord::getIsDeleted, 0);
List<EmotionRecord> 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 @Override
@@ -181,6 +237,7 @@ public class EmotionRecordServiceImpl extends ServiceImpl<EmotionRecordMapper, E
public EmotionRecord createEmotionRecord(String userId, String emotionType, Double intensity, public EmotionRecord createEmotionRecord(String userId, String emotionType, Double intensity,
String trigger, String location, String notes) { String trigger, String location, String notes) {
EmotionRecord record = new EmotionRecord(); EmotionRecord record = new EmotionRecord();
record.setId(snowflakeIdGenerator.nextIdAsString()); // 使用雪花算法生成ID
record.setUserId(userId); record.setUserId(userId);
record.setEmotionType(emotionType); record.setEmotionType(emotionType);
record.setIntensity(BigDecimal.valueOf(intensity)); record.setIntensity(BigDecimal.valueOf(intensity));
@@ -204,4 +261,223 @@ public class EmotionRecordServiceImpl extends ServiceImpl<EmotionRecordMapper, E
return this.page(page, wrapper); return this.page(page, wrapper);
} }
// 新增的方法实现
@Override
public PageResult<EmotionRecordResponse> getPageWithResponse(EmotionRecordPageRequest request) {
IPage<EmotionRecord> page = getPage(request);
List<EmotionRecordResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<EmotionRecordResponse> 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<EmotionRecordResponse> getPageByUserIdWithResponse(String userId, EmotionRecordPageRequest request) {
IPage<EmotionRecord> page = getPageByUserId(request, userId);
List<EmotionRecordResponse> responses = page.getRecords().stream()
.map(this::convertToResponse)
.collect(Collectors.toList());
PageResult<EmotionRecordResponse> 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<String, Object> getEmotionStats(String userId, String startDate, String endDate) {
Map<String, Object> stats = new HashMap<>();
// 构建查询条件
LambdaQueryWrapper<EmotionRecord> 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<EmotionRecord> 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<String, Long> 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<EmotionRecordResponse> getRecentByUserIdWithResponse(String userId, Integer limit) {
List<EmotionRecord> 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;
}
} }
@@ -4,10 +4,13 @@ import com.emotion.dto.response.UserInfoResponse;
import com.emotion.exception.TokenException; import com.emotion.exception.TokenException;
import com.emotion.service.AuthService; import com.emotion.service.AuthService;
import com.emotion.service.TokenService; import com.emotion.service.TokenService;
import com.emotion.util.TokenUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/** /**
* 令牌服务实现类 * 令牌服务实现类
* *
@@ -20,15 +23,19 @@ public class TokenServiceImpl implements TokenService {
@Autowired @Autowired
private AuthService authService; private AuthService authService;
@Autowired
private TokenUtil tokenUtil;
@Override @Override
public UserInfoResponse getUserInfoByToken(String token) { public UserInfoResponse getUserInfoByToken(HttpServletRequest request) {
String userId = validateTokenAndGetUserId(token); String userId = validateTokenAndGetUserId(request);
return authService.getCurrentUserInfo(userId); return authService.getCurrentUserInfo(userId);
} }
@Override @Override
public String getUsernameByToken(String token) { public String getUsernameByToken(HttpServletRequest request) {
if (!StringUtils.hasText(token)) { String token = tokenUtil.extractToken(request);
if (!tokenUtil.isValidToken(token)) {
throw new TokenException("未提供访问令牌"); throw new TokenException("未提供访问令牌");
} }
@@ -45,8 +52,9 @@ public class TokenServiceImpl implements TokenService {
} }
@Override @Override
public String validateTokenAndGetUserId(String token) { public String validateTokenAndGetUserId(HttpServletRequest request) {
if (!StringUtils.hasText(token)) { String token = tokenUtil.extractToken(request);
if (!tokenUtil.isValidToken(token)) {
throw new TokenException("未提供访问令牌"); throw new TokenException("未提供访问令牌");
} }
@@ -61,4 +69,4 @@ public class TokenServiceImpl implements TokenService {
return userId; return userId;
} }
} }
@@ -1,5 +1,6 @@
package com.emotion.service.impl; package com.emotion.service.impl;
import com.emotion.dto.request.WebSocketRequest;
import com.emotion.dto.websocket.ChatRequest; import com.emotion.dto.websocket.ChatRequest;
import com.emotion.dto.websocket.ConnectRequest; import com.emotion.dto.websocket.ConnectRequest;
import com.emotion.dto.websocket.WebSocketMessage; 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.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.security.Principal; import java.security.Principal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -48,64 +50,54 @@ public class WebSocketServiceImpl implements WebSocketService {
* 处理聊天消息 * 处理聊天消息
*/ */
@Override @Override
public void handleChatMessage(ChatRequest request, String sessionId, Principal principal) { public void handleChatMessage(WebSocketRequest webSocketRequest, String sessionId, Principal principal) {
try { 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()) { if (webSocketRequest.getContent() == null || webSocketRequest.getContent().trim().isEmpty()) {
sendErrorMessage(request.getSenderId(), "消息内容不能为空"); sendErrorMessage(getUserId(principal, sessionId), "消息内容不能为空");
return; return;
} }
// 确定用户身份和类型 // 设置默认值
String userId = request.getSenderId(); setWebSocketRequestDefaults(webSocketRequest, principal, sessionId);
WebSocketMessage.SenderType senderType = WebSocketMessage.SenderType.GUEST;
if (principal != null) { log.info("确定用户身份: userId={}, senderType={}", webSocketRequest.getSenderId(),
userId = principal.getName(); webSocketRequest.getSenderType());
// 如果用户ID不是以guest_开头,说明是认证用户
if (!userId.startsWith("guest_")) {
senderType = WebSocketMessage.SenderType.USER;
}
}
// 更新请求中的用户信息 // 转换请求对象
request.setSenderId(userId); ChatRequest chatRequest = convertToChatRequest(webSocketRequest);
request.setSenderType(senderType == WebSocketMessage.SenderType.USER ? ChatRequest.SenderType.USER
: ChatRequest.SenderType.GUEST);
log.info("确定用户身份: userId={}, senderType={}", userId, senderType);
// 构建用户消息 // 构建用户消息
WebSocketMessage userMessage = WebSocketMessage.builder() WebSocketMessage userMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString()) .messageId(UUID.randomUUID().toString())
.conversationId(request.getConversationId()) .conversationId(chatRequest.getConversationId())
.type(WebSocketMessage.MessageType.TEXT) .type("TEXT")
.content(request.getContent()) .content(chatRequest.getContent())
.senderId(userId) .senderId(chatRequest.getSenderId())
.senderType(senderType) .senderType(getSenderType(chatRequest.getSenderType()))
.status(WebSocketMessage.MessageStatus.SENT) .status("SENT")
.createTime(LocalDateTime.now()) .createTime(LocalDateTime.now())
.build(); .build();
// 发送用户消息到会话 // 发送用户消息到会话
if (request.getConversationId() != null) { if (chatRequest.getConversationId() != null) {
messagingTemplate.convertAndSend("/topic/conversation/" + request.getConversationId(), userMessage); messagingTemplate.convertAndSend("/topic/conversation/" + chatRequest.getConversationId(), userMessage);
} }
// 发送给用户私有队列 // 发送给用户私有队列
messagingTemplate.convertAndSendToUser(request.getSenderId(), "/queue/messages", userMessage); messagingTemplate.convertAndSendToUser(chatRequest.getSenderId(), "/queue/messages", userMessage);
// 发送AI思考状态 // 发送AI思考状态
sendAiThinkingMessage(request.getSenderId(), request.getConversationId()); sendAiThinkingMessage(chatRequest.getSenderId(), chatRequest.getConversationId());
// 异步调用AI服务 // 异步调用AI服务
processAiResponse(request); processAiResponse(chatRequest);
} catch (Exception e) { } catch (Exception e) {
log.error("处理聊天消息失败", e); log.error("处理聊天消息失败", e);
sendErrorMessage(request.getSenderId(), "消息处理失败,请稍后重试"); sendErrorMessage(getUserId(principal, sessionId), "消息处理失败,请稍后重试");
} }
} }
@@ -115,39 +107,27 @@ public class WebSocketServiceImpl implements WebSocketService {
@Override @Override
public void handleUserConnect(ConnectRequest request, String sessionId, Principal principal) { public void handleUserConnect(ConnectRequest request, String sessionId, Principal principal) {
try { try {
String userId = request.getUserId(); // 设置默认值
boolean isAuthenticated = false; setConnectRequestDefaults(request, principal, sessionId);
// 优先从Principal获取认证用户信息
if (principal != null) {
userId = principal.getName();
// 检查是否是认证用户(不是访客)
isAuthenticated = !userId.startsWith("guest_");
}
// 如果还没有userId,生成访客ID
if (userId == null) {
userId = "guest_" + sessionId;
}
log.info("用户连接WebSocket: userId={}, sessionId={}, authenticated={}", 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() WebSocketMessage connectMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString()) .messageId(UUID.randomUUID().toString())
.type(WebSocketMessage.MessageType.CONNECTION) .type("CONNECTION")
.content("连接成功") .content("连接成功")
.senderId("system") .senderId("system")
.senderType(WebSocketMessage.SenderType.SYSTEM) .senderType("SYSTEM")
.status(WebSocketMessage.MessageStatus.SENT) .status("SENT")
.createTime(LocalDateTime.now()) .createTime(LocalDateTime.now())
.build(); .build();
messagingTemplate.convertAndSendToUser(userId, "/queue/messages", connectMessage); messagingTemplate.convertAndSendToUser(request.getUserId(), "/queue/messages", connectMessage);
} catch (Exception e) { } catch (Exception e) {
log.error("处理用户连接失败", e); log.error("处理用户连接失败", e);
@@ -182,11 +162,11 @@ public class WebSocketServiceImpl implements WebSocketService {
// 发送心跳响应 // 发送心跳响应
WebSocketMessage heartbeatMessage = WebSocketMessage.builder() WebSocketMessage heartbeatMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString()) .messageId(UUID.randomUUID().toString())
.type(WebSocketMessage.MessageType.HEARTBEAT) .type("HEARTBEAT")
.content("pong") .content("pong")
.senderId("system") .senderId("system")
.senderType(WebSocketMessage.SenderType.SYSTEM) .senderType("SYSTEM")
.status(WebSocketMessage.MessageStatus.SENT) .status("SENT")
.createTime(LocalDateTime.now()) .createTime(LocalDateTime.now())
.build(); .build();
@@ -206,11 +186,11 @@ public class WebSocketServiceImpl implements WebSocketService {
WebSocketMessage thinkingMessage = WebSocketMessage.builder() WebSocketMessage thinkingMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString()) .messageId(UUID.randomUUID().toString())
.conversationId(conversationId) .conversationId(conversationId)
.type(WebSocketMessage.MessageType.AI_THINKING) .type("AI_THINKING")
.content("AI正在思考中...") .content("AI正在思考中...")
.senderId("ai") .senderId("ai")
.senderType(WebSocketMessage.SenderType.AI) .senderType("AI")
.status(WebSocketMessage.MessageStatus.SENT) .status("SENT")
.createTime(LocalDateTime.now()) .createTime(LocalDateTime.now())
.build(); .build();
@@ -246,7 +226,7 @@ public class WebSocketServiceImpl implements WebSocketService {
userMessage.setUserId(userId); userMessage.setUserId(userId);
userMessage.setCreateBy(userId); // 设置创建人为当前用户 userMessage.setCreateBy(userId); // 设置创建人为当前用户
userMessage userMessage
.setUserType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest"); .setUserType("USER".equals(request.getSenderType()) ? "registered" : "guest");
userMessage.setContent(request.getContent()); userMessage.setContent(request.getContent());
userMessage.setType("text"); userMessage.setType("text");
userMessage.setSender("user"); userMessage.setSender("user");
@@ -281,11 +261,11 @@ public class WebSocketServiceImpl implements WebSocketService {
private void sendErrorMessage(String userId, String errorContent) { private void sendErrorMessage(String userId, String errorContent) {
WebSocketMessage errorMessage = WebSocketMessage.builder() WebSocketMessage errorMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString()) .messageId(UUID.randomUUID().toString())
.type(WebSocketMessage.MessageType.ERROR) .type("ERROR")
.content(errorContent) .content(errorContent)
.senderId("system") .senderId("system")
.senderType(WebSocketMessage.SenderType.SYSTEM) .senderType("SYSTEM")
.status(WebSocketMessage.MessageStatus.SENT) .status("SENT")
.createTime(LocalDateTime.now()) .createTime(LocalDateTime.now())
.build(); .build();
@@ -309,7 +289,7 @@ public class WebSocketServiceImpl implements WebSocketService {
Conversation conversation = Conversation.builder() Conversation conversation = Conversation.builder()
.userId(userId) .userId(userId)
.userType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest") .userType("USER".equals(request.getSenderType()) ? "registered" : "guest")
.title("新对话") .title("新对话")
.type("chat") .type("chat")
.conversationStatus("active") .conversationStatus("active")
@@ -341,7 +321,7 @@ public class WebSocketServiceImpl implements WebSocketService {
// 如果会话不存在,创建一个 // 如果会话不存在,创建一个
conversation = Conversation.builder() conversation = Conversation.builder()
.userId(userId) .userId(userId)
.userType(request.getSenderType() == ChatRequest.SenderType.USER ? "registered" : "guest") .userType("USER".equals(request.getSenderType()) ? "registered" : "guest")
.title("对话") .title("对话")
.type("chat") .type("chat")
.conversationStatus("active") .conversationStatus("active")
@@ -453,11 +433,11 @@ public class WebSocketServiceImpl implements WebSocketService {
WebSocketMessage fallbackMessage = WebSocketMessage.builder() WebSocketMessage fallbackMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString()) .messageId(UUID.randomUUID().toString())
.conversationId(conversationId) .conversationId(conversationId)
.type(WebSocketMessage.MessageType.TEXT) .type("TEXT")
.content(aiReply) .content(aiReply)
.senderId("ai") .senderId("ai")
.senderType(WebSocketMessage.SenderType.AI) .senderType("AI")
.status(WebSocketMessage.MessageStatus.SENT) .status("SENT")
.createTime(LocalDateTime.now()) .createTime(LocalDateTime.now())
.build(); .build();
@@ -488,11 +468,11 @@ public class WebSocketServiceImpl implements WebSocketService {
WebSocketMessage aiMessage = WebSocketMessage.builder() WebSocketMessage aiMessage = WebSocketMessage.builder()
.messageId(UUID.randomUUID().toString()) .messageId(UUID.randomUUID().toString())
.conversationId(conversationId) .conversationId(conversationId)
.type(WebSocketMessage.MessageType.TEXT) .type("TEXT")
.content(content) .content(content)
.senderId("ai") .senderId("ai")
.senderType(WebSocketMessage.SenderType.AI) .senderType("AI")
.status(WebSocketMessage.MessageStatus.SENT) .status("SENT")
.createTime(LocalDateTime.now()) .createTime(LocalDateTime.now())
.build(); .build();
@@ -534,4 +514,95 @@ public class WebSocketServiceImpl implements WebSocketService {
log.debug("没有换行符,返回原始内容"); log.debug("没有换行符,返回原始内容");
return new String[]{aiReply}; 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();
}
}
@@ -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);
}
}
@@ -1,57 +1,94 @@
package com.emotion.util; package com.emotion.util;
import com.emotion.util.UserContextHolder;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/** /**
* 用户上下文工具类 * 用户上下文工具类
* 提供便捷的用户上下文操作方法 * 提供便捷的方法操作用户上下文信息
* *
* @author emotion-museum * @author emotion-museum
* @date 2025-07-23 * @date 2025-07-25
*/ */
@Slf4j @Slf4j
public class UserContextUtils { public class UserContextUtils {
/** /**
* 获取当前用户ID,如果为空则返回默认值 * 获取当前用户ID
* *
* @param defaultValue 默认值 * @return 当前用户ID,如果未登录则返回null
* @return 用户ID
*/ */
public static String getCurrentUserIdOrDefault(String defaultValue) { public static String getCurrentUserId() {
String userId = UserContextHolder.getCurrentUserId(); return UserContextHolder.getCurrentUserId();
return userId != null ? userId : defaultValue;
} }
/** /**
* 获取当前用户ID,如果为空则返回"system" * 获取当前用户
* *
* @return 用户ID * @return 当前用户名,如果未登录则返回null
*/ */
public static String getCurrentUserIdOrSystem() { public static String getCurrentUsername() {
return getCurrentUserIdOrDefault("system"); return UserContextHolder.getCurrentUsername();
} }
/** /**
* 获取当前用户名,如果为空则返回默认值 * 获取当前用户类型
* *
* @param defaultValue 默认值 * @return 当前用户类型,如果未登录则返回null
* @return 用户名
*/ */
public static String getCurrentUsernameOrDefault(String defaultValue) { public static String getCurrentUserType() {
String username = UserContextHolder.getCurrentUsername(); return UserContextHolder.getCurrentUserType();
return username != null ? username : defaultValue;
} }
/** /**
* 获取当前用户名,如果为空则返回"guest" * 获取客户端IP
* *
* @return 用户名 * @return 客户端IP
*/ */
public static String getCurrentUsernameOrGuest() { public static String getClientIp() {
return getCurrentUsernameOrDefault("guest"); 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(); String userType = UserContextHolder.getCurrentUserType();
return userType != null ? userType : defaultValue; return userType != null ? userType : defaultValue;
} }
/** /**
* 获取当前用户类型,如果为空则返回"GUEST" * 获取当前用户类型,如果为空则返回"GUEST"
* *
@@ -71,7 +108,7 @@ public class UserContextUtils {
public static String getCurrentUserTypeOrGuest() { public static String getCurrentUserTypeOrGuest() {
return getCurrentUserTypeOrDefault("GUEST"); return getCurrentUserTypeOrDefault("GUEST");
} }
/** /**
* 获取客户端IP,如果为空则返回默认值 * 获取客户端IP,如果为空则返回默认值
* *
@@ -82,7 +119,7 @@ public class UserContextUtils {
String clientIp = UserContextHolder.getClientIp(); String clientIp = UserContextHolder.getClientIp();
return clientIp != null ? clientIp : defaultValue; return clientIp != null ? clientIp : defaultValue;
} }
/** /**
* 获取客户端IP,如果为空则返回"unknown" * 获取客户端IP,如果为空则返回"unknown"
* *
@@ -91,7 +128,7 @@ public class UserContextUtils {
public static String getClientIpOrUnknown() { public static String getClientIpOrUnknown() {
return getClientIpOrDefault("unknown"); return getClientIpOrDefault("unknown");
} }
/** /**
* 获取请求ID,如果为空则返回默认值 * 获取请求ID,如果为空则返回默认值
* *
@@ -102,87 +139,27 @@ public class UserContextUtils {
String requestId = UserContextHolder.getRequestId(); String requestId = UserContextHolder.getRequestId();
return requestId != null ? requestId : defaultValue; return requestId != null ? requestId : defaultValue;
} }
/** /**
* 获取请求ID,如果为空则返回"unknown" * 获取请求ID,如果为空则返回随机UUID
* *
* @return 请求ID * @return 请求ID
*/ */
public static String getRequestIdOrUnknown() { public static String getRequestIdOrRandom() {
return getRequestIdOrDefault("unknown"); String requestId = UserContextHolder.getRequestId();
} if (requestId != null) {
return requestId;
/**
* 检查当前用户是否为访客
*
* @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("清理默认系统用户上下文");
}
} }
return java.util.UUID.randomUUID().toString().replace("-", "");
} }
/** /**
* 临时设置用户上下文执行操作 * 执行带有临时上下文操作
* *
* @param userId 用户ID * @param userId 用户ID
* @param username 用户名 * @param username 用户名
* @param userType 用户类型 * @param userType 用户类型
* @param operation 操作 * @param operation 要执行的操作
*/ */
public static void executeWithTempContext(String userId, String username, String userType, Runnable 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";
}
}
@@ -1,10 +1,12 @@
package com.emotionmuseum.ai.controller; package com.emotionmuseum.ai.controller;
import com.emotionmuseum.ai.request.*; import com.emotionmuseum.ai.dto.GuestChatRequest;
import com.emotionmuseum.ai.response.*; import com.emotionmuseum.ai.dto.GuestChatResponse;
import com.emotionmuseum.ai.dto.MessageListResponse;
import com.emotionmuseum.ai.dto.GuestUserInfo; 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.ai.service.GuestChatService;
import com.emotionmuseum.common.interceptor.UserContextInterceptor;
import com.emotionmuseum.common.result.Result; import com.emotionmuseum.common.result.Result;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@@ -12,31 +14,34 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/** /**
* 访客聊天控制器 * 访客聊天控制器
* * 提供访客模式下的聊天功能
*
* @author emotion-museum * @author emotion-museum
* @since 2025-07-13 * @since 2025-07-24
*/ */
@Slf4j @Slf4j
@RestController @RestController
@RequestMapping("/api/ai/guest") @RequestMapping("/ai/guest")
@RequiredArgsConstructor @RequiredArgsConstructor
@Tag(name = "访客聊天", description = "访客模式AI聊天接口") @Tag(name = "访客聊天", description = "访客模式下的AI聊天功能")
public class GuestChatController { public class GuestChatController {
private final GuestChatService guestChatService; private final GuestChatService guestChatService;
private final UserContextInterceptor userContextInterceptor = new UserContextInterceptor();
@PostMapping("/chat") @PostMapping("/chat")
@Operation(summary = "访客聊天", description = "访客模式下发送消息并获取AI回复") @Operation(summary = "访客聊天", description = "访客模式下发送消息并获取AI回复")
public Result<com.emotionmuseum.ai.dto.GuestChatResponse> guestChat( public Result<GuestChatResponse> guestChat(
@RequestBody com.emotionmuseum.ai.dto.GuestChatRequest request) { @RequestBody GuestChatRequest request) {
// 自动获取客户端IP和User-Agent // 自动获取客户端IP和User-Agent
String clientIp = getClientIp(); String clientIp = getClientIp();
@@ -62,8 +67,8 @@ public class GuestChatController {
return (Result) guestChatService.getGuestConversations(clientIp, pageNum, pageSize); return (Result) guestChatService.getGuestConversations(clientIp, pageNum, pageSize);
} }
@GetMapping("/conversation/{conversationId}/messages") @GetMapping("/messages/{conversationId}")
@Operation(summary = "获取访客会话消息", description = "获取指定会话的消息列表") @Operation(summary = "获取会话消息", description = "获取指定会话的所有消息")
public Result<List<MessageListResponse>> getGuestConversationMessages( public Result<List<MessageListResponse>> getGuestConversationMessages(
@Parameter(description = "会话ID") @PathVariable String conversationId, @Parameter(description = "会话ID") @PathVariable String conversationId,
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum, @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer pageNum,
@@ -72,11 +77,11 @@ public class GuestChatController {
String clientIp = getClientIp(); String clientIp = getClientIp();
log.info("获取访客会话消息: IP={}, ConversationId={}", clientIp, conversationId); 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") @PostMapping("/end/{conversationId}")
@Operation(summary = "结束访客会话", description = "结束指定的访客会话") @Operation(summary = "结束会话", description = "结束指定的访客会话")
public Result<Void> endGuestConversation( public Result<Void> endGuestConversation(
@Parameter(description = "会话ID") @PathVariable String conversationId) { @Parameter(description = "会话ID") @PathVariable String conversationId) {
@@ -86,7 +91,7 @@ public class GuestChatController {
return guestChatService.endGuestConversation(conversationId, clientIp); return guestChatService.endGuestConversation(conversationId, clientIp);
} }
@GetMapping("/user/info") @GetMapping("/user-info")
@Operation(summary = "获取访客用户信息", description = "根据IP地址获取或创建访客用户信息") @Operation(summary = "获取访客用户信息", description = "根据IP地址获取或创建访客用户信息")
public Result<GuestUserInfo> getGuestUserInfo() { public Result<GuestUserInfo> getGuestUserInfo() {
String clientIp = getClientIp(); String clientIp = getClientIp();
@@ -94,38 +99,23 @@ public class GuestChatController {
log.info("获取访客用户信息: IP={}", clientIp); log.info("获取访客用户信息: IP={}", clientIp);
return guestChatService.getOrCreateGuestUser(clientIp, userAgent); return guestChatService.getGuestUserInfo(clientIp, userAgent);
} }
@PostMapping("/emotion/analyze") @PostMapping("/test-split")
@Operation(summary = "访客情绪分析", description = "分析访客输入文本的情绪") @Operation(summary = "测试拆分功能", description = "测试AI回复的拆分功能")
public Result<com.emotionmuseum.ai.dto.EmotionAnalysisResponse> analyzeGuestEmotion( public Result<GuestChatResponse> testSplitFunction(
@RequestBody com.emotionmuseum.ai.dto.EmotionAnalysisRequest request) { @RequestBody GuestChatRequest request) {
String clientIp = getClientIp(); log.info("测试拆分功能: Message={}", request.getMessage());
log.info("访客情绪分析: IP={}, Text={}", clientIp, request.getText());
return guestChatService.analyzeGuestEmotion(request, clientIp); // 根据消息内容生成不同的模拟回复
}
@GetMapping("/health")
@Operation(summary = "访客服务健康检查", description = "检查访客聊天服务状态")
public Result<Boolean> healthCheck() {
return Result.success(true);
}
@PostMapping("/test/split")
@Operation(summary = "测试消息拆分功能", description = "测试AI回复消息的拆分功能")
public Result<com.emotionmuseum.ai.dto.GuestChatResponse> testMessageSplit(
@RequestBody com.emotionmuseum.ai.dto.GuestChatRequest request) {
log.info("测试消息拆分功能: message={}", request.getMessage());
// 模拟包含不同换行符的AI回复进行测试
String mockAiReply; String mockAiReply;
if (request.getMessage().contains("双换行")) { if (request.getMessage().contains("双换行")) {
mockAiReply = "这是第一段回复,介绍基本功能。我可以帮助你进行日常对话。\n\n" + mockAiReply = "这是第一段回复,介绍基本功能。\n\n" +
"这是第二段回复,详细说明聊天功能。我能理解你的情感并给出合适的回应。\n\n" + "这是第二段回复,说明聊天功能。\n\n" +
"这是第三段回复,介绍情感分析功能。我可以分析你的情绪状态并提供建议。"; "这是第三段回复,介绍情感分析\n\n" +
"这是第四段回复,提供使用建议。";
} else if (request.getMessage().contains("单换行")) { } else if (request.getMessage().contains("单换行")) {
mockAiReply = "这是第一行回复,介绍基本功能。\n" + mockAiReply = "这是第一行回复,介绍基本功能。\n" +
"这是第二行回复,说明聊天功能。\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.setGuestUserId("test_guest_user");
response.setGuestNickname("测试用户"); response.setGuestNickname("测试用户");
response.setConversationId("test_conversation_" + System.currentTimeMillis()); response.setConversationId("test_conversation_" + System.currentTimeMillis());
@@ -166,29 +156,7 @@ public class GuestChatController {
} }
var request = attributes.getRequest(); var request = attributes.getRequest();
String ip = request.getHeader("X-Forwarded-For"); return userContextInterceptor.getClientIpAddress(request);
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;
} catch (Exception e) { } catch (Exception e) {
return "127.0.0.1"; return "127.0.0.1";
} }
@@ -211,4 +179,4 @@ public class GuestChatController {
return "Unknown"; return "Unknown";
} }
} }
} }