Compare commits

...

52 Commits

Author SHA1 Message Date
glx c243ae918e 忽略文件 3 days ago
glx fb21d08d3d 删除重复注解 3 days ago
glx e47f65ac45 Merge branch 'glx' into glx_phase2 3 days ago
glx c0132e5eef 游标分页字段加密 3 days ago
glx 6bcc35ced8 增加领域日记 动物笔记 可见性权限过滤 3 days ago
glx b21949693e 增加用户关注接口 4 days ago
glx ff17ff2847 修改举报和申诉模块 4 days ago
glx 1d7928e7fb 增加审核后更新业务状态 5 days ago
glx 3455b64f54 修改图片审核方式为异步 5 days ago
glx bfef04ae63 统一审核任务状态 1 week ago
glx b756b1a86d 增加Oss审核回调 用户举报申诉接口 1 week ago
glx 3b7760051e 增加文本审核plus 1 week ago
glx efff642504 修改通用审核功能 2 weeks ago
glx 5970ad0847 增加审核配置接口 2 weeks ago
glx ee804cf371 增加内容审核模型 2 weeks ago
glx f163514d4c 修改领养申请接口 2 weeks ago
glx b8b6015f56 增加领养申请模块 2 weeks ago
glx b4ca8a6e12 增加手动刷新任务状态接口 2 weeks ago
glx 252db3db99 增加用户作品点赞模块 2 weeks ago
glx abbe200341 增加用户作品模块接口 2 weeks ago
glx 98e4be3826 修改接口请求方式 2 weeks ago
glx 481d72888d 增加首页用户作品展示 2 weeks ago
glx 9aa59d888a 增加任务失败超时退还积分 2 weeks ago
glx 89b0e34eb0 修改回调保存内容方式 2 weeks ago
glx e76df77ce5 增加订阅消息版本管理接口 3 weeks ago
glx 33b305b940 增加用户作品模型 3 weeks ago
glx 9cda7d3986 修改领养日记部分sql问题 3 weeks ago
glx 1b665c2f2c 修复视频回调错误 3 weeks ago
glx 141dca210e 修复部分接口问题 3 weeks ago
glx a0add60c61 增加领养日记评论模块接口 3 weeks ago
glx 19a4ccfcf9 增加领养日记点赞收藏瀑布流接口 3 weeks ago
glx 7e5e1873b6 增加领养日记查询点赞接口 3 weeks ago
glx 784b9b4a13 修复订阅消息问题 3 weeks ago
glx 77f2966237 临时提交代码 3 weeks ago
glx 9fdb7d1900 增加ai生成任务历史查询接口 3 weeks ago
glx 4797810162 领养日记模块接口实现 3 weeks ago
glx 8659fbece5 增加领养日记模块 3 weeks ago
glx 4d1a22f5de 修改返回参数 4 weeks ago
glx 3b73dc2135 增加小程序订阅消息接口 4 weeks ago
glx 65fc5f0a81 修改视频回调接口 4 weeks ago
glx d138d40869 修改接口参数 4 weeks ago
glx e5489fe290 增加视频生成任务接口 4 weeks ago
glx 90ef9bd7b2 修改单图片生成任务 4 weeks ago
glx 3a1a0c94c4 增加ai服务调用接口 1 month ago
glx 60c76855e0 增加浏览记录使用布隆过滤器过滤已看内容 1 month ago
glx 3bd113ade7 增加动物笔记瀑布流接口 1 month ago
glx 017f226ac4 增加动物笔记点赞收藏接口 1 month ago
glx 541e18749f 增加评论点赞功能 1 month ago
glx f9313761c3 增加二级评论查询接口 1 month ago
glx 8ee2b6d6cb 增加动物笔记评论接口 1 month ago
glx 30b8b59860 优化笔记评论接口 1 month ago
glx cc5a360712 增加动物笔记评论模块 1 month ago
  1. 4
      .gitignore
  2. 18
      pom.xml
  3. 2
      src/main/java/com/youlai/boot/YouLaiBootApplication.java
  4. 50
      src/main/java/com/youlai/boot/admin/constant/AuditConstants.java
  5. 41
      src/main/java/com/youlai/boot/admin/controller/AdoptionApplicationManageController.java
  6. 43
      src/main/java/com/youlai/boot/admin/controller/ContentAuditAppealController.java
  7. 70
      src/main/java/com/youlai/boot/admin/controller/ContentAuditConfigController.java
  8. 43
      src/main/java/com/youlai/boot/admin/controller/ReportManageController.java
  9. 18
      src/main/java/com/youlai/boot/admin/converter/AuditConfigConverter.java
  10. 32
      src/main/java/com/youlai/boot/admin/job/AsyncAuditPollJob.java
  11. 75
      src/main/java/com/youlai/boot/admin/job/AuditTimeoutJob.java
  12. 14
      src/main/java/com/youlai/boot/admin/mapper/MiniContentAuditConfigMapper.java
  13. 14
      src/main/java/com/youlai/boot/admin/mapper/MiniContentAuditMapper.java
  14. 14
      src/main/java/com/youlai/boot/admin/mapper/MiniContentAuditTaskMapper.java
  15. 21
      src/main/java/com/youlai/boot/admin/model/dto/AuditContentDTO.java
  16. 91
      src/main/java/com/youlai/boot/admin/model/entity/MiniContentAudit.java
  17. 82
      src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditConfig.java
  18. 118
      src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditTask.java
  19. 20
      src/main/java/com/youlai/boot/admin/model/form/AppealHandleForm.java
  20. 28
      src/main/java/com/youlai/boot/admin/model/form/AuditConfigForm.java
  21. 20
      src/main/java/com/youlai/boot/admin/model/form/ReportHandleForm.java
  22. 16
      src/main/java/com/youlai/boot/admin/model/query/AppealQuery.java
  23. 16
      src/main/java/com/youlai/boot/admin/model/query/AuditConfigQuery.java
  24. 19
      src/main/java/com/youlai/boot/admin/model/query/ReportQuery.java
  25. 50
      src/main/java/com/youlai/boot/admin/model/vo/AppealVO.java
  26. 47
      src/main/java/com/youlai/boot/admin/model/vo/AuditConfigVO.java
  27. 59
      src/main/java/com/youlai/boot/admin/model/vo/ReportVO.java
  28. 27
      src/main/java/com/youlai/boot/admin/service/AuditExecutorService.java
  29. 23
      src/main/java/com/youlai/boot/admin/service/BizAuditStatusHandler.java
  30. 16
      src/main/java/com/youlai/boot/admin/service/ContentAuditAppealService.java
  31. 21
      src/main/java/com/youlai/boot/admin/service/ContentAuditConfigService.java
  32. 14
      src/main/java/com/youlai/boot/admin/service/ContentAuditService.java
  33. 23
      src/main/java/com/youlai/boot/admin/service/ContentAuditTaskService.java
  34. 10
      src/main/java/com/youlai/boot/admin/service/ContentLookupService.java
  35. 8
      src/main/java/com/youlai/boot/admin/service/OssCallbackService.java
  36. 16
      src/main/java/com/youlai/boot/admin/service/ReportManageService.java
  37. 709
      src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java
  38. 172
      src/main/java/com/youlai/boot/admin/service/impl/BizAuditStatusHandlerImpl.java
  39. 96
      src/main/java/com/youlai/boot/admin/service/impl/ContentAuditAppealServiceImpl.java
  40. 105
      src/main/java/com/youlai/boot/admin/service/impl/ContentAuditConfigServiceImpl.java
  41. 76
      src/main/java/com/youlai/boot/admin/service/impl/ContentAuditServiceImpl.java
  42. 108
      src/main/java/com/youlai/boot/admin/service/impl/ContentAuditTaskServiceImpl.java
  43. 153
      src/main/java/com/youlai/boot/admin/service/impl/ContentLookupServiceImpl.java
  44. 222
      src/main/java/com/youlai/boot/admin/service/impl/OssCallbackServiceImpl.java
  45. 109
      src/main/java/com/youlai/boot/admin/service/impl/ReportManageServiceImpl.java
  46. 30
      src/main/java/com/youlai/boot/codegen/freemarker/MyBatisPlusGenerator.java
  47. 8
      src/main/java/com/youlai/boot/common/constant/RedisConstants.java
  48. 16
      src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java
  49. 291
      src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java
  50. 24
      src/main/java/com/youlai/boot/common/util/CursorEncryptUtil.java
  51. 30
      src/main/java/com/youlai/boot/common/util/HttpContext.java
  52. 51
      src/main/java/com/youlai/boot/mini/controller/AdoptionApplicationController.java
  53. 96
      src/main/java/com/youlai/boot/mini/controller/AdoptionDiaryCommentController.java
  54. 197
      src/main/java/com/youlai/boot/mini/controller/AdoptionDiaryController.java
  55. 200
      src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java
  56. 34
      src/main/java/com/youlai/boot/mini/controller/MiniContentAuditAppealController.java
  57. 54
      src/main/java/com/youlai/boot/mini/controller/MiniFollowController.java
  58. 34
      src/main/java/com/youlai/boot/mini/controller/MiniReportController.java
  59. 2
      src/main/java/com/youlai/boot/mini/controller/MiniUserController.java
  60. 66
      src/main/java/com/youlai/boot/mini/controller/StrayAnimalController.java
  61. 94
      src/main/java/com/youlai/boot/mini/controller/StrayAnimalNoteCommentController.java
  62. 94
      src/main/java/com/youlai/boot/mini/controller/UserPostCommentController.java
  63. 187
      src/main/java/com/youlai/boot/mini/controller/UserPostController.java
  64. 28
      src/main/java/com/youlai/boot/mini/job/AiTaskTimeoutJob.java
  65. 108
      src/main/java/com/youlai/boot/mini/job/NoteStatsCalibrateJob.java
  66. 16
      src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionApplicationMapper.java
  67. 20
      src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryCollectMapper.java
  68. 30
      src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryCommentLikeMapper.java
  69. 83
      src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryCommentMapper.java
  70. 20
      src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryLikeMapper.java
  71. 46
      src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryMapper.java
  72. 20
      src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryMediaMapper.java
  73. 21
      src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryViewMapper.java
  74. 14
      src/main/java/com/youlai/boot/mini/mapper/MiniAiGenerationTaskMapper.java
  75. 14
      src/main/java/com/youlai/boot/mini/mapper/MiniAiTaskMediaMapper.java
  76. 14
      src/main/java/com/youlai/boot/mini/mapper/MiniContentAuditAppealMapper.java
  77. 14
      src/main/java/com/youlai/boot/mini/mapper/MiniReportMapper.java
  78. 8
      src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalMapper.java
  79. 30
      src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteCollectMapper.java
  80. 30
      src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteCommentLikeMapper.java
  81. 62
      src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteCommentMapper.java
  82. 30
      src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteLikeMapper.java
  83. 53
      src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteMapper.java
  84. 27
      src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteViewMapper.java
  85. 28
      src/main/java/com/youlai/boot/mini/mapper/MiniUserFollowMapper.java
  86. 20
      src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCollectMapper.java
  87. 21
      src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentLikeMapper.java
  88. 48
      src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentMapper.java
  89. 20
      src/main/java/com/youlai/boot/mini/mapper/MiniUserPostLikeMapper.java
  90. 41
      src/main/java/com/youlai/boot/mini/mapper/MiniUserPostMapper.java
  91. 20
      src/main/java/com/youlai/boot/mini/mapper/MiniUserPostMediaMapper.java
  92. 21
      src/main/java/com/youlai/boot/mini/mapper/MiniUserPostViewMapper.java
  93. 17
      src/main/java/com/youlai/boot/mini/mapper/MiniUserSubscribeMapper.java
  94. 28
      src/main/java/com/youlai/boot/mini/model/dto/DeleteAdoptionDiaryDTO.java
  95. 36
      src/main/java/com/youlai/boot/mini/model/dto/DeleteAdoptionDiaryMediaDTO.java
  96. 20
      src/main/java/com/youlai/boot/mini/model/dto/DeleteUserPostDTO.java
  97. 24
      src/main/java/com/youlai/boot/mini/model/dto/DeleteUserPostMediaDTO.java
  98. 31
      src/main/java/com/youlai/boot/mini/model/dto/GenerateResponse.java
  99. 48
      src/main/java/com/youlai/boot/mini/model/dto/GenerationData.java
  100. 26
      src/main/java/com/youlai/boot/mini/model/dto/ImageData.java

4
.gitignore

@ -17,3 +17,7 @@ docker/*/data/
docker/minio/config
docker/xxljob/logs
application-youlai.yml
.claude
CLAUDE.md
content-audit-design.md
.mcp.json

18
pom.xml

@ -72,6 +72,11 @@
<javacv.platform.version>1.5.13</javacv.platform.version>
<!-- 阿里内容审核 -->
<aliyun.green20220302.version>3.3.3</aliyun.green20220302.version>
<!-- 阿里fastjson -->
<alibaba.fastjson.version>2.0.56</alibaba.fastjson.version>
</properties>
@ -319,6 +324,19 @@
<version>${dynamic-datasource.version}</version>
</dependency>-->
<!-- 阿里云内容审核-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>green20220302</artifactId>
<version>${aliyun.green20220302.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${alibaba.fastjson.version}</version>
</dependency>
</dependencies>
<build>

2
src/main/java/com/youlai/boot/YouLaiBootApplication.java

@ -3,6 +3,7 @@ package com.youlai.boot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 应用启动类
@ -10,6 +11,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author Ray.Hao
* @since 0.0.1
*/
@EnableScheduling
@SpringBootApplication
@MapperScan("com.youlai.boot.**.mapper")
public class YouLaiBootApplication {

50
src/main/java/com/youlai/boot/admin/constant/AuditConstants.java

@ -0,0 +1,50 @@
package com.youlai.boot.admin.constant;
/**
* 内容审核状态常量
*/
public final class AuditConstants {
private AuditConstants() {}
// ======================== 通用状态(task + audit 共用) ========================
/** 审核中 */
public static final String STATUS_REVIEWING = "reviewing";
/** 审核通过 */
public static final String STATUS_PASSED = "passed";
/** 审核不通过 */
public static final String STATUS_REJECTED = "rejected";
/** 待人工审核 */
public static final String STATUS_MANUAL_REVIEW = "manual_review";
// ======================== audit 专用状态 ========================
/** 申诉中 */
public static final String STATUS_APPEALING = "appealing";
// ======================== 风险等级 ========================
public static final String RISK_NONE = "none";
public static final String RISK_MEDIUM = "medium";
public static final String RISK_HIGH = "high";
// ======================== 审核策略 ========================
public static final String STRATEGY_AUTO = "auto";
public static final String STRATEGY_NORMAL = "normal";
public static final String STRATEGY_CAUTIOUS = "cautious";
// ======================== 审核类型 ========================
public static final String AUDIT_TYPE_MACHINE = "machine";
public static final String AUDIT_TYPE_MANUAL = "manual";
// ======================== 触发类型 ========================
public static final String TRIGGER_CREATE = "create";
public static final String TRIGGER_REPORT = "report";
// ======================== 业务审核状态 ========================
/** 审核通过 (可直接发布/展示) */
public static final Integer BIZ_AUDIT_STATUS_PASSED = 0;
/** 审核中 */
public static final Integer BIZ_AUDIT_STATUS_REVIEWING = 1;
/** 审核未通过 */
public static final Integer BIZ_AUDIT_STATUS_REJECTED = 2;
}

41
src/main/java/com/youlai/boot/admin/controller/AdoptionApplicationManageController.java

@ -0,0 +1,41 @@
package com.youlai.boot.admin.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.mini.model.form.AdoptionApplicationAuditForm;
import com.youlai.boot.mini.model.query.AdoptionApplicationQuery;
import com.youlai.boot.mini.model.vo.AdoptionApplicationVO;
import com.youlai.boot.mini.service.AdoptionApplicationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "管理端领养申请相关接口")
@RestController
@RequestMapping("/api/v1/admin/adoptionApplication")
@RequiredArgsConstructor
public class AdoptionApplicationManageController {
private final AdoptionApplicationService adoptionApplicationService;
@Operation(summary = "分页查询领养申请列表")
@GetMapping("/list")
@Log(module = LogModuleEnum.ADOPTION_APPLICATION, value = ActionTypeEnum.LIST)
public PageResult<AdoptionApplicationVO> list(AdoptionApplicationQuery query) {
return PageResult.success(adoptionApplicationService.getAllApplications(query));
}
@Operation(summary = "审核领养申请")
@PostMapping("/audit")
@Log(module = LogModuleEnum.ADOPTION_APPLICATION, value = ActionTypeEnum.UPDATE)
public Result<?> auditApplication(@Valid @RequestBody AdoptionApplicationAuditForm form) {
adoptionApplicationService.auditApplication(form);
return Result.success();
}
}

43
src/main/java/com/youlai/boot/admin/controller/ContentAuditAppealController.java

@ -0,0 +1,43 @@
package com.youlai.boot.admin.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.admin.model.form.AppealHandleForm;
import com.youlai.boot.admin.model.query.AppealQuery;
import com.youlai.boot.admin.model.vo.AppealVO;
import com.youlai.boot.admin.service.ContentAuditAppealService;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "管理端-申诉处理相关接口")
@RestController
@RequestMapping("/api/v1/admin/appeal")
@RequiredArgsConstructor
public class ContentAuditAppealController {
private final ContentAuditAppealService appealService;
@Operation(summary = "分页查询申诉列表")
@GetMapping("/list")
@Log(module = LogModuleEnum.CONTENT_AUDIT_APPEAL, value = ActionTypeEnum.LIST)
public PageResult<AppealVO> listAppeal(@Valid AppealQuery query) {
IPage<AppealVO> result = appealService.pageAppeal(query);
return PageResult.success(result);
}
@Operation(summary = "处理申诉")
@PostMapping("/handle/{id}")
@Log(module = LogModuleEnum.CONTENT_AUDIT_APPEAL, value = ActionTypeEnum.UPDATE)
public Result<Void> handleAppeal(@PathVariable Long id, @Valid @RequestBody AppealHandleForm form) {
appealService.handleAppeal(id, form);
return Result.success();
}
}

70
src/main/java/com/youlai/boot/admin/controller/ContentAuditConfigController.java

@ -0,0 +1,70 @@
package com.youlai.boot.admin.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.admin.model.form.AuditConfigForm;
import com.youlai.boot.admin.model.query.AuditConfigQuery;
import com.youlai.boot.admin.model.vo.AuditConfigVO;
import com.youlai.boot.admin.service.ContentAuditConfigService;
import com.youlai.boot.admin.service.OssCallbackService;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "管理端-审核配置相关接口")
@RestController
@RequestMapping("/api/v1/admin/auditConfig")
@RequiredArgsConstructor
public class ContentAuditConfigController {
private final ContentAuditConfigService contentAuditConfigService;
private final OssCallbackService ossCallbackService;
@Operation(summary = "分页查询审核配置列表")
@GetMapping("/list")
@Log(module = LogModuleEnum.CONTENT_AUDIT_CONFIG, value = ActionTypeEnum.LIST)
public PageResult<AuditConfigVO> listConfig(@Valid AuditConfigQuery query) {
IPage<AuditConfigVO> result = contentAuditConfigService.pageConfig(query);
return PageResult.success(result);
}
@Operation(summary = "新增审核配置")
@PostMapping("/add")
@Log(module = LogModuleEnum.CONTENT_AUDIT_CONFIG, value = ActionTypeEnum.INSERT)
public Result<AuditConfigVO> addConfig(@Valid @RequestBody AuditConfigForm form) {
AuditConfigVO vo = contentAuditConfigService.addConfig(form);
return Result.success(vo);
}
@Operation(summary = "修改审核配置")
@PostMapping("/update/{uuid}")
@Log(module = LogModuleEnum.CONTENT_AUDIT_CONFIG, value = ActionTypeEnum.UPDATE)
public Result<AuditConfigVO> updateConfig(@PathVariable String uuid, @Valid @RequestBody AuditConfigForm form) {
AuditConfigVO vo = contentAuditConfigService.updateConfig(uuid, form);
return Result.success(vo);
}
@Operation(summary = "删除审核配置")
@PostMapping("/delete/{uuid}")
@Log(module = LogModuleEnum.CONTENT_AUDIT_CONFIG, value = ActionTypeEnum.DELETE)
public Result<Void> deleteConfig(@PathVariable String uuid) {
contentAuditConfigService.deleteConfig(uuid);
return Result.success();
}
@Hidden
@Operation(summary = "阿里云OSS增量图片审核回调-测试")
@PostMapping("/images/callback")
public Result<Void> imageCallback(@RequestBody String body) {
ossCallbackService.handleImageCallback(body);
return Result.success();
}
}

43
src/main/java/com/youlai/boot/admin/controller/ReportManageController.java

@ -0,0 +1,43 @@
package com.youlai.boot.admin.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.admin.model.form.ReportHandleForm;
import com.youlai.boot.admin.model.query.ReportQuery;
import com.youlai.boot.admin.model.vo.ReportVO;
import com.youlai.boot.admin.service.ReportManageService;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "管理端-举报管理相关接口")
@RestController
@RequestMapping("/api/v1/admin/report")
@RequiredArgsConstructor
public class ReportManageController {
private final ReportManageService reportService;
@Operation(summary = "分页查询举报列表")
@GetMapping("/list")
@Log(module = LogModuleEnum.REPORT, value = ActionTypeEnum.LIST)
public PageResult<ReportVO> listReport(@Valid ReportQuery query) {
IPage<ReportVO> result = reportService.pageReport(query);
return PageResult.success(result);
}
@Operation(summary = "处理举报")
@PostMapping("/handle/{id}")
@Log(module = LogModuleEnum.REPORT, value = ActionTypeEnum.UPDATE)
public Result<Void> handleReport(@PathVariable Long id, @Valid @RequestBody ReportHandleForm form) {
reportService.handleReport(id, form);
return Result.success();
}
}

18
src/main/java/com/youlai/boot/admin/converter/AuditConfigConverter.java

@ -0,0 +1,18 @@
package com.youlai.boot.admin.converter;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.admin.model.entity.MiniContentAuditConfig;
import com.youlai.boot.admin.model.form.AuditConfigForm;
import com.youlai.boot.admin.model.vo.AuditConfigVO;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface AuditConfigConverter {
Page<AuditConfigVO> toPageVo(Page<MiniContentAuditConfig> page);
AuditConfigVO toVo(MiniContentAuditConfig entity);
MiniContentAuditConfig toEntity(AuditConfigForm form);
}

32
src/main/java/com/youlai/boot/admin/job/AsyncAuditPollJob.java

@ -0,0 +1,32 @@
package com.youlai.boot.admin.job;
import com.youlai.boot.admin.service.AuditExecutorService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 异步审核结果轮询任务图片 + 视频
* <p>
* 一次 DB 查询同时拉取待处理的图片和视频任务按类型分流处理并汇总
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class AsyncAuditPollJob {
private final AuditExecutorService auditExecutorService;
@Scheduled(fixedDelay = 60000)
public void pollAuditResult() {
try {
int count = auditExecutorService.pollAsyncAuditResults();
if (count > 0) {
log.info("异步审核轮询完成, 本次处理 {} 条", count);
}
} catch (Exception e) {
log.error("异步审核轮询任务异常", e);
}
}
}

75
src/main/java/com/youlai/boot/admin/job/AuditTimeoutJob.java

@ -0,0 +1,75 @@
package com.youlai.boot.admin.job;
import com.youlai.boot.admin.constant.AuditConstants;
import com.youlai.boot.admin.model.entity.MiniContentAudit;
import com.youlai.boot.admin.model.entity.MiniContentAuditTask;
import com.youlai.boot.admin.service.ContentAuditService;
import com.youlai.boot.admin.service.ContentAuditTaskService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 审核超时扫描定时任务
* <p>
* 扫描长时间停留在 reviewing 状态的审核记录自动转为人工审核
* 主要覆盖场景
* - 机审 API 部分任务调用失败导致 audit 汇总卡在 reviewing
* - 图片/视频异步审核长时间未回调
* - 其他异常导致的审核流程中断
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class AuditTimeoutJob {
private final ContentAuditService contentAuditService;
private final ContentAuditTaskService contentAuditTaskService;
@Value("${audit.timeout-minutes:30}")
private int timeoutMinutes;
/**
* 5 分钟扫描一次超时的审核记录
*/
@Scheduled(cron = "0 */5 * * * ?")
public void handleTimeoutAudits() {
long thresholdTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(timeoutMinutes);
List<MiniContentAudit> stuckAudits = contentAuditService.lambdaQuery()
.eq(MiniContentAudit::getStatus, AuditConstants.STATUS_REVIEWING)
.lt(MiniContentAudit::getCreateTimestamp, thresholdTimestamp)
.list();
if (stuckAudits.isEmpty()) {
return;
}
log.info("扫描到 {} 条超时审核记录,开始转为人工审核", stuckAudits.size());
for (MiniContentAudit audit : stuckAudits) {
try {
// 更新 status 为 manual_review
contentAuditService.updateAuditStatus(audit.getId(),
AuditConstants.STATUS_MANUAL_REVIEW);
// 将关联的 reviewing 状态任务统一更新为 manual_review
contentAuditTaskService.lambdaUpdate()
.eq(MiniContentAuditTask::getContentAuditId, audit.getId())
.eq(MiniContentAuditTask::getStatus, AuditConstants.STATUS_REVIEWING)
.set(MiniContentAuditTask::getStatus, AuditConstants.STATUS_MANUAL_REVIEW)
.update();
log.info("超时审核已转人工, auditId={}, moduleCode={}, bizId={}",
audit.getId(), audit.getModuleCode(), audit.getBizId());
} catch (Exception e) {
log.error("超时审核转人工失败, auditId={}", audit.getId(), e);
}
}
}
}

14
src/main/java/com/youlai/boot/admin/mapper/MiniContentAuditConfigMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.admin.mapper;
import com.youlai.boot.admin.model.entity.MiniContentAuditConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 内容审核配置表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniContentAuditConfigMapper extends BaseMapper<MiniContentAuditConfig> {
}

14
src/main/java/com/youlai/boot/admin/mapper/MiniContentAuditMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.admin.mapper;
import com.youlai.boot.admin.model.entity.MiniContentAudit;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 审核汇总表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniContentAuditMapper extends BaseMapper<MiniContentAudit> {
}

14
src/main/java/com/youlai/boot/admin/mapper/MiniContentAuditTaskMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.admin.mapper;
import com.youlai.boot.admin.model.entity.MiniContentAuditTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 审核任务表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniContentAuditTaskMapper extends BaseMapper<MiniContentAuditTask> {
}

21
src/main/java/com/youlai/boot/admin/model/dto/AuditContentDTO.java

@ -0,0 +1,21 @@
package com.youlai.boot.admin.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
@Schema(description = "审核内容入参")
public class AuditContentDTO {
@Schema(description = "待审核文本列表")
private List<String> texts;
@Schema(description = "待审核图片URL列表")
private List<String> images;
@Schema(description = "待审核视频URL列表")
private List<String> videos;
}

91
src/main/java/com/youlai/boot/admin/model/entity/MiniContentAudit.java

@ -0,0 +1,91 @@
package com.youlai.boot.admin.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
@Getter
@Setter
@ToString
@Accessors(chain = true)
@TableName("mini_content_audit")
@Schema(description = "审核汇总表")
public class MiniContentAudit implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
@Schema(description = "审核汇总表主键")
private Long id;
@TableField("uuid")
@Schema(description = "UUID,前端交互")
private String uuid;
@TableField("module_code")
@Schema(description = "业务模块")
private String moduleCode;
@TableField("biz_id")
@Schema(description = "业务数据主键ID(日记ID或评论ID)")
private Long bizId;
@TableField("audit_type")
@Schema(description = "审核类型(从config快照)")
private String auditType;
@TableField("status")
@Schema(description = "审核状态:reviewing审核中 / passed通过 / manual_review 待人工审核 / rejected不通过 / appealing申诉中")
private String status;
@TableField("operator")
@Schema(description = "最后操作人ID")
private Long operator;
@TableField("audit_time")
@Schema(description = "审核完成时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date auditTime;
@TableField("create_time")
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@TableField("create_timestamp")
@Schema(description = "创建时间毫秒级时间戳")
private Long createTimestamp;
@TableField("create_by")
@Schema(description = "创建人ID")
private Long createBy;
@TableField("update_time")
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
@TableField("update_timestamp")
@Schema(description = "更新时间毫秒级时间戳")
private Long updateTimestamp;
@TableField("update_by")
@Schema(description = "修改人ID")
private Long updateBy;
@TableField("is_deleted")
@Schema(description = "逻辑删除标识(0-未删除 1-已删除)")
private Boolean deleted;
@TableField("trigger_type")
@Schema(description = "触发来源:create发布/report举报")
private String triggerType;
}

82
src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditConfig.java

@ -0,0 +1,82 @@
package com.youlai.boot.admin.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
@Getter
@Setter
@ToString
@Accessors(chain = true)
@TableName("mini_content_audit_config")
@Schema(description = "内容审核配置表")
public class MiniContentAuditConfig implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
@Schema(description = "审核配置表主键id")
private Long id;
@TableField("uuid")
@Schema(description = "UUID,前端交互")
private String uuid;
@TableField("module_code")
@Schema(description = "业务模块: animal_note / adoption_diary / user_post / note_comment / diary_comment / post_comment / user_avatar / user_nickname / user_introduction")
private String moduleCode;
@TableField("audit_enable")
@Schema(description = "是否开启审核, 0-开启, 1-关闭")
private Boolean auditEnable;
@TableField("audit_type")
@Schema(description = "审核类型: machine机器 / manual手动")
private String auditType;
@TableField("risk_strategy")
@Schema(description = "机审风险策略:auto:NONE → passed,MEDIUM → rejected (AI否决),HIGH → rejected (AI否决);normal:NONE → passed,MEDIUM → manual_review (转人工) ,HIGH → rejected (高风险直接否决); cautious: NONE → passed,MEDIUM → manual_review,HIGH → manual_review (全部不确定,交人工);")
private String riskStrategy;
@TableField("create_time")
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@TableField("create_by")
@Schema(description = "创建人ID")
private Long createBy;
@TableField("update_time")
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
@TableField("update_by")
@Schema(description = "修改人ID")
private Long updateBy;
@TableField("is_deleted")
@Schema(description = "逻辑删除标识(0-未删除 1-已删除)")
private Boolean deleted;
@TableField("text_service")
@Schema(description = "文本审核服务")
private String textService;
@TableField("image_service")
@Schema(description = "图片审核服务")
private String imageService;
@TableField("video_service")
@Schema(description = "视频审核服务")
private String videoService;
}

118
src/main/java/com/youlai/boot/admin/model/entity/MiniContentAuditTask.java

@ -0,0 +1,118 @@
package com.youlai.boot.admin.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
@Getter
@Setter
@ToString
@Accessors(chain = true)
@TableName("mini_content_audit_task")
@Schema(description = "审核任务表")
public class MiniContentAuditTask implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
@Schema(description = "审核任务表主键id")
private Long id;
@TableField("uuid")
@Schema(description = "UUID,前端交互")
private String uuid;
@TableField("content_audit_id")
@Schema(description = "内容审核汇总id")
private Long contentAuditId;
@TableField("content_type")
@Schema(description = "内容类型: text / image / video")
private String contentType;
@TableField("content_value")
@Schema(description = "待审核内容(文字或文件URL)")
private String contentValue;
@TableField("audit_type")
@Schema(description = "审核类型: machine机器 / manual手动 / mixed混合(来自mini_content_audit)")
private String auditType;
@TableField("status")
@Schema(description = "审核状态:reviewing审核中 / passed通过 / manual_review待人工审核 / rejected 不通过(机审按策略判定 / 人工驳回)")
private String status;
@TableField("risk_level")
@Schema(description = "机审风险等级: none无 / medium中 / high高")
private String riskLevel;
@TableField("label")
@Schema(description = "机审标签")
private String label;
@TableField("confidence")
@Schema(description = "机审信任度")
private Integer confidence;
@TableField("description")
@Schema(description = "机审描述")
private String description;
@TableField("request_id")
@Schema(description = "机审请求ID")
private String requestId;
@TableField("machine_result")
@Schema(description = "机审结果JSON")
private String machineResult;
@TableField("task_id")
@Schema(description = "机审异步视频任务id(轮询设置结果)")
private String taskId;
@TableField("operator")
@Schema(description = "人工审核操作人")
private Long operator;
@TableField("create_time")
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@TableField("create_timestamp")
@Schema(description = "创建时间毫秒级时间戳")
private Long createTimestamp;
@TableField("create_by")
@Schema(description = "创建人ID")
private Long createBy;
@TableField("update_time")
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
@TableField("update_timestamp")
@Schema(description = "更新时间毫秒级时间戳")
private Long updateTimestamp;
@TableField("update_by")
@Schema(description = "修改人ID")
private Long updateBy;
@TableField("is_deleted")
@Schema(description = "逻辑删除标识(0-未删除 1-已删除)")
private Boolean deleted;
@TableField("service_name")
@Schema(description = "使用的审核服务名")
private String serviceName;
}

20
src/main/java/com/youlai/boot/admin/model/form/AppealHandleForm.java

@ -0,0 +1,20 @@
package com.youlai.boot.admin.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "处理申诉表单")
@Getter
@Setter
public class AppealHandleForm {
@NotBlank(message = "处理结果不能为空")
@Schema(description = "处理结果: approved通过 / rejected驳回")
private String result;
@Schema(description = "处理备注")
private String handleRemark;
}

28
src/main/java/com/youlai/boot/admin/model/form/AuditConfigForm.java

@ -0,0 +1,28 @@
package com.youlai.boot.admin.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "审核配置表单")
@Getter
@Setter
public class AuditConfigForm {
@NotBlank(message = "业务模块编码不能为空")
@Schema(description = "业务模块: animal_note / adoption_diary / user_post / note_comment / diary_comment / post_comment / user_avatar / user_nickname / user_introduction")
private String moduleCode;
@NotNull(message = "是否开启审核不能为空")
@Schema(description = "是否开启审核, false-开启, true-关闭")
private Boolean auditEnable;
@Schema(description = "审核类型: machine / manual / mixed")
private String auditType;
@Schema(description = "机器审核风险策略: auto / normal / cautious")
private String riskStrategy;
}

20
src/main/java/com/youlai/boot/admin/model/form/ReportHandleForm.java

@ -0,0 +1,20 @@
package com.youlai.boot.admin.model.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
@Schema(description = "处理举报表单")
@Getter
@Setter
public class ReportHandleForm {
@NotBlank(message = "处理结果不能为空")
@Schema(description = "处理结果: processed已处理 / dismissed已驳回")
private String result;
@Schema(description = "处理备注")
private String handleRemark;
}

16
src/main/java/com/youlai/boot/admin/model/query/AppealQuery.java

@ -0,0 +1,16 @@
package com.youlai.boot.admin.model.query;
import com.youlai.boot.common.base.BaseQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Schema(description = "申诉列表分页查询")
public class AppealQuery extends BaseQuery {
@Schema(description = "处理状态: pending待处理 / approved通过 / rejected驳回")
private String status;
}

16
src/main/java/com/youlai/boot/admin/model/query/AuditConfigQuery.java

@ -0,0 +1,16 @@
package com.youlai.boot.admin.model.query;
import com.youlai.boot.common.base.BaseQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Schema(description = "审核配置分页查询")
public class AuditConfigQuery extends BaseQuery {
@Schema(description = "业务模块编码")
private String moduleCode;
}

19
src/main/java/com/youlai/boot/admin/model/query/ReportQuery.java

@ -0,0 +1,19 @@
package com.youlai.boot.admin.model.query;
import com.youlai.boot.common.base.BaseQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Schema(description = "举报列表分页查询")
public class ReportQuery extends BaseQuery {
@Schema(description = "举报目标类型")
private String targetType;
@Schema(description = "处理状态: pending待处理 / processed已处理 / dismissed已驳回")
private String status;
}

50
src/main/java/com/youlai/boot/admin/model/vo/AppealVO.java

@ -0,0 +1,50 @@
package com.youlai.boot.admin.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
@Schema(description = "申诉列表VO")
public class AppealVO {
@Schema(description = "申诉ID")
private Long id;
@Schema(description = "UUID")
private String uuid;
@Schema(description = "关联审核ID")
private Long auditId;
@Schema(description = "申诉人用户ID")
private Long userId;
@Schema(description = "申诉原因")
private String reason;
@Schema(description = "证据图片URL")
private String evidence;
@Schema(description = "处理状态: pending / approved / rejected")
private String status;
@Schema(description = "处理人ID")
private Long handlerUserId;
@Schema(description = "处理备注")
private String handleRemark;
@Schema(description = "处理时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date handleTime;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}

47
src/main/java/com/youlai/boot/admin/model/vo/AuditConfigVO.java

@ -0,0 +1,47 @@
package com.youlai.boot.admin.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
@Schema(description = "审核配置列表VO")
public class AuditConfigVO {
@Schema(description = "主键ID")
private Long id;
@Schema(description = "UUID")
private String uuid;
@Schema(description = "业务模块编码")
private String moduleCode;
@Schema(description = "是否开启审核, false-开启, true-关闭")
private Boolean auditEnable;
@Schema(description = "审核类型: machine / manual")
private String auditType;
@Schema(description = "机器审核风险策略: auto / normal / cautious")
private String riskStrategy;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@Schema(description = "创建人ID")
private Long createBy;
@Schema(description = "更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
@Schema(description = "修改人ID")
private Long updateBy;
}

59
src/main/java/com/youlai/boot/admin/model/vo/ReportVO.java

@ -0,0 +1,59 @@
package com.youlai.boot.admin.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
@Schema(description = "举报列表VO")
public class ReportVO {
@Schema(description = "举报ID")
private Long id;
@Schema(description = "UUID")
private String uuid;
@Schema(description = "举报目标类型")
private String targetType;
@Schema(description = "被举报内容ID")
private Long targetId;
@Schema(description = "举报人用户ID")
private Long reporterUserId;
@Schema(description = "举报原因类别")
private String reasonCategory;
@Schema(description = "证据图片URL")
private String evidence;
@Schema(description = "举报补充描述")
private String description;
@Schema(description = "处理状态: pending / processed / dismissed")
private String status;
@Schema(description = "关联审核ID")
private Long auditId;
@Schema(description = "处理人ID")
private Long handlerUserId;
@Schema(description = "处理备注")
private String handleRemark;
@Schema(description = "处理时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date handleTime;
@Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}

27
src/main/java/com/youlai/boot/admin/service/AuditExecutorService.java

@ -0,0 +1,27 @@
package com.youlai.boot.admin.service;
import com.youlai.boot.admin.model.dto.AuditContentDTO;
import java.util.Map;
public interface AuditExecutorService {
/**
* 执行内容审核供业务层调用
*
* @param moduleCode 业务模块: animal_note / adoption_diary / user_post / *_comment / user_avatar / user_nickname / user_introduction
* @param bizId 业务数据ID
* @param content 待审核内容 {text[], images[], video[]}
* @param triggerType 触发类型: create发布 / report举报
* @return {status: "passed"|"failed"|"manual_review", auditId: Long}配置关闭或无配置时返回null
*/
Map<String, Object> executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType);
/**
* 轮询所有待处理的异步审核结果图片+视频一次查询按类型分流处理
*
* @return 本次处理的任务数量
*/
int pollAsyncAuditResults();
}

23
src/main/java/com/youlai/boot/admin/service/BizAuditStatusHandler.java

@ -0,0 +1,23 @@
package com.youlai.boot.admin.service;
/**
* 业务审核状态处理器 审核汇总(MiniContentAudit)状态变更后同步更新对应业务实体的审核状态
*/
public interface BizAuditStatusHandler {
/**
* 审核通过时回调
*
* @param moduleCode 业务模块编码
* @param bizId 业务数据ID
*/
void onAuditPassed(String moduleCode, Long bizId);
/**
* 审核不通过时回调
*
* @param moduleCode 业务模块编码
* @param bizId 业务数据ID
*/
void onAuditRejected(String moduleCode, Long bizId);
}

16
src/main/java/com/youlai/boot/admin/service/ContentAuditAppealService.java

@ -0,0 +1,16 @@
package com.youlai.boot.admin.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.admin.model.form.AppealHandleForm;
import com.youlai.boot.admin.model.query.AppealQuery;
import com.youlai.boot.admin.model.vo.AppealVO;
public interface ContentAuditAppealService {
/** 分页查询申诉列表 */
IPage<AppealVO> pageAppeal(AppealQuery query);
/** 处理申诉(通过/驳回) */
void handleAppeal(Long id, AppealHandleForm form);
}

21
src/main/java/com/youlai/boot/admin/service/ContentAuditConfigService.java

@ -0,0 +1,21 @@
package com.youlai.boot.admin.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.admin.model.entity.MiniContentAuditConfig;
import com.youlai.boot.admin.model.form.AuditConfigForm;
import com.youlai.boot.admin.model.query.AuditConfigQuery;
import com.youlai.boot.admin.model.vo.AuditConfigVO;
public interface ContentAuditConfigService extends IService<MiniContentAuditConfig> {
IPage<AuditConfigVO> pageConfig(AuditConfigQuery query);
AuditConfigVO getConfigByUuid(String uuid);
AuditConfigVO addConfig(AuditConfigForm form);
AuditConfigVO updateConfig(String uuid, AuditConfigForm form);
void deleteConfig(String uuid);
}

14
src/main/java/com/youlai/boot/admin/service/ContentAuditService.java

@ -0,0 +1,14 @@
package com.youlai.boot.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.admin.model.entity.MiniContentAudit;
public interface ContentAuditService extends IService<MiniContentAudit> {
MiniContentAudit createAudit(String moduleCode, Long bizId, String auditType, String triggerType);
void updateAuditStatus(Long auditId, String status);
MiniContentAudit getByModuleAndBizId(String moduleCode, Long bizId);
}

23
src/main/java/com/youlai/boot/admin/service/ContentAuditTaskService.java

@ -0,0 +1,23 @@
package com.youlai.boot.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.admin.model.entity.MiniContentAuditTask;
import java.util.List;
public interface ContentAuditTaskService extends IService<MiniContentAuditTask> {
/** 批量创建审核任务(按内容类型拆分:text/image/video) */
void batchCreateTasks(Long auditId, String auditType, List<String> texts, List<String> images, List<String> videos);
/** 更新单个任务的机审结果 */
void updateTaskMachineResult(Long taskId, String machineResult, String riskLevel, String status,
String label, Integer confidence, String description, String requestId);
/** 查询某个审核汇总下的所有任务明细 */
List<MiniContentAuditTask> listTasksByAuditId(Long auditId);
/** 查询待轮询的异步审核任务(contentType in (image, video), status=reviewing, taskId不为空) */
List<MiniContentAuditTask> getPendingAsyncTasks();
}

10
src/main/java/com/youlai/boot/admin/service/ContentLookupService.java

@ -0,0 +1,10 @@
package com.youlai.boot.admin.service;
import com.youlai.boot.admin.model.dto.AuditContentDTO;
public interface ContentLookupService {
/** 根据模块编码和业务ID查询待审核的原始内容 */
AuditContentDTO lookupContent(String moduleCode, Long bizId);
}

8
src/main/java/com/youlai/boot/admin/service/OssCallbackService.java

@ -0,0 +1,8 @@
package com.youlai.boot.admin.service;
public interface OssCallbackService {
/** 处理OSS图片审核增量回调 */
void handleImageCallback(String body);
}

16
src/main/java/com/youlai/boot/admin/service/ReportManageService.java

@ -0,0 +1,16 @@
package com.youlai.boot.admin.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.admin.model.form.ReportHandleForm;
import com.youlai.boot.admin.model.query.ReportQuery;
import com.youlai.boot.admin.model.vo.ReportVO;
public interface ReportManageService {
/** 分页查询举报列表 */
IPage<ReportVO> pageReport(ReportQuery query);
/** 处理举报(受理/驳回) */
void handleReport(Long id, ReportHandleForm form);
}

709
src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java

@ -0,0 +1,709 @@
package com.youlai.boot.admin.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.green20220302.models.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.youlai.boot.admin.constant.AuditConstants;
import com.youlai.boot.admin.model.dto.AuditContentDTO;
import com.youlai.boot.admin.model.entity.MiniContentAudit;
import com.youlai.boot.admin.model.entity.MiniContentAuditConfig;
import com.youlai.boot.admin.model.entity.MiniContentAuditTask;
import com.youlai.boot.admin.service.*;
import com.youlai.boot.common.util.AliyunContentAuditUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
/**
* 内容审核核心执行器
* <p>
* 职责编排审核全流程查配置 创建汇总 拆解内容创建任务 调用阿里云机审 按策略判定结果 汇总最终状态
* 供业务层创建/修改内容时注入调用
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class AuditExecutorServiceImpl implements AuditExecutorService {
private final ContentAuditConfigService contentAuditConfigService;
private final ContentAuditService contentAuditService;
private final ContentAuditTaskService contentAuditTaskService;
private final AliyunContentAuditUtil aliyunContentAuditUtil;
/**
* 执行内容审核供业务层在内容创建/修改后调用
*
* @param moduleCode 业务模块编码
* @param bizId 业务数据ID
* @param content 待审核内容 {texts, images, videos}
* @return {status, auditId}配置关闭或无配置时返回 null
*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public Map<String, Object> executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType) {
// 1) 查询审核配置
MiniContentAuditConfig config = findAuditConfig(moduleCode);
if (config == null || isAuditDisabled(config)) {
return null;
}
String auditType = config.getAuditType();
// 2) 创建审核汇总
MiniContentAudit audit = contentAuditService.createAudit(moduleCode, bizId, auditType, triggerType);
Long auditId = audit.getId();
// 3) 拆解内容,批量创建审核任务
contentAuditTaskService.batchCreateTasks(auditId, auditType,
content.getTexts(), content.getImages(), content.getVideos());
// 4) 按审核类型执行
if (AuditConstants.AUDIT_TYPE_MANUAL.equals(auditType)) {
// 人工审核
return executeManualAudit(auditId);
}
// machine:调 AI 并按策略裁决
String strictness = config.getRiskStrategy() != null ? config.getRiskStrategy() : AuditConstants.STRATEGY_NORMAL;
List<MiniContentAuditTask> tasks = contentAuditTaskService.listTasksByAuditId(auditId);
executeBatchAuditByType(tasks, config, strictness);
List<MiniContentAuditTask> updatedTasks = contentAuditTaskService.listTasksByAuditId(auditId);
return aggregateTaskResultsAndUpdateAudit(auditId, updatedTasks);
}
/**
* manual 人工审核不调 AI所有任务直接标记为 manual_reviewaudit 状态设为 manual_review
*/
private Map<String, Object> executeManualAudit(Long auditId) {
contentAuditTaskService.lambdaUpdate()
.eq(MiniContentAuditTask::getContentAuditId, auditId)
.set(MiniContentAuditTask::getStatus, AuditConstants.STATUS_MANUAL_REVIEW)
.update();
contentAuditService.updateAuditStatus(auditId, AuditConstants.STATUS_MANUAL_REVIEW);
Map<String, Object> result = new HashMap<>();
result.put("status", AuditConstants.STATUS_MANUAL_REVIEW);
result.put("auditId", auditId);
return result;
}
/**
* 按类型分组批量执行机审
*/
private void executeBatchAuditByType(List<MiniContentAuditTask> tasks, MiniContentAuditConfig config, String strictness) {
Map<String, List<MiniContentAuditTask>> grouped = tasks.stream()
.collect(Collectors.groupingBy(MiniContentAuditTask::getContentType));
String textService = config.getTextService() != null ? config.getTextService() : "ugc_moderation_byllm_pro";
String imageService = config.getImageService() != null ? config.getImageService() : "baselineCheck";
String videoService = config.getVideoService() != null ? config.getVideoService() : "videoDetection";
List<MiniContentAuditTask> textTasks = grouped.getOrDefault("text", List.of());
if (!textTasks.isEmpty()) {
for (MiniContentAuditTask task : textTasks) {
try {
TextModerationPlusResponse r = aliyunContentAuditUtil.textModerationPlus(task.getContentValue(), textService);
processSingleTextResponse(task, r, strictness);
contentAuditTaskService.lambdaUpdate()
.eq(MiniContentAuditTask::getId, task.getId())
.set(MiniContentAuditTask::getServiceName, textService)
.update();
} catch (Exception e) {
log.error("文本审核失败, taskId={}", task.getId(), e);
contentAuditTaskService.updateTaskMachineResult(
task.getId(), null, null, AuditConstants.STATUS_MANUAL_REVIEW,
null, null, null, null);
}
}
}
List<MiniContentAuditTask> imageTasks = grouped.getOrDefault("image", List.of());
if (!imageTasks.isEmpty()) {
for (MiniContentAuditTask task : imageTasks) {
try {
handleImageAsyncAudit(task, task.getContentValue(), imageService);
} catch (Exception e) {
log.error("图片异步审核提交失败, taskId={}", task.getId(), e);
contentAuditTaskService.updateTaskMachineResult(
task.getId(), null, null, AuditConstants.STATUS_MANUAL_REVIEW,
null, null, null, null);
}
}
}
List<MiniContentAuditTask> videoTasks = grouped.getOrDefault("video", List.of());
for (MiniContentAuditTask task : videoTasks) {
handleVideoAudit(task, task.getContentValue(), videoService);
}
}
/**
* 单文本审核结果处理machine 模式按策略裁决
* <p>
* data.result[] 可能包含多条取置信度最高的非 nonLabel 标签
*/
private void processSingleTextResponse(MiniContentAuditTask task, TextModerationPlusResponse response, String strictness) {
String machineResultJson = JSON.toJSONString(response);
String requestId = extractRequestIdFromResponse(response);
JSONObject dataObj = extractDataObjectFromResponse(response);
if (dataObj == null) {
log.info("文本审核返回data为空, taskId={}, requestId={}", task.getId(), requestId);
contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW,
null, null, null, requestId);
return;
}
String riskLevel = dataObj.getString("riskLevel");
JSONArray resultArray = dataObj.getJSONArray("result");
JSONObject best = pickBestFromResultArray(resultArray);
String label = null;
Integer confidence = null;
String description = null;
if (best != null) {
label = best.getString("label");
confidence = best.getInteger("confidence");
description = best.getString("description");
}
log.info("文本审核结果: taskId={}, riskLevel={}, label={}, confidence={}, description={}",
task.getId(), riskLevel, label, confidence, description);
if (riskLevel == null) {
contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW,
label, confidence, description, requestId);
} else {
String status = applyStrategy(riskLevel, strictness);
contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, riskLevel, status,
label, confidence, description, requestId);
}
}
/**
* 单张图片审核结果处理machine 模式按策略裁决
* <p>
* data.result[] 可能包含多条取置信度最高的非 nonLabel 标签
*/
private void processSingleImageResponse(MiniContentAuditTask task, ImageModerationResponse response, String strictness) {
String machineResultJson = JSON.toJSONString(response);
String requestId = extractRequestIdFromResponse(response);
JSONObject dataObj = extractDataObjectFromResponse(response);
if (dataObj == null) {
log.info("图片审核返回data为空, taskId={}", task.getId());
contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW,
null, null, null, requestId);
return;
}
String riskLevel = dataObj.getString("riskLevel");
JSONArray resultArray = dataObj.getJSONArray("result");
JSONObject best = pickBestFromResultArray(resultArray);
String label = null;
Integer confidence = null;
String description = null;
if (best != null) {
label = best.getString("label");
confidence = best.getInteger("confidence");
description = best.getString("description");
}
log.info("图片审核结果: taskId={}, riskLevel={}, label={}, confidence={}, description={}",
task.getId(), riskLevel, label, confidence, description);
if (riskLevel == null) {
contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, null, AuditConstants.STATUS_MANUAL_REVIEW,
label, confidence, description, requestId);
} else {
String status = applyStrategy(riskLevel, strictness);
contentAuditTaskService.updateTaskMachineResult(
task.getId(), machineResultJson, riskLevel, status,
label, confidence, description, requestId);
}
}
/**
* 图片审核异步提交
*/
private void handleImageAsyncAudit(MiniContentAuditTask task, String imageUrl, String serviceName) {
ImageAsyncModerationResponse response = aliyunContentAuditUtil.imageAsyncModeration(imageUrl, serviceName);
if (response == null || response.getBody() == null) {
log.warn("图片异步审核请求返回null, taskId={}", task.getId());
return;
}
if (response.getBody().getCode() == null || response.getBody().getCode() != 200) {
log.warn("图片异步审核提交失败, taskId={}, code={}", task.getId(), response.getBody().getCode());
return;
}
if (response.getBody().getData() != null && response.getBody().getData().getReqId() != null) {
contentAuditTaskService.lambdaUpdate()
.eq(MiniContentAuditTask::getId, task.getId())
.set(MiniContentAuditTask::getTaskId, response.getBody().getData().getReqId())
.set(MiniContentAuditTask::getMachineResult, JSON.toJSONString(response))
.set(MiniContentAuditTask::getServiceName, serviceName)
.set(MiniContentAuditTask::getUpdateTime, new Date())
.set(MiniContentAuditTask::getUpdateTimestamp, System.currentTimeMillis())
.update();
log.info("图片异步审核已提交, taskId={}, reqId={}, service={}", task.getId(), response.getBody().getData().getReqId(), serviceName);
}
}
/**
* 视频审核异步
*/
private void handleVideoAudit(MiniContentAuditTask task, String videoUrl, String serviceName) {
VideoModerationResponse response = aliyunContentAuditUtil.videoModeration(videoUrl, serviceName);
if (response == null) {
log.warn("视频审核请求返回null, taskId={}", task.getId());
return;
}
String asyncTaskId = extractVideoTaskIdFromResponse(response);
if (asyncTaskId != null) {
contentAuditTaskService.lambdaUpdate()
.eq(MiniContentAuditTask::getId, task.getId())
.set(MiniContentAuditTask::getTaskId, asyncTaskId)
.set(MiniContentAuditTask::getServiceName, serviceName)
.update();
log.info("视频异步审核已提交, taskId={}, aliyunTaskId={}, service={}", task.getId(), asyncTaskId, serviceName);
}
}
// ================================================================
// 从阿里云响应中提取数据
// ================================================================
private String extractVideoTaskIdFromResponse(VideoModerationResponse response) {
if (response.getBody() == null) return null;
if (response.getBody().getData() == null) return null;
return response.getBody().getData().getTaskId();
}
/** 反射提取 body.requestId */
private String extractRequestIdFromResponse(Object response) {
try {
Method getBody = response.getClass().getMethod("getBody");
Object body = getBody.invoke(response);
if (body == null) return null;
Method getRequestId = body.getClass().getMethod("getRequestId");
Object requestId = getRequestId.invoke(body);
return requestId != null ? requestId.toString() : null;
} catch (Exception e) {
return null;
}
}
/** 反射提取 body.data,转为 JSONObject */
private JSONObject extractDataObjectFromResponse(Object response) {
try {
Method getBody = response.getClass().getMethod("getBody");
Object body = getBody.invoke(response);
if (body == null) return null;
Method getData = body.getClass().getMethod("getData");
Object data = getData.invoke(body);
if (data == null) return null;
return JSON.parseObject(JSON.toJSONString(data));
} catch (Exception e) {
return null;
}
}
// ================================================================
// 策略映射
// ================================================================
/**
* 根据 riskLevel + strictness返回 task status
* <pre>
* auto: NONEpassed, MEDIUMrejected, HIGHrejected
* normal: NONEpassed, MEDIUMmanual_review, HIGHrejected
* cautious: NONEpassed, MEDIUMmanual_review, HIGHmanual_review
* </pre>
*/
private String applyStrategy(String riskLevel, String strictness) {
if (AuditConstants.RISK_NONE.equals(riskLevel)) {
return AuditConstants.STATUS_PASSED;
}
if (AuditConstants.STRATEGY_CAUTIOUS.equals(strictness)) {
return AuditConstants.STATUS_MANUAL_REVIEW;
}
if (AuditConstants.STRATEGY_AUTO.equals(strictness)) {
return AuditConstants.STATUS_REJECTED;
}
// normal(默认):MEDIUM→manual_review, HIGH→rejected
if (AuditConstants.RISK_MEDIUM.equals(riskLevel)) {
return AuditConstants.STATUS_MANUAL_REVIEW;
}
return AuditConstants.STATUS_REJECTED;
}
// ================================================================
// 结果汇总
// ================================================================
/**
* 汇总所有任务状态更新 audit
* <p>
* 汇总规则
* - 任一 task.status = rejected audit.status = rejected
* - task.status = manual_review audit.status = manual_review
* - 全部 task.status = passed audit.status = passed
* - 视频任务未完成status = reviewing 保持 reviewing
*/
private Map<String, Object> aggregateTaskResultsAndUpdateAudit(Long auditId, List<MiniContentAuditTask> tasks) {
boolean hasRejected = false;
boolean hasManualReview = false;
boolean allPassed = true;
for (MiniContentAuditTask task : tasks) {
String taskStatus = task.getStatus();
if (AuditConstants.STATUS_REJECTED.equals(taskStatus)) {
hasRejected = true;
allPassed = false;
break;
}
if (AuditConstants.STATUS_MANUAL_REVIEW.equals(taskStatus)) {
hasManualReview = true;
allPassed = false;
} else if (!AuditConstants.STATUS_PASSED.equals(taskStatus)) {
allPassed = false;
}
}
// 视频任务未完成时不能算通过
for (MiniContentAuditTask task : tasks) {
if ("video".equals(task.getContentType()) && AuditConstants.STATUS_REVIEWING.equals(task.getStatus())) {
allPassed = false;
break;
}
}
String auditStatus;
if (hasRejected) {
auditStatus = AuditConstants.STATUS_REJECTED;
} else if (hasManualReview) {
auditStatus = AuditConstants.STATUS_MANUAL_REVIEW;
} else if (allPassed && !tasks.isEmpty()) {
auditStatus = AuditConstants.STATUS_PASSED;
} else {
auditStatus = AuditConstants.STATUS_REVIEWING;
}
// 更新业务状态
if (!AuditConstants.STATUS_REVIEWING.equals(auditStatus)) {
contentAuditService.updateAuditStatus(auditId, auditStatus);
}
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("status", auditStatus);
resultMap.put("auditId", auditId);
return resultMap;
}
// ================================================================
// 异步审核结果轮询(图片 + 视频合并查询)
// ================================================================
@Override
public int pollAsyncAuditResults() {
List<MiniContentAuditTask> pendingTasks = contentAuditTaskService.getPendingAsyncTasks();
if (pendingTasks.isEmpty()) {
return 0;
}
log.info("开始轮询异步审核任务, 总数={}", pendingTasks.size());
Set<Long> affectedAuditIds = new HashSet<>();
int processedCount = 0;
for (MiniContentAuditTask task : pendingTasks) {
try {
boolean done = "video".equals(task.getContentType())
? processSingleVideoTask(task)
: processSingleImageAsyncTask(task);
if (done) {
affectedAuditIds.add(task.getContentAuditId());
processedCount++;
}
} catch (Exception e) {
log.error("轮询异步任务异常, taskId={}, contentType={}", task.getId(), task.getContentType(), e);
}
}
for (Long auditId : affectedAuditIds) {
try {
MiniContentAudit audit = contentAuditService.getById(auditId);
if (audit != null && AuditConstants.AUDIT_TYPE_MACHINE.equals(audit.getAuditType())) {
List<MiniContentAuditTask> tasks = contentAuditTaskService.listTasksByAuditId(auditId);
aggregateTaskResultsAndUpdateAudit(auditId, tasks);
}
} catch (Exception e) {
log.error("重新汇总审核失败, auditId={}", auditId, e);
}
}
log.info("异步审核轮询完成, 已处理={}, 受影响审核={}", processedCount, affectedAuditIds.size());
return processedCount;
}
private boolean processSingleVideoTask(MiniContentAuditTask task) {
String asyncTaskId = task.getTaskId();
VideoModerationResultResponse response = aliyunContentAuditUtil.videoModerationResult(asyncTaskId);
if (response == null || response.getBody() == null) {
log.info("视频审核结果查询返回null, taskId={}", task.getId());
return false;
}
Integer code = response.getBody().getCode();
if (code == null || code != 200) {
log.info("视频审核结果查询失败, taskId={}, code={}", task.getId(), code);
return false;
}
Object data = response.getBody().getData();
if (data == null) {
log.info("视频审核未完成(无data), taskId={}", task.getId());
return false;
}
String dataJson = JSON.toJSONString(data);
if (!isVideoAuditCompleted(dataJson)) {
log.debug("视频审核仍在处理中, taskId={}", task.getId());
return false;
}
String riskLevel = extractVideoRiskLevel(dataJson);
if (riskLevel == null) {
log.warn("视频审核结果中未找到riskLevel, taskId={}", task.getId());
return false;
}
// FrameSummarys → AudioSummarys 优先级提取最佳标签;全为 nonLabel 则存空
String label = null;
Integer confidence = null;
String description = null;
try {
JSONObject dataJsonObj = JSON.parseObject(dataJson);
JSONObject frameResult = dataJsonObj.getJSONObject("frameResult");
JSONObject audioResult = dataJsonObj.getJSONObject("audioResult");
JSONArray frameSummarys = frameResult != null ? frameResult.getJSONArray("frameSummarys") : null;
JSONObject best = pickBestFromFrameSummarys(frameSummarys);
if (best != null) {
log.info("视频标签来自frameSummarys, taskId={}, label={}, labelSum={}",
task.getId(), best.getString("label"), best.getInteger("labelSum"));
} else if (audioResult != null) {
JSONArray audioSummarys = audioResult.getJSONArray("audioSummarys");
best = pickBestFromFrameSummarys(audioSummarys);
if (best != null) {
log.info("视频标签来自audioSummarys, taskId={}, label={}, labelSum={}",
task.getId(), best.getString("label"), best.getInteger("labelSum"));
}
}
if (best == null) {
log.info("视频FrameSummarys和AudioSummarys均无有效违规标签, taskId={}", task.getId());
}
if (best != null) {
label = best.getString("label");
description = best.getString("description");
}
} catch (Exception e) {
log.error("提取视频标签失败, taskId={}", task.getId(), e);
}
MiniContentAudit audit = contentAuditService.getById(task.getContentAuditId());
if (audit == null) {
log.warn("未找到关联审核记录, taskId={}", task.getId());
return false;
}
// 查找配置获取策略
String strictness = AuditConstants.STRATEGY_NORMAL;
MiniContentAuditConfig config = findAuditConfig(audit.getModuleCode());
if (config != null && config.getRiskStrategy() != null) {
strictness = config.getRiskStrategy();
}
String status = applyStrategy(riskLevel, strictness);
contentAuditTaskService.updateTaskMachineResult(
task.getId(), dataJson, riskLevel, status,
label, confidence, description, null);
log.info("视频任务审核完成, taskId={}, riskLevel={}, label={}, strictness={}, status={}",
task.getId(), riskLevel, label, strictness, status);
return true;
}
private boolean isVideoAuditCompleted(String dataJson) {
try {
JSONObject json = JSON.parseObject(dataJson);
return json.containsKey("riskLevel");
} catch (Exception e) {
log.error("解析视频审核完成状态失败", e);
return false;
}
}
private String extractVideoRiskLevel(String dataJson) {
try {
JSONObject json = JSON.parseObject(dataJson);
return json.getString("riskLevel");
} catch (Exception e) {
log.error("解析视频审核riskLevel失败", e);
return null;
}
}
private boolean processSingleImageAsyncTask(MiniContentAuditTask task) {
String reqId = task.getTaskId();
DescribeImageModerationResultResponse response = aliyunContentAuditUtil.describeImageModerationResult(reqId);
if (response == null || response.getBody() == null) {
log.info("图片审核结果查询返回null, taskId={}", task.getId());
return false;
}
Integer code = response.getBody().getCode();
if (code == null || code != 200) {
log.info("图片审核结果查询失败, taskId={}, code={}", task.getId(), code);
return false;
}
Object data = response.getBody().getData();
if (data == null) {
log.info("图片异步审核未完成(无data), taskId={}", task.getId());
return false;
}
String dataJson = JSON.toJSONString(data);
JSONObject dataObj = JSON.parseObject(dataJson);
String riskLevel = dataObj.getString("riskLevel");
if (riskLevel == null) {
log.info("图片异步审核仍在处理中(无riskLevel), taskId={}", task.getId());
return false;
}
JSONArray resultArray = dataObj.getJSONArray("result");
JSONObject best = pickBestFromResultArray(resultArray);
String label = null;
Integer confidence = null;
String description = null;
if (best != null) {
label = best.getString("label");
confidence = best.getInteger("confidence");
description = best.getString("description");
}
MiniContentAudit audit = contentAuditService.getById(task.getContentAuditId());
if (audit == null) {
log.warn("未找到关联审核记录, taskId={}", task.getId());
return false;
}
String strictness = AuditConstants.STRATEGY_NORMAL;
MiniContentAuditConfig config = findAuditConfig(audit.getModuleCode());
if (config != null && config.getRiskStrategy() != null) {
strictness = config.getRiskStrategy();
}
String status = applyStrategy(riskLevel, strictness);
contentAuditTaskService.updateTaskMachineResult(
task.getId(), dataJson, riskLevel, status,
label, confidence, description, null);
log.info("图片异步任务审核完成, taskId={}, reqId={}, riskLevel={}, label={}, strictness={}, status={}",
task.getId(), reqId, riskLevel, label, strictness, status);
return true;
}
/**
* 查找审核配置
*/
private MiniContentAuditConfig findAuditConfig(String moduleCode) {
LambdaQueryWrapper<MiniContentAuditConfig> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(MiniContentAuditConfig::getModuleCode, moduleCode);
queryWrapper.eq(MiniContentAuditConfig::getDeleted, false);
queryWrapper.last("LIMIT 1");
return contentAuditConfigService.getOne(queryWrapper);
}
/**
* 判断审核是否已关闭
*/
private boolean isAuditDisabled(MiniContentAuditConfig config) {
Boolean auditEnable = config.getAuditEnable();
return Boolean.TRUE.equals(auditEnable);
}
// ================================================================
// 公共提取工具方法
// ================================================================
/**
* result 数组中取置信度最高的非 nonLabel 标签
* 全部为 nonLabel 时兜底取第一条
*
* @param resultArray data.result[]元素含 label / confidence / description
* @return 最佳标签项 null
*/
static JSONObject pickBestFromResultArray(JSONArray resultArray) {
if (resultArray == null || resultArray.isEmpty()) return null;
JSONObject best = null;
double bestConf = -1;
JSONObject fallback = null;
for (int i = 0; i < resultArray.size(); i++) {
JSONObject item = resultArray.getJSONObject(i);
String label = item.getString("label");
if (label == null || label.isEmpty()) continue;
if (fallback == null) fallback = item;
if ("nonLabel".equals(label)) continue;
double conf = item.getDoubleValue("confidence");
if (conf > bestConf) {
bestConf = conf;
best = item;
}
}
if (best != null) return best;
return fallback;
}
/**
* 从视频 FrameSummarys / AudioSummarys 中取 LabelSum 最大的非 nonLabel 标签
* 全部为 nonLabel 或空 Label 时返回 null
*
* @param summarys FrameSummarys[] AudioSummarys[]元素含 Label / Description / LabelSum
* @return 最佳标签项 null
*/
static JSONObject pickBestFromFrameSummarys(JSONArray summarys) {
if (summarys == null || summarys.isEmpty()) return null;
JSONObject best = null;
int bestSum = -1;
JSONObject fallback = null;
for (int i = 0; i < summarys.size(); i++) {
JSONObject item = summarys.getJSONObject(i);
String label = item.getString("label");
if (label == null) continue;
if (fallback == null) fallback = item;
if ("nonLabel".equals(label) || label.isEmpty()) continue;
int sum = item.getIntValue("labelSum");
if (sum > bestSum) {
bestSum = sum;
best = item;
}
}
return best != null ? best : fallback;
}
}

172
src/main/java/com/youlai/boot/admin/service/impl/BizAuditStatusHandlerImpl.java

@ -0,0 +1,172 @@
package com.youlai.boot.admin.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.youlai.boot.admin.constant.AuditConstants;
import com.youlai.boot.admin.service.BizAuditStatusHandler;
import com.youlai.boot.mini.mapper.*;
import com.youlai.boot.mini.model.entity.*;
import com.youlai.boot.system.mapper.UserMapper;
import com.youlai.boot.system.model.entity.SysUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
/**
* 业务审核状态处理器实现 审核完成后同步更新业务实体的审核状态
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class BizAuditStatusHandlerImpl implements BizAuditStatusHandler {
private final MiniUserPostMapper miniUserPostMapper;
private final MiniStrayAnimalMapper miniStrayAnimalMapper;
private final MiniAdoptionDiaryMapper miniAdoptionDiaryMapper;
private final MiniStrayAnimalNoteCommentMapper strayAnimalNoteCommentMapper;
private final MiniAdoptionDiaryCommentMapper adoptionDiaryCommentMapper;
private final MiniUserPostCommentMapper userPostCommentMapper;
private final UserMapper userMapper;
@Value("${oss.aliyun.endpoint}")
private String endpoint;
@Value("${oss.aliyun.bucket-name}")
private String bucketName;
@Override
@Transactional(rollbackFor = Exception.class)
public void onAuditPassed(String moduleCode, Long bizId) {
log.info("审核通过, moduleCode={}, bizId={}", moduleCode, bizId);
switch (moduleCode) {
case "user_post" -> updateUserPostAuditStatus(bizId, AuditConstants.BIZ_AUDIT_STATUS_PASSED);
case "animal_note" -> updateStrayAnimalAuditStatus(bizId, AuditConstants.BIZ_AUDIT_STATUS_PASSED);
case "adoption_diary" -> updateAdoptionDiaryAuditStatus(bizId, AuditConstants.BIZ_AUDIT_STATUS_PASSED);
// 评论 / 头像 / 昵称 / 通过时无需额外操作
default -> log.debug("模块 {} 审核通过无需同步业务状态", moduleCode);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void onAuditRejected(String moduleCode, Long bizId) {
log.info("审核不通过, moduleCode={}, bizId={}", moduleCode, bizId);
switch (moduleCode) {
case "user_post" -> updateUserPostAuditStatus(bizId, AuditConstants.BIZ_AUDIT_STATUS_REJECTED);
case "animal_note" -> updateStrayAnimalAuditStatus(bizId, AuditConstants.BIZ_AUDIT_STATUS_REJECTED);
case "adoption_diary" -> updateAdoptionDiaryAuditStatus(bizId, AuditConstants.BIZ_AUDIT_STATUS_REJECTED);
case "note_comment" -> deleteNoteComment(bizId);
case "diary_comment" -> deleteDiaryComment(bizId);
case "post_comment" -> deletePostComment(bizId);
case "user_avatar" -> resetUserAvatarToDefault(bizId);
default -> log.debug("模块 {} 审核不通过无需同步业务状态", moduleCode);
}
}
private void updateUserPostAuditStatus(Long postId, Integer auditStatus) {
MiniUserPost entity = new MiniUserPost();
entity.setId(postId);
entity.setAuditStatus(auditStatus);
entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis());
miniUserPostMapper.updateById(entity);
log.info("用户作品审核状态已更新, postId={}, auditStatus={}", postId, auditStatus);
}
private void updateStrayAnimalAuditStatus(Long animalId, Integer auditStatus) {
MiniStrayAnimal entity = new MiniStrayAnimal();
entity.setId(animalId);
entity.setAuditStatus(auditStatus);
entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis());
miniStrayAnimalMapper.updateById(entity);
log.info("流浪动物审核状态已更新, animalId={}, auditStatus={}", animalId, auditStatus);
}
private void updateAdoptionDiaryAuditStatus(Long diaryId, Integer auditStatus) {
MiniAdoptionDiary entity = new MiniAdoptionDiary();
entity.setId(diaryId);
entity.setAuditStatus(auditStatus);
entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis());
miniAdoptionDiaryMapper.updateById(entity);
log.info("领养日记审核状态已更新, diaryId={}, auditStatus={}", diaryId, auditStatus);
}
private void deleteNoteComment(Long commentId) {
MiniStrayAnimalNoteComment comment = strayAnimalNoteCommentMapper.selectById(commentId);
Long noteId = comment != null ? comment.getNoteId() : null;
MiniStrayAnimalNoteComment entity = new MiniStrayAnimalNoteComment();
entity.setId(commentId);
entity.setDeleted(true);
entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis());
strayAnimalNoteCommentMapper.updateById(entity);
log.info("流浪动物笔记评论审核不通过已删除, commentId={}", commentId);
if (noteId != null) {
try {
strayAnimalNoteCommentMapper.decrementCommentCount(noteId);
} catch (Exception e) {
log.error("扣减笔记评论数失败, noteId={}, commentId={}", noteId, commentId, e);
}
}
}
private void deleteDiaryComment(Long commentId) {
MiniAdoptionDiaryComment comment = adoptionDiaryCommentMapper.selectById(commentId);
Long diaryId = comment != null ? comment.getDiaryId() : null;
MiniAdoptionDiaryComment entity = new MiniAdoptionDiaryComment();
entity.setId(commentId);
entity.setDeleted(true);
entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis());
adoptionDiaryCommentMapper.updateById(entity);
log.info("领养日记评论审核不通过已删除, commentId={}", commentId);
if (diaryId != null) {
try {
adoptionDiaryCommentMapper.decrementCommentCount(diaryId);
} catch (Exception e) {
log.error("扣减日记评论数失败, diaryId={}, commentId={}", diaryId, commentId, e);
}
}
}
private void deletePostComment(Long commentId) {
// 先查出postId,软删后查不到
MiniUserPostComment comment = userPostCommentMapper.selectById(commentId);
Long postId = comment != null ? comment.getPostId() : null;
MiniUserPostComment entity = new MiniUserPostComment();
entity.setId(commentId);
entity.setDeleted(true);
entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis());
userPostCommentMapper.updateById(entity);
log.info("用户作品评论审核不通过已删除, commentId={}", commentId);
if (postId != null) {
try {
userPostCommentMapper.decrementCommentCount(postId);
} catch (Exception e) {
log.error("扣减作品评论数失败, postId={}, commentId={}", postId, commentId, e);
}
}
}
private void resetUserAvatarToDefault(Long userId) {
String defaultAvatar = "https://" + bucketName + "." + endpoint + "/mini_home_page_default_picture.png";
userMapper.update(null,
new LambdaUpdateWrapper<SysUser>()
.eq(SysUser::getId, userId)
.set(SysUser::getAvatar, defaultAvatar)
);
log.info("用户头像审核不通过已重置为默认头像, userId={}", userId);
}
}

96
src/main/java/com/youlai/boot/admin/service/impl/ContentAuditAppealServiceImpl.java

@ -0,0 +1,96 @@
package com.youlai.boot.admin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.admin.constant.AuditConstants;
import com.youlai.boot.admin.model.entity.MiniContentAudit;
import com.youlai.boot.admin.model.form.AppealHandleForm;
import com.youlai.boot.admin.model.query.AppealQuery;
import com.youlai.boot.admin.model.vo.AppealVO;
import com.youlai.boot.admin.service.ContentAuditAppealService;
import com.youlai.boot.admin.service.ContentAuditService;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.mapper.MiniContentAuditAppealMapper;
import com.youlai.boot.mini.model.entity.MiniContentAuditAppeal;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
@Service
@RequiredArgsConstructor
@Slf4j
public class ContentAuditAppealServiceImpl implements ContentAuditAppealService {
private final MiniContentAuditAppealMapper appealMapper;
private final ContentAuditService contentAuditService;
@Override
public IPage<AppealVO> pageAppeal(AppealQuery query) {
LambdaQueryWrapper<MiniContentAuditAppeal> wrapper = new LambdaQueryWrapper<>();
if (query.getStatus() != null && !query.getStatus().isBlank()) {
wrapper.eq(MiniContentAuditAppeal::getStatus, query.getStatus());
}
wrapper.orderByDesc(MiniContentAuditAppeal::getCreateTime);
Page<MiniContentAuditAppeal> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<MiniContentAuditAppeal> result = appealMapper.selectPage(page, wrapper);
return result.convert(entity -> AppealVO.builder()
.id(entity.getId())
.uuid(entity.getUuid())
.auditId(entity.getAuditId())
.userId(entity.getUserId())
.reason(entity.getReason())
.evidence(entity.getEvidence())
.status(entity.getStatus())
.handlerUserId(entity.getHandlerUserId())
.handleRemark(entity.getHandleRemark())
.handleTime(entity.getHandleTime())
.createTime(entity.getCreateTime())
.build());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void handleAppeal(Long id, AppealHandleForm form) {
MiniContentAuditAppeal appeal = appealMapper.selectById(id);
if (appeal == null) {
throw new MsgException("申诉记录不存在");
}
if (!"pending".equals(appeal.getStatus())) {
throw new MsgException("申诉已处理,无法重复操作");
}
Long adminId = SecurityUtils.getUserId();
Date now = new Date();
appeal.setHandlerUserId(adminId);
appeal.setHandleRemark(form.getHandleRemark());
appeal.setHandleTime(now);
appeal.setStatus(form.getResult());
appeal.setUpdateBy(adminId);
appeal.setUpdateTime(now);
appeal.setUpdateTimestamp(System.currentTimeMillis());
appealMapper.updateById(appeal);
// 更新审核汇总状态
MiniContentAudit audit = contentAuditService.getById(appeal.getAuditId());
if (audit != null) {
if ("approved".equals(form.getResult())) {
contentAuditService.updateAuditStatus(appeal.getAuditId(),
AuditConstants.STATUS_PASSED);
log.info("申诉通过, auditId={}, 审核状态已改为passed", appeal.getAuditId());
} else {
contentAuditService.updateAuditStatus(appeal.getAuditId(),
AuditConstants.STATUS_REJECTED);
log.info("申诉驳回, auditId={}, 审核状态维持rejected", appeal.getAuditId());
}
}
}
}

105
src/main/java/com/youlai/boot/admin/service/impl/ContentAuditConfigServiceImpl.java

@ -0,0 +1,105 @@
package com.youlai.boot.admin.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.admin.converter.AuditConfigConverter;
import com.youlai.boot.admin.mapper.MiniContentAuditConfigMapper;
import com.youlai.boot.admin.model.entity.MiniContentAuditConfig;
import com.youlai.boot.admin.model.form.AuditConfigForm;
import com.youlai.boot.admin.model.query.AuditConfigQuery;
import com.youlai.boot.admin.model.vo.AuditConfigVO;
import com.youlai.boot.admin.service.ContentAuditConfigService;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.framework.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@RequiredArgsConstructor
@Slf4j
public class ContentAuditConfigServiceImpl extends ServiceImpl<MiniContentAuditConfigMapper, MiniContentAuditConfig> implements ContentAuditConfigService {
private final AuditConfigConverter auditConfigConverter;
@Override
public IPage<AuditConfigVO> pageConfig(AuditConfigQuery query) {
Page<MiniContentAuditConfig> page = new Page<>(query.getPageNum(), query.getPageSize());
LambdaQueryWrapper<MiniContentAuditConfig> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(query.getModuleCode() != null && !query.getModuleCode().isBlank(),
MiniContentAuditConfig::getModuleCode, query.getModuleCode());
wrapper.orderByDesc(MiniContentAuditConfig::getCreateTime);
Page<MiniContentAuditConfig> result = this.page(page, wrapper);
return auditConfigConverter.toPageVo(result);
}
@Override
public AuditConfigVO getConfigByUuid(String uuid) {
MiniContentAuditConfig entity = getByUuidOrThrow(uuid);
return auditConfigConverter.toVo(entity);
}
@Override
public AuditConfigVO addConfig(AuditConfigForm form) {
// 同一 moduleCode 只能有一条配置
long count = this.count(new LambdaQueryWrapper<MiniContentAuditConfig>()
.eq(MiniContentAuditConfig::getModuleCode, form.getModuleCode()));
if (count > 0) {
throw new MsgException("该业务模块已存在审核配置");
}
MiniContentAuditConfig entity = auditConfigConverter.toEntity(form);
entity.setUuid(IdUtil.fastSimpleUUID());
entity.setCreateBy(SecurityUtils.getUserId());
entity.setCreateTime(new Date());
this.save(entity);
return auditConfigConverter.toVo(entity);
}
@Override
public AuditConfigVO updateConfig(String uuid, AuditConfigForm form) {
MiniContentAuditConfig entity = getByUuidOrThrow(uuid);
// 如果修改了 moduleCode,校验唯一性
if (!entity.getModuleCode().equals(form.getModuleCode())) {
long count = this.count(new LambdaQueryWrapper<MiniContentAuditConfig>()
.eq(MiniContentAuditConfig::getModuleCode, form.getModuleCode())
.ne(MiniContentAuditConfig::getId, entity.getId()));
if (count > 0) {
throw new MsgException("该业务模块已存在审核配置");
}
}
entity.setModuleCode(form.getModuleCode());
entity.setAuditEnable(form.getAuditEnable());
entity.setAuditType(form.getAuditType());
entity.setRiskStrategy(form.getRiskStrategy());
entity.setUpdateBy(SecurityUtils.getUserId());
entity.setUpdateTime(new Date());
this.updateById(entity);
return auditConfigConverter.toVo(entity);
}
@Override
public void deleteConfig(String uuid) {
MiniContentAuditConfig entity = getByUuidOrThrow(uuid);
entity.setDeleted(true);
entity.setUpdateBy(SecurityUtils.getUserId());
entity.setUpdateTime(new Date());
this.updateById(entity);
}
private MiniContentAuditConfig getByUuidOrThrow(String uuid) {
MiniContentAuditConfig entity = this.getOne(new LambdaQueryWrapper<MiniContentAuditConfig>()
.eq(MiniContentAuditConfig::getUuid, uuid));
if (entity == null) {
throw new MsgException("审核配置不存在");
}
return entity;
}
}

76
src/main/java/com/youlai/boot/admin/service/impl/ContentAuditServiceImpl.java

@ -0,0 +1,76 @@
package com.youlai.boot.admin.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.admin.constant.AuditConstants;
import com.youlai.boot.admin.mapper.MiniContentAuditMapper;
import com.youlai.boot.admin.model.entity.MiniContentAudit;
import com.youlai.boot.admin.service.BizAuditStatusHandler;
import com.youlai.boot.admin.service.ContentAuditService;
import com.youlai.boot.framework.security.util.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Date;
@Slf4j
@Service
public class ContentAuditServiceImpl extends ServiceImpl<MiniContentAuditMapper, MiniContentAudit> implements ContentAuditService {
private final BizAuditStatusHandler bizAuditStatusHandler;
public ContentAuditServiceImpl(BizAuditStatusHandler bizAuditStatusHandler) {
this.bizAuditStatusHandler = bizAuditStatusHandler;
}
@Override
public MiniContentAudit createAudit(String moduleCode, Long bizId, String auditType, String triggerType) {
MiniContentAudit entity = new MiniContentAudit();
entity.setUuid(IdUtil.fastSimpleUUID());
entity.setModuleCode(moduleCode);
entity.setBizId(bizId);
entity.setAuditType(auditType);
entity.setTriggerType(triggerType);
entity.setStatus(AuditConstants.STATUS_REVIEWING);
entity.setCreateBy(SecurityUtils.getUserId());
entity.setCreateTime(new Date());
entity.setCreateTimestamp(System.currentTimeMillis());
this.save(entity);
return entity;
}
@Override
public void updateAuditStatus(Long auditId, String status) {
MiniContentAudit entity = new MiniContentAudit();
entity.setId(auditId);
entity.setStatus(status);
entity.setUpdateBy(SecurityUtils.getUserId());
entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis());
this.updateById(entity);
// 审核完成(通过/不通过)时回调业务处理器,同步更新业务实体的审核状态
if (AuditConstants.STATUS_PASSED.equals(status) || AuditConstants.STATUS_REJECTED.equals(status)) {
try {
MiniContentAudit audit = this.getById(auditId);
if (audit != null) {
if (AuditConstants.STATUS_PASSED.equals(status)) {
bizAuditStatusHandler.onAuditPassed(audit.getModuleCode(), audit.getBizId());
} else {
bizAuditStatusHandler.onAuditRejected(audit.getModuleCode(), audit.getBizId());
}
}
} catch (Exception e) {
log.error("回调业务审核状态处理器失败, auditId={}, status={}", auditId, status, e);
}
}
}
@Override
public MiniContentAudit getByModuleAndBizId(String moduleCode, Long bizId) {
return this.getOne(new LambdaQueryWrapper<MiniContentAudit>()
.eq(MiniContentAudit::getModuleCode, moduleCode)
.eq(MiniContentAudit::getBizId, bizId));
}
}

108
src/main/java/com/youlai/boot/admin/service/impl/ContentAuditTaskServiceImpl.java

@ -0,0 +1,108 @@
package com.youlai.boot.admin.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.admin.constant.AuditConstants;
import com.youlai.boot.admin.mapper.MiniContentAuditTaskMapper;
import com.youlai.boot.admin.model.entity.MiniContentAuditTask;
import com.youlai.boot.admin.service.ContentAuditTaskService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Service
public class ContentAuditTaskServiceImpl extends ServiceImpl<MiniContentAuditTaskMapper, MiniContentAuditTask> implements ContentAuditTaskService {
@Override
@Transactional(rollbackFor = Exception.class)
public void batchCreateTasks(Long auditId, String auditType, List<String> texts, List<String> images, List<String> videos) {
List<MiniContentAuditTask> tasks = new ArrayList<>();
Date now = new Date();
Long nowTs = System.currentTimeMillis();
// 文本内容:每条文本单独一个任务
if (texts != null) {
for (String text : texts) {
if (text != null && !text.isBlank()) {
tasks.add(buildTask(auditId, auditType, "text", text, now, nowTs));
}
}
}
// 图片内容:每张图片URL单独一个任务
if (images != null) {
for (String imageUrl : images) {
if (imageUrl != null && !imageUrl.isBlank()) {
tasks.add(buildTask(auditId, auditType, "image", imageUrl, now, nowTs));
}
}
}
// 视频内容:每个视频URL单独一个任务
if (videos != null) {
for (String videoUrl : videos) {
if (videoUrl != null && !videoUrl.isBlank()) {
tasks.add(buildTask(auditId, auditType, "video", videoUrl, now, nowTs));
}
}
}
if (!tasks.isEmpty()) {
//批量插入任务记录
this.saveBatch(tasks);
}
}
/**
* 构建单个审核任务实体
* MiniContentAuditTask字段uuid(前端交互) / contentAuditId(关联汇总) / contentType(内容类型) / contentValue(待审核值)
* / auditType(审核类型) / status(任务状态) / createTime / createTimestamp
*/
private MiniContentAuditTask buildTask(Long auditId, String auditType, String contentType, String contentValue, Date now, Long nowTs) {
MiniContentAuditTask task = new MiniContentAuditTask();
task.setUuid(IdUtil.fastSimpleUUID());
task.setContentAuditId(auditId);
task.setContentType(contentType);
task.setContentValue(contentValue);
task.setAuditType(auditType);
task.setStatus(AuditConstants.STATUS_REVIEWING);
task.setCreateTime(now);
task.setCreateTimestamp(nowTs);
return task;
}
@Override
public void updateTaskMachineResult(Long taskId, String machineResult, String riskLevel, String status,
String label, Integer confidence, String description, String requestId) {
MiniContentAuditTask entity = new MiniContentAuditTask();
entity.setId(taskId);
entity.setMachineResult(machineResult);
entity.setRiskLevel(riskLevel);
entity.setStatus(status);
entity.setLabel(label);
entity.setConfidence(confidence);
entity.setDescription(description);
entity.setRequestId(requestId);
entity.setUpdateTime(new Date());
entity.setUpdateTimestamp(System.currentTimeMillis());
this.updateById(entity);
}
@Override
public List<MiniContentAuditTask> listTasksByAuditId(Long auditId) {
// LambdaQueryWrapper:构建查询条件 content_audit_id = auditId
return this.list(new LambdaQueryWrapper<MiniContentAuditTask>()
.eq(MiniContentAuditTask::getContentAuditId, auditId));
}
@Override
public List<MiniContentAuditTask> getPendingAsyncTasks() {
return this.list(new LambdaQueryWrapper<MiniContentAuditTask>()
.in(MiniContentAuditTask::getContentType, List.of("image", "video"))
.eq(MiniContentAuditTask::getStatus, AuditConstants.STATUS_REVIEWING)
.isNotNull(MiniContentAuditTask::getTaskId)
.ne(MiniContentAuditTask::getTaskId, ""));
}
}

153
src/main/java/com/youlai/boot/admin/service/impl/ContentLookupServiceImpl.java

@ -0,0 +1,153 @@
package com.youlai.boot.admin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.admin.model.dto.AuditContentDTO;
import com.youlai.boot.admin.service.ContentLookupService;
import com.youlai.boot.mini.mapper.*;
import com.youlai.boot.mini.model.entity.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Service
@RequiredArgsConstructor
@Slf4j
public class ContentLookupServiceImpl implements ContentLookupService {
private final MiniUserPostMapper userPostMapper;
private final MiniUserPostMediaMapper userPostMediaMapper;
private final MiniAdoptionDiaryMapper adoptionDiaryMapper;
private final MiniAdoptionDiaryMediaMapper adoptionDiaryMediaMapper;
private final MiniStrayAnimalNoteMapper strayAnimalNoteMapper;
private final MiniStrayAnimalNoteMediaMapper strayAnimalNoteMediaMapper;
private final MiniUserPostCommentMapper userPostCommentMapper;
private final MiniAdoptionDiaryCommentMapper adoptionDiaryCommentMapper;
private final MiniStrayAnimalNoteCommentMapper strayAnimalNoteCommentMapper;
@Override
public AuditContentDTO lookupContent(String moduleCode, Long bizId) {
if (bizId == null) {
return emptyResult();
}
return switch (moduleCode) {
case "user_post" -> lookupUserPost(bizId);
case "adoption_diary" -> lookupAdoptionDiary(bizId);
case "animal_note" -> lookupAnimalNote(bizId);
case "post_comment" -> lookupPostComment(bizId);
case "diary_comment" -> lookupDiaryComment(bizId);
case "note_comment" -> lookupNoteComment(bizId);
default -> {
log.warn("未知业务模块编码: {}", moduleCode);
yield emptyResult();
}
};
}
private AuditContentDTO lookupUserPost(Long id) {
MiniUserPost post = userPostMapper.selectById(id);
if (post == null) return emptyResult();
List<String> texts = new ArrayList<>();
if (post.getTitle() != null) texts.add(post.getTitle());
if (post.getContent() != null) texts.add(post.getContent());
return buildContentResult(texts, lookupMedia(userPostMediaMapper,
new LambdaQueryWrapper<MiniUserPostMedia>().eq(MiniUserPostMedia::getPostId, id)));
}
private AuditContentDTO lookupAdoptionDiary(Long id) {
MiniAdoptionDiary diary = adoptionDiaryMapper.selectById(id);
if (diary == null) return emptyResult();
List<String> texts = new ArrayList<>();
if (diary.getTitle() != null) texts.add(diary.getTitle());
if (diary.getContent() != null) texts.add(diary.getContent());
return buildContentResult(texts, lookupMedia(adoptionDiaryMediaMapper,
new LambdaQueryWrapper<MiniAdoptionDiaryMedia>().eq(MiniAdoptionDiaryMedia::getDiaryId, id)));
}
private AuditContentDTO lookupAnimalNote(Long id) {
MiniStrayAnimalNote note = strayAnimalNoteMapper.selectById(id);
if (note == null) return emptyResult();
List<String> texts = new ArrayList<>();
if (note.getTitle() != null) texts.add(note.getTitle());
if (note.getContent() != null) texts.add(note.getContent());
return buildContentResult(texts, lookupMedia(strayAnimalNoteMediaMapper,
new LambdaQueryWrapper<MiniStrayAnimalNoteMedia>().eq(MiniStrayAnimalNoteMedia::getNoteId, id)));
}
private AuditContentDTO lookupPostComment(Long id) {
MiniUserPostComment comment = userPostCommentMapper.selectById(id);
if (comment == null) return emptyResult();
AuditContentDTO dto = new AuditContentDTO();
dto.setTexts(comment.getContent() != null ? List.of(comment.getContent()) : Collections.emptyList());
return dto;
}
private AuditContentDTO lookupDiaryComment(Long id) {
MiniAdoptionDiaryComment comment = adoptionDiaryCommentMapper.selectById(id);
if (comment == null) return emptyResult();
AuditContentDTO dto = new AuditContentDTO();
dto.setTexts(comment.getContent() != null ? List.of(comment.getContent()) : Collections.emptyList());
return dto;
}
private AuditContentDTO lookupNoteComment(Long id) {
MiniStrayAnimalNoteComment comment = strayAnimalNoteCommentMapper.selectById(id);
if (comment == null) return emptyResult();
AuditContentDTO dto = new AuditContentDTO();
dto.setTexts(comment.getContent() != null ? List.of(comment.getContent()) : Collections.emptyList());
return dto;
}
/** 分离媒体文件的图片和视频 URL */
private <T> AuditContentDTO buildContentResult(List<String> texts, List<MediaUrlPair> mediaList) {
AuditContentDTO dto = new AuditContentDTO();
dto.setTexts(texts);
List<String> images = new ArrayList<>();
List<String> videos = new ArrayList<>();
for (MediaUrlPair m : mediaList) {
if ("video".equals(m.type)) {
videos.add(m.url);
} else {
images.add(m.url);
}
}
dto.setImages(images.isEmpty() ? null : images);
dto.setVideos(videos.isEmpty() ? null : videos);
return dto;
}
private record MediaUrlPair(String type, String url) {}
/** 通用媒体查询 */
private <M> List<MediaUrlPair> lookupMedia(BaseMapper<M> mapper, LambdaQueryWrapper<M> wrapper) {
List<M> mediaList = mapper.selectList(wrapper);
List<MediaUrlPair> result = new ArrayList<>();
for (M media : mediaList) {
try {
String type = (String) media.getClass().getMethod("getMediaType").invoke(media);
String url = (String) media.getClass().getMethod("getSourceUrl").invoke(media);
if (url != null && !url.isBlank()) {
result.add(new MediaUrlPair(type, url));
}
} catch (Exception e) {
log.warn("提取媒体信息失败", e);
}
}
return result;
}
private AuditContentDTO emptyResult() {
return new AuditContentDTO();
}
}

222
src/main/java/com/youlai/boot/admin/service/impl/OssCallbackServiceImpl.java

@ -0,0 +1,222 @@
package com.youlai.boot.admin.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.youlai.boot.admin.constant.AuditConstants;
import com.youlai.boot.admin.model.entity.MiniContentAudit;
import com.youlai.boot.admin.model.entity.MiniContentAuditTask;
import com.youlai.boot.admin.service.ContentAuditService;
import com.youlai.boot.admin.service.ContentAuditTaskService;
import com.youlai.boot.admin.service.OssCallbackService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@Service
@Slf4j
public class OssCallbackServiceImpl implements OssCallbackService {
private final ContentAuditTaskService contentAuditTaskService;
private final ContentAuditService contentAuditService;
@Value("${audit.aliyun.oss.callbackUid}")
private String callbackUid;
@Value("${audit.aliyun.oss.callbackSeed}")
private String callbackSeed;
public OssCallbackServiceImpl(ContentAuditTaskService contentAuditTaskService,
ContentAuditService contentAuditService) {
this.contentAuditTaskService = contentAuditTaskService;
this.contentAuditService = contentAuditService;
}
@Override
public void handleImageCallback(String body) {
log.info("OSS图片审核回调, body={}", body);
// 解析 form-urlencoded: checksum=xxx&content=xxx%7B...%7D
String checksum = null;
String content = null;
for (String pair : body.split("&")) {
String[] kv = pair.split("=", 2);
if (kv.length == 2) {
if ("checksum".equals(kv[0])) {
checksum = kv[1];
} else if ("content".equals(kv[0])) {
content = URLDecoder.decode(kv[1], StandardCharsets.UTF_8);
}
}
}
if (content == null) {
log.warn("OSS回调content为空");
return;
}
// 1. SHA256 验签
if (!verifyChecksum(content, checksum)) {
log.warn("OSS回调验签失败, checksum={}, content={}", checksum, content);
return;
}
// 2. 解析回调内容
JSONObject payload = JSON.parseObject(content);
Integer code = payload.getInteger("Code");
if (code == null || code != 200) {
log.warn("OSS回调Code非200, code={}", code);
return;
}
JSONObject data = payload.getJSONObject("Data");
if (data == null) {
log.warn("OSS回调Data为空");
return;
}
String ossObjectName = data.getString("OssObjectName");
String riskLevel = data.getString("RiskLevel");
JSONArray results = data.getJSONArray("Results");
String requestId = payload.getString("RequestId");
log.info("OSS图片审核回调, object={}, riskLevel={}, requestId={}", ossObjectName, riskLevel, requestId);
// 3. 根据 ossObjectName 匹配审核任务
MiniContentAuditTask task = findTaskByImageName(ossObjectName);
if (task == null) {
log.warn("未找到匹配的审核任务, ossObjectName={}", ossObjectName);
return;
}
// 4. 展平 Results[].Result[],取置信度最高的非 nonLabel 标签
String label = null;
Integer confidence = null;
String description = null;
if (results != null && !results.isEmpty()) {
JSONArray flatResults = new JSONArray();
for (int i = 0; i < results.size(); i++) {
JSONObject result = results.getJSONObject(i);
JSONArray subResults = result.getJSONArray("Result");
if (subResults != null) {
for (int j = 0; j < subResults.size(); j++) {
JSONObject sub = subResults.getJSONObject(j);
// OSS 回调 key 为大写,统一转为小写以复用 pickBestFromResultArray
JSONObject normalized = new JSONObject();
normalized.put("label", sub.getString("Label"));
normalized.put("description", sub.getString("Description"));
if (sub.containsKey("Confidence")) {
normalized.put("confidence", sub.getFloat("Confidence"));
}
flatResults.add(normalized);
}
}
}
JSONObject best = AuditExecutorServiceImpl.pickBestFromResultArray(flatResults);
if (best != null) {
label = best.getString("label");
description = best.getString("description");
if (best.containsKey("confidence")) {
confidence = best.getInteger("confidence");
}
}
}
// 5. riskLevel → status 映射,更新任务
String status = ossRiskLevelToStatus(riskLevel);
contentAuditTaskService.updateTaskMachineResult(
task.getId(), JSON.toJSONString(payload), riskLevel != null ? riskLevel.toLowerCase() : null,
status, label, confidence, description, requestId);
// 6. 重新汇总 audit
aggregateCallbackAudit(task.getContentAuditId());
log.info("OSS回调处理完成, taskId={}, ossObjectName={}, riskLevel={}, label={}, confidence={}, description={}, status={}, requestId={}",
task.getId(), ossObjectName, riskLevel, label, confidence, description, status, requestId);
}
/** SHA256(uid + seed + content) 验签,对应阿里云控制台配置的加密算法 */
private boolean verifyChecksum(String content, String checksum) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
String input = callbackUid + callbackSeed + content;
byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
hex.append(String.format("%02x", b));
}
return hex.toString().equals(checksum);
} catch (Exception e) {
log.error("SHA256验签异常", e);
return false;
}
}
/** 通过 ossObjectName(如 v2-xxx_r.jpg)匹配审核任务中图片 URL 尾部 */
private MiniContentAuditTask findTaskByImageName(String ossObjectName) {
if (ossObjectName == null) return null;
return contentAuditTaskService.getOne(new LambdaQueryWrapper<MiniContentAuditTask>()
.eq(MiniContentAuditTask::getContentType, "image")
.like(MiniContentAuditTask::getContentValue, ossObjectName)
.last("LIMIT 1"));
}
/** OSS 回调 RiskLevel → task status:none→passed, medium→manual_review, high→rejected */
private String ossRiskLevelToStatus(String riskLevel) {
if (riskLevel == null) return AuditConstants.STATUS_MANUAL_REVIEW;
return switch (riskLevel.toLowerCase()) {
case "none" -> AuditConstants.STATUS_PASSED;
case "high" -> AuditConstants.STATUS_REJECTED;
default -> AuditConstants.STATUS_MANUAL_REVIEW; // medium 及其他
};
}
/** 聚合回调任务结果,重新计算所属审核汇总的状态 */
private void aggregateCallbackAudit(Long auditId) {
if (auditId == null) return;
try {
MiniContentAudit audit = contentAuditService.getById(auditId);
if (audit == null) return;
java.util.List<MiniContentAuditTask> tasks = contentAuditTaskService.listTasksByAuditId(auditId);
if (tasks.isEmpty()) return;
boolean hasRejected = false;
boolean hasManualReview = false;
boolean allPassed = true;
for (MiniContentAuditTask t : tasks) {
String s = t.getStatus();
if (AuditConstants.STATUS_REJECTED.equals(s)) {
hasRejected = true;
allPassed = false;
break;
}
if (AuditConstants.STATUS_MANUAL_REVIEW.equals(s)) {
hasManualReview = true;
allPassed = false;
} else if (!AuditConstants.STATUS_PASSED.equals(s)) {
allPassed = false;
}
}
String auditStatus;
if (hasRejected) {
auditStatus = AuditConstants.STATUS_REJECTED;
} else if (hasManualReview) {
auditStatus = AuditConstants.STATUS_MANUAL_REVIEW;
} else if (allPassed) {
auditStatus = AuditConstants.STATUS_PASSED;
} else {
auditStatus = AuditConstants.STATUS_REVIEWING;
}
if (!AuditConstants.STATUS_REVIEWING.equals(auditStatus)) {
contentAuditService.updateAuditStatus(auditId, auditStatus);
log.info("OSS回调更新审核汇总状态, auditId={}, status={}", auditId, auditStatus);
}
} catch (Exception e) {
log.error("OSS回调汇总审核失败, auditId={}", auditId, e);
}
}
}

109
src/main/java/com/youlai/boot/admin/service/impl/ReportManageServiceImpl.java

@ -0,0 +1,109 @@
package com.youlai.boot.admin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.admin.constant.AuditConstants;
import com.youlai.boot.admin.model.dto.AuditContentDTO;
import com.youlai.boot.admin.model.form.ReportHandleForm;
import com.youlai.boot.admin.model.query.ReportQuery;
import com.youlai.boot.admin.model.vo.ReportVO;
import com.youlai.boot.admin.service.AuditExecutorService;
import com.youlai.boot.admin.service.ContentLookupService;
import com.youlai.boot.admin.service.ReportManageService;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.mapper.MiniReportMapper;
import com.youlai.boot.mini.model.entity.MiniReport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.Map;
@Service
@RequiredArgsConstructor
@Slf4j
public class ReportManageServiceImpl implements ReportManageService {
private final MiniReportMapper reportMapper;
private final ContentLookupService contentLookupService;
private final AuditExecutorService auditExecutorService;
@Override
public IPage<ReportVO> pageReport(ReportQuery query) {
LambdaQueryWrapper<MiniReport> wrapper = new LambdaQueryWrapper<>();
if (query.getTargetType() != null && !query.getTargetType().isBlank()) {
wrapper.eq(MiniReport::getTargetType, query.getTargetType());
}
if (query.getStatus() != null && !query.getStatus().isBlank()) {
wrapper.eq(MiniReport::getStatus, query.getStatus());
}
wrapper.orderByDesc(MiniReport::getCreateTime);
Page<MiniReport> page = new Page<>(query.getPageNum(), query.getPageSize());
Page<MiniReport> result = reportMapper.selectPage(page, wrapper);
return result.convert(entity -> ReportVO.builder()
.id(entity.getId())
.uuid(entity.getUuid())
.targetType(entity.getTargetType())
.targetId(entity.getTargetId())
.reporterUserId(entity.getReporterUserId())
.reasonCategory(entity.getReasonCategory())
.evidence(entity.getEvidence())
.description(entity.getDescription())
.status(entity.getStatus())
.auditId(entity.getAuditId())
.handlerUserId(entity.getHandlerUserId())
.handleRemark(entity.getHandleRemark())
.handleTime(entity.getHandleTime())
.createTime(entity.getCreateTime())
.build());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void handleReport(Long id, ReportHandleForm form) {
MiniReport report = reportMapper.selectById(id);
if (report == null) {
throw new MsgException("举报记录不存在");
}
if (!"pending".equals(report.getStatus())) {
throw new MsgException("举报已处理,无法重复操作");
}
Long adminId = SecurityUtils.getUserId();
Date now = new Date();
if ("processed".equals(form.getResult())) {
// 受理举报:自动查询目标内容 → 创建审核 → 执行机审
String moduleCode = report.getTargetType();
Long bizId = report.getTargetId();
AuditContentDTO content = contentLookupService.lookupContent(moduleCode, bizId);
Map<String, Object> auditResult = auditExecutorService.executeAudit(
moduleCode, bizId, content, AuditConstants.TRIGGER_REPORT);
if (auditResult != null && auditResult.get("auditId") != null) {
report.setAuditId((Long) auditResult.get("auditId"));
log.info("举报受理, 已自动创建审核并执行机审, auditId={}, moduleCode={}, bizId={}",
auditResult.get("auditId"), moduleCode, bizId);
} else {
log.warn("举报受理, 审核配置关闭或不存在, moduleCode={}, bizId={}", moduleCode, bizId);
}
}
report.setStatus(form.getResult());
report.setHandlerUserId(adminId);
report.setHandleRemark(form.getHandleRemark());
report.setHandleTime(now);
report.setUpdateBy(adminId);
report.setUpdateTime(now);
report.setUpdateTimestamp(System.currentTimeMillis());
reportMapper.updateById(report);
}
}

30
src/main/java/com/youlai/boot/codegen/freemarker/MyBatisPlusGenerator.java

@ -99,6 +99,36 @@ public class MyBatisPlusGenerator {
,new TableConfig("mini_point_record", IdType.AUTO, "mini")
,new TableConfig("mini_point_rule", IdType.AUTO, "mini")
,new TableConfig("mini_sign_record", IdType.AUTO, "mini")
,new TableConfig("mini_stray_animal_note_comment", IdType.AUTO, "mini")
,new TableConfig("mini_stray_animal_note_comment_like", IdType.AUTO, "mini")
,new TableConfig("mini_stray_animal_note_like", IdType.AUTO, "mini")
,new TableConfig("mini_stray_animal_note_collect", IdType.AUTO, "mini")
,new TableConfig("mini_stray_animal_note_view", IdType.AUTO, "mini")
,new TableConfig("mini_adoption_diary", IdType.AUTO, "mini")
,new TableConfig("mini_adoption_diary_media", IdType.AUTO, "mini")
,new TableConfig("mini_adoption_diary_view", IdType.AUTO, "mini")
,new TableConfig("mini_adoption_diary_like", IdType.AUTO, "mini")
,new TableConfig("mini_adoption_diary_collect", IdType.AUTO, "mini")
,new TableConfig("mini_adoption_diary_comment", IdType.AUTO, "mini")
,new TableConfig("mini_adoption_diary_comment_like", IdType.AUTO, "mini")
,new TableConfig("mini_user_post", IdType.AUTO, "mini")
,new TableConfig("mini_user_post_media", IdType.AUTO, "mini")
,new TableConfig("mini_user_post_like", IdType.AUTO, "mini")
,new TableConfig("mini_user_post_collect", IdType.AUTO, "mini")
,new TableConfig("mini_user_post_view", IdType.AUTO, "mini")
,new TableConfig("mini_user_post_view", IdType.AUTO, "mini")
,new TableConfig("mini_user_post_comment", IdType.AUTO, "mini")
,new TableConfig("mini_user_post_comment_like", IdType.AUTO, "mini")
,new TableConfig("mini_adoption_application", IdType.AUTO, "mini")
,new TableConfig("mini_content_audit_appeal", IdType.AUTO, "mini")
,new TableConfig("mini_report", IdType.AUTO, "mini")
,new TableConfig("mini_content_audit_config", IdType.AUTO, "admin")
,new TableConfig("mini_content_audit", IdType.AUTO, "admin")
,new TableConfig("mini_content_audit_task", IdType.AUTO, "admin")
,new TableConfig("mini_user_follow", IdType.AUTO, "mini")
,new TableConfig("mini_ai_generation_task", IdType.AUTO, "mini")
,new TableConfig("mini_ai_task_media", IdType.AUTO, "mini")
,new TableConfig("mini_user_subscribe", IdType.AUTO, "mini")
// ,new TableConfig("mini_stray_animal", IdType.AUTO, "mini")
// ,new TableConfig("mini_stray_animal", IdType.INPUT, "minitest")

8
src/main/java/com/youlai/boot/common/constant/RedisConstants.java

@ -60,4 +60,12 @@ public interface RedisConstants {
String ROLE_PERMS = "system:role:perms"; // 系统角色和权限映射
}
/**
* 社交模块
*/
interface Social {
String FOLLOWING = "social:follow:following:{}"; // 用户关注列表(userId → Set<followedUserId>)
String FOLLOWERS = "social:follow:followers:{}"; // 用户粉丝列表(userId → Set<followerUserId>)
}
}

16
src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java

@ -32,7 +32,21 @@ public enum LogModuleEnum implements IBaseEnum<Integer> {
POINT_ACCOUNT(100, "积分账户"),
POINT_RECORD(101, "积分流水"),
POINT_RULE(102, "积分规则"),
SIGN_RECORD(103, "签到记录");
SIGN_RECORD(103, "签到记录"),
AI_TASK_MEDIA(104, "AI任务媒体"),
AI_GENERATION_TASK(105, "AI生成任务"),
USER_SUBSCRIBE(106, "用户订阅模板消息"),
STRAY_ANIMAL_NOTE_COMMENT(110, "流浪动物笔记评论"),
ADOPTION_DIARY_COMMENT(111, "领养日记评论"),
ADOPTION_DIARY_INFO(112, "领养日记信息"),
USER_POST_INFO(113, "用户作品信息"),
USER_POST_COMMENT(114, "用户作品评论"),
ADOPTION_APPLICATION(115, "领养申请"),
CONTENT_AUDIT_CONFIG(116, "内容审核配置"),
CONTENT_AUDIT(117, "内容审核"),
CONTENT_AUDIT_TASK(118, "审核任务"),
CONTENT_AUDIT_APPEAL(119, "内容申诉"),
REPORT(120, "举报管理");
@EnumValue
private final Integer value;

291
src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java

@ -0,0 +1,291 @@
package com.youlai.boot.common.util;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.List;
import com.aliyun.green20220302.Client;
import com.aliyun.green20220302.models.*;
import com.aliyun.teaopenapi.models.Config;
import com.youlai.boot.common.exception.MsgException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 阿里云内容安全统一审核工具类v2.0
* ----------------------------------------
* 支持
* 1. 文本审核
* 2. 图片审核
* 3. 视频审核异步
* 4. 取消视频审核任务直播流
*
* 特性
* - 自动切换备用节点cn-beijing
* - application.yml 中读取配置
* - 返回 null 表示请求失败业务层自行判断
*/
@Slf4j
@Component
public class AliyunContentAuditUtil {
@Value("${audit.aliyun.green.accessKeyId}")
private String accessKeyId;
@Value("${audit.aliyun.green.accessKeySecret}")
private String accessKeySecret;
// 主节点(上海)
private static final String REGION_PRIMARY = "cn-shanghai";
private static final String ENDPOINT_PRIMARY = "green-cip.cn-shanghai.aliyuncs.com";
// 备用节点(北京)
private static final String REGION_BACKUP = "cn-beijing";
private static final String ENDPOINT_BACKUP = "green-cip.cn-beijing.aliyuncs.com";
// 批量审核上限
private static final int BATCH_TEXT_MAX = 100;
private static final int BATCH_TEXT_SINGLE_MAX_CHARS = 600;
/**
* 构建客户端
*/
private Client createClient(boolean useBackup) throws Exception {
String region = useBackup ? REGION_BACKUP : REGION_PRIMARY;
String endpoint = useBackup ? ENDPOINT_BACKUP : ENDPOINT_PRIMARY;
Config config = new Config()
.setAccessKeyId(accessKeyId)
.setAccessKeySecret(accessKeySecret)
.setRegionId(region)
.setEndpoint(endpoint)
.setReadTimeout(6000)
.setConnectTimeout(3000);
return new Client(config);
}
/**
* 自动调用机制
* - 优先使用上海节点
* - 如果响应码为 500 或出现异常自动切换至北京节点重试一次
*/
private <T> T executeWithFailover(AliyunRequestExecutor<T> executor) {
try {
Client primary = createClient(false);
T result = executor.execute(primary);
if (isServerError(result)) {
log.warn("阿里云内容安全服务端异常,切换到北京节点重试...");
Client backup = createClient(true);
return executor.execute(backup);
}
return result;
} catch (Exception e) {
log.error("阿里云内容安全调用异常(主节点失败),切换至备用节点", e);
try {
Client backup = createClient(true);
return executor.execute(backup);
} catch (Exception ex) {
log.error("备用节点调用也失败", ex);
return null;
}
}
}
/**
* 判断是否服务端异常statusCode=500
*/
private boolean isServerError(Object response) {
try {
Integer code = (Integer) response.getClass().getMethod("getStatusCode").invoke(response);
return code != null && code == 500;
} catch (Exception ignored) {
return false;
}
}
// ===================== 文本审核 =====================
public TextModerationResponse textModeration(String content) {
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
params.put("content", content);
TextModerationRequest request = new TextModerationRequest()
.setService("comment_detection")
.setServiceParameters(params.toJSONString());
return client.textModeration(request);
});
}
/**
* 批量文本审核上限100条单条不超过600字
*/
public TextModerationResponse batchTextModeration(List<String> contents) {
if (contents == null || contents.isEmpty()) {
return null;
}
if (contents.size() > BATCH_TEXT_MAX) {
throw new MsgException("批量文本审核单次上限" + BATCH_TEXT_MAX + "条,当前" + contents.size() + "条");
}
for (int i = 0; i < contents.size(); i++) {
String text = contents.get(i);
if (text != null && text.length() > BATCH_TEXT_SINGLE_MAX_CHARS) {
throw new MsgException("第" + (i + 1) + "条文本超过" + BATCH_TEXT_SINGLE_MAX_CHARS + "字上限");
}
}
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
JSONArray arr = new JSONArray();
arr.addAll(contents);
params.put("content", arr);
TextModerationRequest request = new TextModerationRequest()
.setService("comment_detection")
.setServiceParameters(params.toJSONString());
return client.textModeration(request);
});
}
// ===================== 文本审核Plus =====================
//nickname_detection_pro 用户昵称检测_专业版(用户昵称) ;ugc_moderation_byllm_pro UGC场景文本审核大模型服务_专业版(个人简介 / 作品内容) ;
//comment_detection_pro 公聊评论内容检测_专业版(评论); ugc_moderation_byllm UGC场景文本审核大模型服务 (作品标题)
public TextModerationPlusResponse textModerationPlus(String content, String service) {
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
params.put("content", content);
TextModerationPlusRequest textModerationPlusRequest = new TextModerationPlusRequest()
.setService(service != null ? service : "ugc_moderation_byllm_pro")
.setServiceParameters(params.toJSONString());
return client.textModerationPlus(textModerationPlusRequest);
});
}
/**
* 批量文本审核Plus上限100条单条不超过600字
*/
public TextModerationPlusResponse batchTextModerationPlus(List<String> contents) {
if (contents == null || contents.isEmpty()) {
return null;
}
if (contents.size() > BATCH_TEXT_MAX) {
throw new MsgException("批量文本审核单次上限" + BATCH_TEXT_MAX + "条,当前" + contents.size() + "条");
}
for (int i = 0; i < contents.size(); i++) {
String text = contents.get(i);
if (text != null && text.length() > BATCH_TEXT_SINGLE_MAX_CHARS) {
throw new MsgException("第" + (i + 1) + "条文本超过" + BATCH_TEXT_SINGLE_MAX_CHARS + "字上限");
}
}
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
JSONArray arr = new JSONArray();
arr.addAll(contents);
params.put("content", arr);
TextModerationPlusRequest request = new TextModerationPlusRequest()
.setService("ugc_moderation_byllm_pro")
.setServiceParameters(params.toJSONString());
return client.textModerationPlus(request);
});
}
// ===================== 图片审核 =====================
// 头像图片检测:profilePhotoCheck (头像) ; AI生成图片鉴别_含隐式标识版:aigcDetectorFull(AI生成图片) ;
// 大小模型融合图片审核服务:postlmageCheckByVL (用户上传的图片) ; OSS基线检测(OSS普惠版专用):oss_baselineCheck
// 通用图片审核大模型服务:baselineCheckByVL(用户作品);图片万物识别:generalRecognition ;营销素材检测:advertisingCheck
public ImageModerationResponse imageModeration(String imageUrl, String service) {
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
params.put("imageUrl", imageUrl);
ImageModerationRequest request = new ImageModerationRequest()
.setService(service != null ? service : "baselineCheckByVL")
.setServiceParameters(params.toJSONString());
return client.imageModeration(request);
});
}
// ===================== 图片审核(异步) =====================
public ImageAsyncModerationResponse imageAsyncModeration(String imageUrl, String service) {
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
params.put("imageUrl", imageUrl);
params.put("dataId", java.util.UUID.randomUUID().toString());
ImageAsyncModerationRequest request = new ImageAsyncModerationRequest()
.setService(service != null ? service : "baselineCheckByVL")
.setServiceParameters(params.toJSONString());
return client.imageAsyncModeration(request);
});
}
public DescribeImageModerationResultResponse describeImageModerationResult(String reqId) {
return executeWithFailover(client -> {
DescribeImageModerationResultRequest request = new DescribeImageModerationResultRequest()
.setReqId(reqId);
return client.describeImageModerationResult(request);
});
}
// ===================== 视频审核(异步) =====================
//视频文件检测:videoDetection ; AI生成视频判定:videoAigcDetector ; 视频文件检测_大模型版:videoDetectionByVL
public VideoModerationResponse videoModeration(String videoUrl, String service) {
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
params.put("url", videoUrl);
VideoModerationRequest request = new VideoModerationRequest()
.setService(service != null ? service : "videoDetection")
.setServiceParameters(params.toJSONString());
return client.videoModeration(request);
});
}
// 视频审核不支持批量,使用 videoModeration 逐个调用
// ===================== 查询视频审核结果 =====================
public VideoModerationResultResponse videoModerationResult(String taskId) {
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
params.put("taskId", taskId);
VideoModerationResultRequest request = new VideoModerationResultRequest()
.setService("videoDetection")
.setServiceParameters(params.toJSONString());
return client.videoModerationResult(request);
});
}
// ===================== 取消视频直播流检测任务 =====================
public VideoModerationCancelResponse cancelVideoTask(String taskId) {
return executeWithFailover(client -> {
JSONObject params = new JSONObject();
params.put("taskId", taskId);
VideoModerationCancelRequest request = new VideoModerationCancelRequest()
.setService("liveStreamDetection")
.setServiceParameters(params.toJSONString());
return client.videoModerationCancel(request);
});
}
/**
* 函数式接口用于统一封装 SDK 调用逻辑
*/
@FunctionalInterface
private interface AliyunRequestExecutor<T> {
T execute(Client client) throws Exception;
}
}

24
src/main/java/com/youlai/boot/common/util/CursorEncryptUtil.java

@ -0,0 +1,24 @@
package com.youlai.boot.common.util;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
/**
* 瀑布流游标加解密使用 AES 对称加密隐藏内部自增主键 ID
*/
public class CursorEncryptUtil {
private static final byte[] AES_KEY = "CrsrK3y!2026_StrayAnimals_Proj".getBytes(java.nio.charset.StandardCharsets.UTF_8);
private static final AES AES = SecureUtil.aes(AES_KEY);
public static String encode(Long id) {
if (id == null) return null;
return AES.encryptBase64(String.valueOf(id));
}
public static Long decode(String cursor) {
if (cursor == null || cursor.isBlank()) return null;
return Long.parseLong(AES.decryptStr(cursor));
}
}

30
src/main/java/com/youlai/boot/common/util/HttpContext.java

@ -0,0 +1,30 @@
package com.youlai.boot.common.util;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
public class HttpContext {
public static HttpServletResponse getResponse() throws NullPointerException {
if (ObjectUtil.isNull(RequestContextHolder.getRequestAttributes())) {
return null;
}
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
public static HttpServletRequest getRequest() throws NullPointerException {
if (ObjectUtil.isNull(RequestContextHolder.getRequestAttributes())) {
return null;
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request;
}
}

51
src/main/java/com/youlai/boot/mini/controller/AdoptionApplicationController.java

@ -0,0 +1,51 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.mini.model.query.AdoptionApplicationQuery;
import com.youlai.boot.mini.model.vo.AdoptionApplicationVO;
import com.youlai.boot.mini.service.AdoptionApplicationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@Tag(name = "领养申请相关接口")
@RestController
@RequestMapping("/api/v1/mini/adoptionApplication")
@RequiredArgsConstructor
public class AdoptionApplicationController {
private final AdoptionApplicationService adoptionApplicationService;
@Operation(summary = "提交领养申请")
@PostMapping(value = "/submit", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RepeatSubmit
@Log(module = LogModuleEnum.ADOPTION_APPLICATION, value = ActionTypeEnum.INSERT)
public Result<?> submitApplication(
@RequestParam("strayAnimalUuId") String strayAnimalUuId,
@RequestParam(value = "reason", required = false) String reason,
@RequestPart("photo") MultipartFile photo
) {
String applicationUuid = adoptionApplicationService.submitApplication(strayAnimalUuId, reason, photo);
return Result.success(applicationUuid);
}
@Operation(summary = "获取我的领养申请列表")
@GetMapping("/myApplications")
public PageResult<AdoptionApplicationVO> getMyApplications(AdoptionApplicationQuery query) {
return PageResult.success(adoptionApplicationService.getMyApplications(query));
}
}

96
src/main/java/com/youlai/boot/mini/controller/AdoptionDiaryCommentController.java

@ -0,0 +1,96 @@
package com.youlai.boot.mini.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.form.AdoptionDiaryCommentForm;
import com.youlai.boot.mini.model.form.DeleteAdoptionDiaryCommentForm;
import com.youlai.boot.mini.model.form.DiaryCommentLikeForm;
import com.youlai.boot.mini.model.query.DiaryFirstLevelCommentQueryParam;
import com.youlai.boot.mini.model.query.DiarySecondLevelCommentQueryParam;
import com.youlai.boot.mini.model.vo.AdoptionDiaryCommentVO;
import com.youlai.boot.mini.model.vo.DiaryFirstLevelCommentVO;
import com.youlai.boot.mini.model.vo.DiarySecondLevelCommentVO;
import com.youlai.boot.mini.service.AdoptionDiaryCommentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "领养日记评论的相关接口")
@RestController
@RequestMapping("/api/v1/mini/diaryComment")
@RequiredArgsConstructor
public class AdoptionDiaryCommentController {
private final AdoptionDiaryCommentService adoptionDiaryCommentService;
@Operation(summary = "领养日记评论接口")
@PostMapping(value = "add")
@RepeatSubmit
@Log(module = LogModuleEnum.ADOPTION_DIARY_COMMENT, value = ActionTypeEnum.INSERT)
public Result<?> addDiaryComment(
@Valid @RequestBody AdoptionDiaryCommentForm formData
) {
AdoptionDiaryCommentVO vo = adoptionDiaryCommentService.addDiaryComment(formData);
return Result.success(vo);
}
@Operation(summary = "删除领养日记评论接口")
@PostMapping(value = "delete")
@PreAuthorize("isAuthenticated()")
@RepeatSubmit
@Log(module = LogModuleEnum.ADOPTION_DIARY_COMMENT, value = ActionTypeEnum.DELETE)
public Result<?> deleteDiaryComment(
@Valid @RequestBody DeleteAdoptionDiaryCommentForm formData
) {
Long userId = SecurityUtils.getUserId();
adoptionDiaryCommentService.deleteDiaryComment(formData, userId);
return Result.success();
}
@Operation(summary = "分页查询领养日记一级评论列表接口")
@GetMapping(value = "firstLevel/list")
@Log(module = LogModuleEnum.ADOPTION_DIARY_COMMENT, value = ActionTypeEnum.LIST)
public Result<IPage<DiaryFirstLevelCommentVO>> getFirstLevelCommentList(
@Valid @RequestBody DiaryFirstLevelCommentQueryParam queryParam
) {
Long userId = SecurityUtils.getUserId();
IPage<DiaryFirstLevelCommentVO> result = adoptionDiaryCommentService.getFirstLevelCommentList(queryParam, userId);
return Result.success(result);
}
@Operation(summary = "分页查询领养日记二级评论列表接口")
@GetMapping(value = "secondLevel/list")
@Log(module = LogModuleEnum.ADOPTION_DIARY_COMMENT, value = ActionTypeEnum.LIST)
public Result<IPage<DiarySecondLevelCommentVO>> getSecondLevelCommentList(
@Valid @RequestBody DiarySecondLevelCommentQueryParam queryParam
) {
Long userId = SecurityUtils.getUserId();
IPage<DiarySecondLevelCommentVO> result = adoptionDiaryCommentService.getSecondLevelCommentList(queryParam, userId);
return Result.success(result);
}
@Operation(summary = "评论点赞/取消点赞接口")
@PostMapping(value = "like/toggle")
@PreAuthorize("isAuthenticated()")
@RepeatSubmit(expire = 1)
@Log(module = LogModuleEnum.ADOPTION_DIARY_COMMENT, value = ActionTypeEnum.UPDATE)
public Result<Map<String, Object>> toggleLike(
@Valid @RequestBody DiaryCommentLikeForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = adoptionDiaryCommentService.toggleLike(form, userId);
return Result.success(result);
}
}

197
src/main/java/com/youlai/boot/mini/controller/AdoptionDiaryController.java

@ -0,0 +1,197 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.dto.*;
import com.youlai.boot.mini.model.form.*;
import com.youlai.boot.mini.model.query.AdoptionDiaryWaterfallQuery;
import com.youlai.boot.mini.model.query.OwnAdoptionDiaryQuery;
import com.youlai.boot.mini.model.query.OwnStrayAnimalQuery;
import com.youlai.boot.mini.model.query.WaterfallQuery;
import com.youlai.boot.mini.model.vo.AdoptionDiaryVO;
import com.youlai.boot.mini.model.vo.AdoptionDiaryDetailsVO;
import com.youlai.boot.mini.model.vo.StrayAnimalShortVO;
import com.youlai.boot.mini.model.vo.WaterfallResult;
import com.youlai.boot.mini.service.AdoptionDiaryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
@Tag(name = "领养日记的相关接口")
@RestController
@RequestMapping("/api/v1/mini/adoptionDiary")
@RequiredArgsConstructor
public class AdoptionDiaryController {
private final AdoptionDiaryService adoptionDiaryService;
@Operation(summary = "添加领养日记时先保存文件", operationId = "DiarySaveFile")
@PostMapping(value = "save/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.INSERT)
public Result<?> saveFile(
@RequestPart(name = "images", required = false) List<MultipartFile> images,
@RequestPart(name = "videos", required = false) List<MultipartFile> videos
) {
List<String> urlList = adoptionDiaryService.saveFile(images, videos);
return Result.success(urlList);
}
@Operation(summary = "添加领养日记信息")
@PostMapping(value = "save")
@RepeatSubmit
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.INSERT)
public Result<?> saveAdoptionDiary(
@Valid @RequestBody AdoptionDiaryForm formData
) {
String diaryUuid = adoptionDiaryService.saveAdoptionDiary(formData);
return Result.success(diaryUuid);
}
@Operation(summary = "编辑领养日记信息(不包含图片/视频媒体资源)")
@PostMapping(value = "/update/{diaryUuid}")
@RepeatSubmit
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.UPDATE)
public Result<?> updateAdoptionDiary(
@PathVariable String diaryUuid,
@Validated @RequestBody AdoptionDiaryForm formData
) {
adoptionDiaryService.updateAdoptionDiary(diaryUuid, formData);
return Result.success();
}
@Operation(summary = "编辑领养日记时,删除领养日记媒体资源")
@PostMapping(value = "/update/deleteMediaSource")
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.UPDATE)
public Result<?> deleteMediaSource(
@RequestBody @Validated DeleteAdoptionDiaryMediaDTO deleteAdoptionDiaryMediaDTO
){
adoptionDiaryService.deleteMediaSource(deleteAdoptionDiaryMediaDTO);
return Result.success();
}
@Operation(summary = "编辑领养日记时,添加领养日记媒体资源", description = "比如补充图片、补充视频")
@PostMapping(value = "/update/saveMediaSource/{diaryUuid}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RepeatSubmit
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.UPDATE)
public Result<?> saveMediaSource(
@PathVariable String diaryUuid,
@RequestPart(name = "images", required = false) List<MultipartFile> images,
@RequestPart(name = "videos", required = false) List<MultipartFile> videos
) {
adoptionDiaryService.saveMediaSource(diaryUuid, images, videos);
return Result.success();
}
@Operation(summary = "编辑领养日记可见范围")
@PostMapping(value = "/update/visibility/{diaryUuid}")
@RepeatSubmit(expire = 1)
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.UPDATE)
public Result<?> updateVisibility(
@PathVariable String diaryUuid,
@RequestBody @Validated EditVisibilityDTO editVisibilityDTO
) {
adoptionDiaryService.updateVisibility(diaryUuid, editVisibilityDTO);
return Result.success();
}
@Operation(summary = "删除领养日记")
@PostMapping(value = "/delete")
@RepeatSubmit
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.DELETE)
public Result<?> delete(
@RequestBody @Validated DeleteAdoptionDiaryDTO deleteAdoptionDiaryDTO
) {
adoptionDiaryService.delete(deleteAdoptionDiaryDTO);
return Result.success();
}
@Operation(summary = "获取自己创建的领养日记列表")
@GetMapping(value = "/getSelfCreatedPage")
public PageResult<AdoptionDiaryVO> getSelfCreatedPage(
OwnAdoptionDiaryQuery queryParams
) {
return PageResult.success(adoptionDiaryService.getSelfCreatedPage(queryParams));
}
@Operation(summary = "获取领养日记详情")
@RequestMapping(value = "/getDetails/{diaryUuid}", method = RequestMethod.GET)
public Result<AdoptionDiaryDetailsVO> getDetails(
@PathVariable String diaryUuid){
return Result.success(adoptionDiaryService.getDetails(diaryUuid, SecurityUtils.getUserId()));
}
@Operation(summary = "获取某个用户创建的领养日记列表")
@GetMapping(value = "/getOthersCreatedPage/{authorUuid}")
public PageResult<AdoptionDiaryVO> getOthersCreatedPage(
@PathVariable String authorUuid,
OwnAdoptionDiaryQuery queryParams
) {
return PageResult.success(adoptionDiaryService.getOthersCreatedPage(authorUuid, queryParams));
}
@Operation(summary = "领养日记点赞/取消点赞接口")
@PostMapping(value = "/diary/like/toggle")
@PreAuthorize("isAuthenticated()")
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.UPDATE)
public Result<Map<String, Object>> toggleDiaryLike(
@Valid @RequestBody DiaryLikeForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = adoptionDiaryService.toggleDiaryLike(form, userId);
return Result.success(result);
}
@Operation(summary = "日记收藏/取消收藏接口")
@PostMapping(value = "/diary/collect/toggle")
@PreAuthorize("isAuthenticated()")
public Result<Map<String, Object>> toggleDiaryCollect(
@Valid @RequestBody DiaryCollectForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = adoptionDiaryService.toggleDiaryCollect(form, userId);
return Result.success(result);
}
@Operation(summary = "获取领养日记瀑布流")
@GetMapping(value = "/diary/waterfall")
public Result<WaterfallResult<AdoptionDiaryVO>> getWaterfall(
@Valid @RequestBody AdoptionDiaryWaterfallQuery query
) {
Long userId = SecurityUtils.getUserId();
WaterfallResult<AdoptionDiaryVO> result = adoptionDiaryService.getWaterfall(query, userId);
return Result.success(result);
}
@Operation(summary = "重置当前用户的浏览记录过滤规则", description = "重置后瀑布流会重新展示所有看过的内容")
@PostMapping(value = "/resetViewBloom")
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.UPDATE)
public Result<?> resetViewBloom() {
Long userId = SecurityUtils.getUserId();
adoptionDiaryService.resetCurrentUserBloom(userId);
return Result.success("重置成功");
}
@Operation(summary = "增加领养日记浏览量")
@PostMapping(value = "/note/increase-view")
@Log(module = LogModuleEnum.ADOPTION_DIARY_INFO, value = ActionTypeEnum.UPDATE)
@RepeatSubmit(expire = 1)
public Result<?> increaseDiaryViewCount(@RequestParam String diaryUuid) {
adoptionDiaryService.increaseDiaryViewCount(diaryUuid);
return Result.success();
}
}

200
src/main/java/com/youlai/boot/mini/controller/AiGenerationController.java

@ -0,0 +1,200 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.form.*;
import com.youlai.boot.mini.model.vo.AiVideoCallbackVO;
import com.youlai.boot.mini.service.AiGenerationService;
import com.youlai.boot.mini.service.WxSubscribeService;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Pattern;
import org.springframework.security.access.prepost.PreAuthorize;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.mini.model.query.AiTaskMediaQuery;
import com.youlai.boot.mini.model.query.DiscoveryPublicWorkQuery;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.youlai.boot.mini.model.vo.AiGenerationTaskVO;
import com.youlai.boot.mini.model.vo.DiscoveryPublicWorkVO;
import cn.hutool.core.util.StrUtil;
import java.util.List;
import com.youlai.boot.mini.model.vo.UserUploadMediaVO;
@Tag(name = "AI生成相关接口")
@RestController
@RequestMapping("/api/v1/mini/ai/generation")
@RequiredArgsConstructor
@Valid
public class AiGenerationController {
private final AiGenerationService aiGenerationService;
private final WxSubscribeService wxSubscribeService;
@Operation(summary = "上传AI生成参考文件")
@PostMapping(value = "/upload/reference", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RepeatSubmit
@Log(module = LogModuleEnum.AI_TASK_MEDIA, value = ActionTypeEnum.INSERT)
public Result<List<String>> uploadReferenceFile(
@RequestParam(name = "images", required = false) List<MultipartFile> images,
@RequestParam(name = "videos", required = false) List<MultipartFile> videos
) {
Long userId = SecurityUtils.getUserId();
List<String> urlList = aiGenerationService.uploadReferenceFile(images, videos, userId);
return Result.success(urlList);
}
@Operation(summary = "提交单图生成任务")
@PostMapping("/generate-single-image")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<String> generateSingleImage(@Valid @RequestBody AiSingleImageGenerateForm form) {
Long userId = SecurityUtils.getUserId();
String taskUuid = aiGenerationService.createAndGenerateImage(form, userId);
return Result.success(taskUuid);
}
@Operation(summary = "单图任务回调接口")
@PostMapping("/single-image/task/callback")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Boolean> singleImageTaskCallback(@Valid @RequestBody AiSingleImageCallbackForm form) {
boolean success = aiGenerationService.handleTaskCallback(form);
return Result.success(success);
}
@Operation(summary = "提交四宫格漫画生成任务")
@PostMapping("/generate-four-panel")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<String> generateFourPanelImage(@Valid @RequestBody AiFourPanelGenerateForm form) {
Long userId = SecurityUtils.getUserId();
String taskUuid = aiGenerationService.createAndGenerateFourPanel(form, userId);
return Result.success(taskUuid);
}
@Operation(summary = "四宫格漫画任务回调接口")
@PostMapping("/four-panel/task/callback")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Boolean> fourPanelTaskCallback(@Valid @RequestBody AiFourPanelCallbackForm form) {
boolean success = aiGenerationService.handleFourPanelCallback(form);
return Result.success(success);
}
@Operation(summary = "提交视频生成任务")
@PostMapping("/generate-video")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<String> generateVideo(@Valid @RequestBody AiVideoGenerateForm form) {
Long userId = SecurityUtils.getUserId();
String taskUuid = aiGenerationService.createAndGenerateVideo(form, userId);
return Result.success(taskUuid);
}
@Operation(summary = "视频任务回调接口")
@PostMapping("/video/task/callback")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Boolean> videoTaskCallback(@Valid @RequestBody AiVideoCallbackVO vo) {
boolean success = aiGenerationService.handleVideoTaskCallback(vo);
return Result.success(success);
}
@Operation(summary = "上报微信订阅消息授权状态")
@PostMapping("/subscribe/auth/report")
@Log(module = LogModuleEnum.USER_SUBSCRIBE, value = ActionTypeEnum.UPDATE)
public Result<Void> reportSubscribeAuth(@Valid @RequestBody WxSubscribeAuthForm form) {
form.setUserId(SecurityUtils.getUserId());
wxSubscribeService.reportAuth(form);
return Result.success();
}
@Hidden
@Operation(summary = "发送微信订阅消息")
@PostMapping("/subscribe/send")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.INSERT)
public Result<Boolean> sendSubscribeMessage(@Valid @RequestBody WxSubscribeSendForm form) {
form.setUserId(SecurityUtils.getUserId());
Boolean result = wxSubscribeService.sendMessage(form);
return Result.success(result);
}
@Operation(summary = "查询当前用户最近上传的图片视频")
@GetMapping("/my/recent-upload")
public Result<List<UserUploadMediaVO>> getMyRecentUpload() {
Long userId = SecurityUtils.getUserId();
List<UserUploadMediaVO> voList = aiGenerationService.getRecentUploadVO(userId);
return Result.success(voList);
}
@Operation(summary = "删除用户上传的图片/视频")
@PostMapping("/my/upload/delete")
@Log(module = LogModuleEnum.AI_TASK_MEDIA, value = ActionTypeEnum.DELETE)
public Result<Boolean> deleteMyUpload(@RequestParam String uuid) {
Long userId = SecurityUtils.getUserId();
boolean success = aiGenerationService.deleteUploadMedia(userId, uuid);
return Result.success(success);
}
@Operation(summary = "查询我的AI生成任务历史")
@GetMapping("/my/history")
@Log(module = LogModuleEnum.AI_TASK_MEDIA, value = ActionTypeEnum.LIST)
public Result<IPage<AiGenerationTaskVO>> getMyAiGenerateHistory(@Valid AiTaskMediaQuery query) {
Long userId = SecurityUtils.getUserId();
IPage<AiGenerationTaskVO> result = aiGenerationService.getMyAiGenerateHistory(query, userId);
return Result.success(result);
}
@Operation(summary = "设置微信订阅消息跳转版本")
@PostMapping("/subscribe/mini-program-state/set")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Void> setMiniProgramState(
@Schema(description = "跳转版本:developer=开发版 trial=体验版 formal=正式版,为空时删除配置")
@Pattern(regexp = "^(developer|trial|formal|)$", message = "参数只能是developer/trial/formal或为空")
@RequestParam(required = false) String miniProgramState
) {
aiGenerationService.setMiniProgramState(miniProgramState);
return Result.success();
}
@Operation(summary = "更新任务可见范围")
@PostMapping("/my/task/visibility")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Void> updateTaskVisibility(@Valid @RequestBody AiTaskVisibilityForm form) {
Long userId = SecurityUtils.getUserId();
aiGenerationService.updateTaskVisibility(userId, form);
return Result.success();
}
@Operation(summary = "查询公开作品发现页")
@GetMapping("/discovery/feed")
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.LIST)
public Result<IPage<DiscoveryPublicWorkVO>> getPublicDiscoveryFeed(@Valid DiscoveryPublicWorkQuery query) {
IPage<DiscoveryPublicWorkVO> result = aiGenerationService.getPublicDiscoveryFeed(query);
return Result.success(result);
}
@Operation(summary = "手动同步AI任务状态")
@PostMapping("/task/{taskUuid}/sync-status")
@RepeatSubmit(expire = 60)
@Log(module = LogModuleEnum.AI_GENERATION_TASK, value = ActionTypeEnum.UPDATE)
public Result<Void> syncTaskStatus(@PathVariable String taskUuid) {
Long userId = SecurityUtils.getUserId();
MiniAiGenerationTask task = aiGenerationService.getTaskByUuid(taskUuid);
if (task == null) {
return Result.failed("任务不存在");
}
if (!task.getMiniUserId().equals(userId)) {
return Result.failed("无权操作该任务");
}
aiGenerationService.syncTaskStatus(taskUuid);
return Result.success();
}
}

34
src/main/java/com/youlai/boot/mini/controller/MiniContentAuditAppealController.java

@ -0,0 +1,34 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.mini.model.form.AppealSubmitForm;
import com.youlai.boot.mini.service.MiniContentAuditAppealService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "小程序端-内容申诉相关接口")
@RestController
@RequestMapping("/api/v1/mini/appeal")
@RequiredArgsConstructor
public class MiniContentAuditAppealController {
private final MiniContentAuditAppealService appealService;
@Operation(summary = "提交申诉")
@PostMapping("/submit")
@Log(module = LogModuleEnum.CONTENT_AUDIT_APPEAL, value = ActionTypeEnum.INSERT)
public Result<Void> submitAppeal(@Valid @RequestBody AppealSubmitForm form) {
appealService.submitAppeal(form);
return Result.success();
}
}

54
src/main/java/com/youlai/boot/mini/controller/MiniFollowController.java

@ -0,0 +1,54 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.base.BaseQuery;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.vo.FollowUserVO;
import com.youlai.boot.mini.service.MiniFollowService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Tag(name = "用户关注相关接口")
@RestController
@RequestMapping("/api/v1/mini/follow")
@RequiredArgsConstructor
public class MiniFollowController {
private final MiniFollowService followService;
@Operation(summary = "关注/取关用户")
@PostMapping("/toggle")
public Result<Map<String, Object>> toggleFollow(@RequestParam String targetUserUuid) {
Long userId = SecurityUtils.getUserId();
boolean following = followService.toggleFollow(userId, targetUserUuid);
return Result.success(Map.of("following", following));
}
@Operation(summary = "检查是否已关注某用户")
@GetMapping("/isFollowing/{targetUserUuid}")
public Result<Map<String, Object>> isFollowing(@PathVariable String targetUserUuid) {
Long userId = SecurityUtils.getUserId();
boolean following = followService.isFollowingByUuid(userId, targetUserUuid);
return Result.success(Map.of("following", following));
}
@Operation(summary = "获取我的关注列表")
@GetMapping("/followingPage")
public PageResult<FollowUserVO> getFollowingPage(BaseQuery query) {
Long userId = SecurityUtils.getUserId();
return PageResult.success(followService.getFollowingPage(userId, query.getPageNum(), query.getPageSize()));
}
@Operation(summary = "获取我的粉丝列表")
@GetMapping("/followerPage")
public PageResult<FollowUserVO> getFollowerPage(BaseQuery query) {
Long userId = SecurityUtils.getUserId();
return PageResult.success(followService.getFollowerPage(userId, query.getPageNum(), query.getPageSize()));
}
}

34
src/main/java/com/youlai/boot/mini/controller/MiniReportController.java

@ -0,0 +1,34 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.mini.model.form.ReportSubmitForm;
import com.youlai.boot.mini.service.MiniReportService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "小程序端-举报相关接口")
@RestController
@RequestMapping("/api/v1/mini/report")
@RequiredArgsConstructor
public class MiniReportController {
private final MiniReportService reportService;
@Operation(summary = "提交举报")
@PostMapping("/submit")
@Log(module = LogModuleEnum.REPORT, value = ActionTypeEnum.INSERT)
public Result<Void> submitReport(@Valid @RequestBody ReportSubmitForm form) {
reportService.submitReport(form);
return Result.success();
}
}

2
src/main/java/com/youlai/boot/mini/controller/MiniUserController.java

@ -46,7 +46,7 @@ public class MiniUserController {
@Operation(summary = "修改当前登录用户基本信息")
@PostMapping(value = "/updateInfo")
@Log(module = LogModuleEnum.USER, value = ActionTypeEnum.UPDATE)
public Result<Void> updateCurrentUserInfo(@Valid MiniUserUpdateForm form) {
public Result<Void> updateCurrentUserInfo(@Valid @RequestBody MiniUserUpdateForm form) {
Long userId = SecurityUtils.getUserId();
miniUserService.updateCurrentUserInfo(userId, form);
return Result.success();

66
src/main/java/com/youlai/boot/mini/controller/StrayAnimalController.java

@ -10,8 +10,13 @@ import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.dto.DeleteStrayAnimalDTO;
import com.youlai.boot.mini.model.dto.DeleteStrayAnimalNoteMediaDTO;
import com.youlai.boot.mini.model.dto.EditVisibilityDTO;
import com.youlai.boot.mini.model.form.NoteCollectForm;
import com.youlai.boot.mini.model.form.NoteLikeForm;
import com.youlai.boot.mini.model.form.StrayAnimalForm;
import com.youlai.boot.mini.model.query.OwnStrayAnimalQuery;
import com.youlai.boot.mini.model.query.WaterfallQuery;
import com.youlai.boot.mini.model.vo.AdoptedAnimalVO;
import com.youlai.boot.mini.model.vo.WaterfallResult;
import com.youlai.boot.mini.model.vo.SaveStrayAnimalVO;
import com.youlai.boot.mini.model.vo.StrayAnimalDetailsVO;
import com.youlai.boot.mini.model.vo.StrayAnimalShortVO;
@ -21,11 +26,13 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
/**
* 流浪动物信息
@ -149,4 +156,63 @@ public class StrayAnimalController {
return PageResult.success(strayAnimalService.getOthersCreatedPage(authorUuid, queryParams));
}
@Operation(summary = "笔记点赞/取消点赞接口")
@PostMapping(value = "/note/like/toggle")
@PreAuthorize("isAuthenticated()")
public Result<Map<String, Object>> toggleNoteLike(
@Valid @RequestBody NoteLikeForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = strayAnimalService.toggleNoteLike(form, userId);
return Result.success(result);
}
@Operation(summary = "笔记收藏/取消收藏接口")
@PostMapping(value = "/note/collect/toggle")
@PreAuthorize("isAuthenticated()")
public Result<Map<String, Object>> toggleNoteCollect(
@Valid @RequestBody NoteCollectForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = strayAnimalService.toggleNoteCollect(form, userId);
return Result.success(result);
}
@Operation(summary = "获取流浪动物笔记瀑布流")
@GetMapping(value = "/note/waterfall")
public Result<WaterfallResult<StrayAnimalShortVO>> getWaterfall(
@Valid @RequestBody WaterfallQuery query
) {
Long userId = SecurityUtils.getUserId();
WaterfallResult<StrayAnimalShortVO> result = strayAnimalService.getWaterfall(query, userId);
return Result.success(result);
}
@Operation(summary = "重置当前用户的浏览记录过滤规则", description = "重置后瀑布流会重新展示所有看过的内容")
@PostMapping(value = "/resetViewBloom")
@Log(module = LogModuleEnum.STRAY_ANIMAL_INFO, value = ActionTypeEnum.UPDATE)
public Result<?> resetViewBloom() {
Long userId = SecurityUtils.getUserId();
strayAnimalService.resetCurrentUserBloom(userId);
return Result.success("重置成功");
}
@Operation(summary = "查询当前用户已领养的动物列表")
@GetMapping("/getMyAdoptedPage")
public PageResult<AdoptedAnimalVO> getMyAdoptedPage(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize
) {
return PageResult.success(strayAnimalService.getMyAdoptedPage(pageNum, pageSize));
}
@Operation(summary = "增加动物笔记浏览量")
@PostMapping(value = "/note/increase-view")
@Log(module = LogModuleEnum.STRAY_ANIMAL_INFO, value = ActionTypeEnum.UPDATE)
@RepeatSubmit(expire = 1)
public Result<?> increaseNoteViewCount(@RequestParam String noteUuid) {
strayAnimalService.increaseNoteViewCount(noteUuid);
return Result.success();
}
}

94
src/main/java/com/youlai/boot/mini/controller/StrayAnimalNoteCommentController.java

@ -0,0 +1,94 @@
package com.youlai.boot.mini.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.form.CommentLikeForm;
import com.youlai.boot.mini.model.form.DeleteStrayAnimalNoteCommentForm;
import com.youlai.boot.mini.model.form.StrayAnimalNoteCommentForm;
import com.youlai.boot.mini.model.query.AnimalNoteFirstLevelCommentQueryParam;
import com.youlai.boot.mini.model.query.AnimalNoteSecondLevelCommentQueryParam;
import com.youlai.boot.mini.model.vo.AnimalNoteFirstLevelCommentVO;
import com.youlai.boot.mini.model.vo.AnimalNoteSecondLevelCommentVO;
import com.youlai.boot.mini.model.vo.StrayAnimalNoteCommentVO;
import java.util.Map;
import com.youlai.boot.mini.service.StrayAnimalNoteCommentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@Tag(name = "流浪动物笔记评论相关接口")
@RestController
@RequestMapping("/api/v1/mini/noteComment")
@RequiredArgsConstructor
public class StrayAnimalNoteCommentController {
private final StrayAnimalNoteCommentService strayAnimalNoteCommentService;
@Operation(summary = "笔记评论接口")
@PostMapping(value = "add")
@RepeatSubmit
@Log(module = LogModuleEnum.STRAY_ANIMAL_NOTE_COMMENT, value = ActionTypeEnum.INSERT)
public Result<?> addNoteComment(
@Valid @RequestBody StrayAnimalNoteCommentForm formData
) {
StrayAnimalNoteCommentVO vo = strayAnimalNoteCommentService.addNoteComment(formData);
return Result.success(vo);
}
@Operation(summary = "删除笔记评论接口")
@PostMapping(value = "delete")
@PreAuthorize("isAuthenticated()")
@RepeatSubmit
@Log(module = LogModuleEnum.STRAY_ANIMAL_NOTE_COMMENT, value = ActionTypeEnum.DELETE)
public Result<?> deleteNoteComment(
@Valid @RequestBody DeleteStrayAnimalNoteCommentForm formData
) {
Long userId = SecurityUtils.getUserId();
strayAnimalNoteCommentService.deleteNoteComment(formData, userId);
return Result.success();
}
@Operation(summary = "分页查询笔记一级评论列表接口")
@GetMapping(value = "firstLevel/list")
@Log(module = LogModuleEnum.STRAY_ANIMAL_NOTE_COMMENT, value = ActionTypeEnum.LIST)
public Result<IPage<AnimalNoteFirstLevelCommentVO>> getFirstLevelCommentList(
@Valid AnimalNoteFirstLevelCommentQueryParam queryParam
) {
Long userId = SecurityUtils.getUserId();
IPage<AnimalNoteFirstLevelCommentVO> result = strayAnimalNoteCommentService.getFirstLevelCommentList(queryParam, userId);
return Result.success(result);
}
@Operation(summary = "分页查询笔记二级评论列表接口")
@GetMapping(value = "secondLevel/list")
@Log(module = LogModuleEnum.STRAY_ANIMAL_NOTE_COMMENT, value = ActionTypeEnum.LIST)
public Result<IPage<AnimalNoteSecondLevelCommentVO>> getSecondLevelCommentList(
@Valid AnimalNoteSecondLevelCommentQueryParam queryParam
) {
Long userId = SecurityUtils.getUserId();
IPage<AnimalNoteSecondLevelCommentVO> result = strayAnimalNoteCommentService.getSecondLevelCommentList(queryParam, userId);
return Result.success(result);
}
@Operation(summary = "评论点赞/取消点赞接口")
@PostMapping(value = "like/toggle")
@PreAuthorize("isAuthenticated()")
@RepeatSubmit(expire = 1)
@Log(module = LogModuleEnum.STRAY_ANIMAL_NOTE_COMMENT, value = ActionTypeEnum.UPDATE)
public Result<Map<String, Object>> toggleLike(
@Valid @RequestBody CommentLikeForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = strayAnimalNoteCommentService.toggleLike(form, userId);
return Result.success(result);
}
}

94
src/main/java/com/youlai/boot/mini/controller/UserPostCommentController.java

@ -0,0 +1,94 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.form.DeleteUserPostCommentForm;
import com.youlai.boot.mini.model.form.PostCommentLikeForm;
import com.youlai.boot.mini.model.form.UserPostCommentForm;
import com.youlai.boot.mini.model.query.PostFirstLevelCommentQueryParam;
import com.youlai.boot.mini.model.query.PostSecondLevelCommentQueryParam;
import com.youlai.boot.mini.model.vo.PostFirstLevelCommentVO;
import com.youlai.boot.mini.model.vo.PostSecondLevelCommentVO;
import com.youlai.boot.mini.model.vo.UserPostCommentVO;
import com.youlai.boot.mini.service.UserPostCommentService;
import java.util.Map;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@Tag(name = "用户作品评论的相关接口")
@RestController
@RequestMapping("/api/v1/mini/postComment")
@RequiredArgsConstructor
public class UserPostCommentController {
private final UserPostCommentService userPostCommentService;
@Operation(summary = "用户作品评论接口")
@PostMapping(value = "add")
@RepeatSubmit
@Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.INSERT)
public Result<?> addUserPostComment(
@Valid @RequestBody UserPostCommentForm formData
) {
UserPostCommentVO vo = userPostCommentService.addUserPostComment(formData);
return Result.success(vo);
}
@Operation(summary = "删除用户作品评论接口")
@PostMapping(value = "delete")
@PreAuthorize("isAuthenticated()")
@RepeatSubmit
@Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.DELETE)
public Result<?> deleteUserPostComment(
@Valid @RequestBody DeleteUserPostCommentForm formData
) {
Long userId = SecurityUtils.getUserId();
userPostCommentService.deleteUserPostComment(formData, userId);
return Result.success();
}
@Operation(summary = "分页查询用户作品一级评论列表接口")
@GetMapping(value = "firstLevel/list")
@Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.LIST)
public Result<IPage<PostFirstLevelCommentVO>> getFirstLevelCommentList(
@Valid PostFirstLevelCommentQueryParam queryParam
) {
Long userId = SecurityUtils.getUserId();
IPage<PostFirstLevelCommentVO> result = userPostCommentService.getFirstLevelCommentList(queryParam, userId);
return Result.success(result);
}
@Operation(summary = "分页查询用户作品二级评论列表接口")
@GetMapping(value = "secondLevel/list")
@Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.LIST)
public Result<IPage<PostSecondLevelCommentVO>> getSecondLevelCommentList(
@Valid PostSecondLevelCommentQueryParam queryParam
) {
Long userId = SecurityUtils.getUserId();
IPage<PostSecondLevelCommentVO> result = userPostCommentService.getSecondLevelCommentList(queryParam, userId);
return Result.success(result);
}
@Operation(summary = "评论点赞/取消点赞接口")
@PostMapping(value = "like/toggle")
@PreAuthorize("isAuthenticated()")
@RepeatSubmit(expire = 1)
@Log(module = LogModuleEnum.USER_POST_COMMENT, value = ActionTypeEnum.UPDATE)
public Result<Map<String, Object>> toggleLike(
@Valid @RequestBody PostCommentLikeForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = userPostCommentService.toggleLike(form, userId);
return Result.success(result);
}
}

187
src/main/java/com/youlai/boot/mini/controller/UserPostController.java

@ -0,0 +1,187 @@
package com.youlai.boot.mini.controller;
import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.ActionTypeEnum;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.mini.model.dto.DeleteUserPostDTO;
import com.youlai.boot.mini.model.dto.DeleteUserPostMediaDTO;
import com.youlai.boot.mini.model.dto.EditVisibilityDTO;
import com.youlai.boot.mini.model.form.UserPostCollectForm;
import com.youlai.boot.mini.model.form.UserPostForm;
import com.youlai.boot.mini.model.form.UserPostLikeForm;
import com.youlai.boot.mini.model.query.OwnUserPostQuery;
import com.youlai.boot.mini.model.query.UserPostWaterfallQuery;
import com.youlai.boot.mini.model.vo.UserPostDetailsVO;
import com.youlai.boot.mini.model.vo.UserPostVO;
import com.youlai.boot.mini.model.vo.WaterfallResult;
import com.youlai.boot.mini.service.UserPostService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
@Tag(name = "用户作品的相关接口")
@RestController
@RequestMapping("/api/v1/mini/userPost")
@RequiredArgsConstructor
public class UserPostController {
private final UserPostService userPostService;
@Operation(summary = "添加用户作品时先保存文件")
@PostMapping(value = "/save/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.INSERT)
public Result<List<String>> saveFile(
@RequestPart(name = "images", required = false) List<MultipartFile> images,
@RequestPart(name = "videos", required = false) List<MultipartFile> videos
) {
List<String> urlList = userPostService.saveFile(images, videos);
return Result.success(urlList);
}
@Operation(summary = "添加用户作品信息")
@PostMapping(value = "/save")
@RepeatSubmit
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.INSERT)
public Result<String> saveUserPost(@Valid @RequestBody UserPostForm formData) {
String postUuid = userPostService.saveUserPost(formData);
return Result.success(postUuid);
}
@Operation(summary = "编辑用户作品(不包含图片/视频媒体资源)")
@PostMapping(value = "/update/{postUuid}")
@RepeatSubmit
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.UPDATE)
public Result<Void> updateUserPost(
@PathVariable String postUuid,
@Valid @RequestBody UserPostForm formData
) {
userPostService.updateUserPost(postUuid, formData);
return Result.success();
}
@Operation(summary = "编辑用户作品时,删除媒体资源")
@PostMapping(value = "/update/deleteMediaSource")
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.DELETE)
public Result<Void> deleteMediaSource(@Valid @RequestBody DeleteUserPostMediaDTO dto) {
userPostService.deleteMediaSource(dto);
return Result.success();
}
@Operation(summary = "编辑用户作品时,添加媒体资源", description = "比如补充图片、补充视频")
@PostMapping(value = "/update/saveMediaSource/{postUuid}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RepeatSubmit
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.UPDATE)
public Result<Void> saveMediaSource(
@PathVariable String postUuid,
@RequestPart(name = "images", required = false) List<MultipartFile> images,
@RequestPart(name = "videos", required = false) List<MultipartFile> videos
) {
userPostService.saveMediaSource(postUuid, images, videos);
return Result.success();
}
@Operation(summary = "编辑用户作品可见范围")
@PostMapping(value = "/update/visibility/{postUuid}")
@RepeatSubmit(expire = 1)
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.UPDATE)
public Result<Void> updateVisibility(
@PathVariable String postUuid,
@Valid @RequestBody EditVisibilityDTO dto
) {
userPostService.updateVisibility(postUuid, dto);
return Result.success();
}
@Operation(summary = "删除用户作品")
@PostMapping(value = "/delete")
@RepeatSubmit
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.DELETE)
public Result<Void> delete(@Valid @RequestBody DeleteUserPostDTO dto) {
userPostService.delete(dto);
return Result.success();
}
@Operation(summary = "获取自己创建的作品列表")
@GetMapping(value = "/getSelfCreatedPage")
public PageResult<UserPostVO> getSelfCreatedPage(OwnUserPostQuery queryParams) {
return PageResult.success(userPostService.getSelfCreatedPage(queryParams));
}
@Operation(summary = "获取用户作品详情")
@RequestMapping(value = "/getDetails/{postUuid}", method = RequestMethod.GET)
public Result<UserPostDetailsVO> getDetails(@PathVariable String postUuid) {
Long userId = SecurityUtils.getUserId();
return Result.success(userPostService.getDetails(postUuid, userId));
}
@Operation(summary = "获取某个用户创建的作品列表")
@GetMapping(value = "/getOthersCreatedPage/{authorUuid}")
public PageResult<UserPostVO> getOthersCreatedPage(
@PathVariable String authorUuid,
OwnUserPostQuery queryParams
) {
return PageResult.success(userPostService.getOthersCreatedPage(authorUuid, queryParams));
}
@Operation(summary = "用户作品点赞/取消点赞接口")
@PostMapping(value = "/like/toggle")
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.UPDATE)
public Result<Map<String, Object>> toggleUserPostLike(
@Valid @RequestBody UserPostLikeForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = userPostService.toggleUserPostLike(form, userId);
return Result.success(result);
}
@Operation(summary = "用户作品收藏/取消收藏接口")
@PostMapping(value = "/collect/toggle")
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.UPDATE)
public Result<Map<String, Object>> toggleUserPostCollect(
@Valid @RequestBody UserPostCollectForm form
) {
Long userId = SecurityUtils.getUserId();
Map<String, Object> result = userPostService.toggleUserPostCollect(form, userId);
return Result.success(result);
}
@Operation(summary = "获取用户作品瀑布流")
@GetMapping(value = "/waterfall")
public Result<WaterfallResult<UserPostVO>> getWaterfall(
@Valid UserPostWaterfallQuery query
) {
Long userId = SecurityUtils.getUserId();
WaterfallResult<UserPostVO> result = userPostService.getWaterfall(query, userId);
return Result.success(result);
}
@Operation(summary = "重置当前用户的浏览记录过滤规则", description = "重置后瀑布流会重新展示所有看过的内容")
@PostMapping(value = "/resetViewBloom")
public Result<?> resetViewBloom() {
Long userId = SecurityUtils.getUserId();
userPostService.resetCurrentUserBloom(userId);
return Result.success("重置成功");
}
@Operation(summary = "增加用户作品浏览量")
@PostMapping(value = "/increase-view")
@Log(module = LogModuleEnum.USER_POST_INFO, value = ActionTypeEnum.UPDATE)
@RepeatSubmit(expire = 1)
public Result<?> increaseViewCount(@RequestParam String postUuid) {
userPostService.increaseUserPostViewCount(postUuid);
return Result.success();
}
}

28
src/main/java/com/youlai/boot/mini/job/AiTaskTimeoutJob.java

@ -0,0 +1,28 @@
package com.youlai.boot.mini.job;
import com.youlai.boot.mini.service.AiGenerationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RequiredArgsConstructor
public class AiTaskTimeoutJob {
private final AiGenerationService aiGenerationService;
/**
* 每10分钟扫描一次超时的AI生成任务标记为超时并退还积分
*/
@Scheduled(cron = "0 */10 * * * ?")
public void execute() {
log.info("开始扫描超时AI生成任务");
try {
aiGenerationService.handleTimeoutTasks();
} catch (Exception e) {
log.error("扫描超时AI生成任务异常", e);
}
}
}

108
src/main/java/com/youlai/boot/mini/job/NoteStatsCalibrateJob.java

@ -0,0 +1,108 @@
package com.youlai.boot.mini.job;
import com.youlai.boot.mini.mapper.MiniStrayAnimalNoteCommentMapper;
import com.youlai.boot.mini.mapper.MiniStrayAnimalNoteMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* 动物笔记统计数据校准定时任务
* <p>
* 定时校准评论数点赞数收藏数防止并发操作导致计数不准确
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class NoteStatsCalibrateJob {
private final MiniStrayAnimalNoteCommentMapper miniStrayAnimalNoteCommentMapper;
private final MiniStrayAnimalNoteMapper miniStrayAnimalNoteMapper;
/**
* 校准所有统计数据
* 每天凌晨 00:00:00 执行
*/
@Scheduled(cron = "0 0 0 * * ?")
// @Scheduled(cron = "0 */1 * * * ?")
@Transactional
public void calibrateAllStats() {
log.info("========== 开始校准动物笔记统计数据 ==========");
long totalStartTime = System.currentTimeMillis();
calibrateCommentCounts();
calibrateCommentLikeCounts();
calibrateNoteLikeCounts();
calibrateCollectCounts();
long totalCostTime = System.currentTimeMillis() - totalStartTime;
log.info("========== 全部统计数据校准完成,总耗时 {} ms ==========", totalCostTime);
}
/**
* 校准笔记评论数
*/
private void calibrateCommentCounts() {
log.info("开始校准动物笔记评论数...");
long startTime = System.currentTimeMillis();
try {
int updatedCount = miniStrayAnimalNoteCommentMapper.batchCalibrateAllCommentCounts();
long costTime = System.currentTimeMillis() - startTime;
log.info("校准评论数完成,共更新 {} 条笔记,耗时 {} ms", updatedCount, costTime);
} catch (Exception e) {
log.error("校准评论数失败", e);
}
}
/**
* 校准评论点赞数
*/
private void calibrateCommentLikeCounts() {
log.info("开始校准评论点赞数...");
long startTime = System.currentTimeMillis();
try {
int updatedCount = miniStrayAnimalNoteCommentMapper.batchCalibrateAllCommentLikeCounts();
long costTime = System.currentTimeMillis() - startTime;
log.info("校准评论点赞数完成,共更新 {} 条评论,耗时 {} ms", updatedCount, costTime);
} catch (Exception e) {
log.error("校准评论点赞数失败", e);
}
}
/**
* 校准笔记点赞数
*/
private void calibrateNoteLikeCounts() {
log.info("开始校准动物笔记点赞数...");
long startTime = System.currentTimeMillis();
try {
int updatedCount = miniStrayAnimalNoteMapper.batchCalibrateAllLikeCounts();
long costTime = System.currentTimeMillis() - startTime;
log.info("校准点赞数完成,共更新 {} 条笔记,耗时 {} ms", updatedCount, costTime);
} catch (Exception e) {
log.error("校准点赞数失败", e);
}
}
/**
* 校准笔记收藏数
*/
private void calibrateCollectCounts() {
log.info("开始校准动物笔记收藏数...");
long startTime = System.currentTimeMillis();
try {
int updatedCount = miniStrayAnimalNoteMapper.batchCalibrateAllCollectCounts();
long costTime = System.currentTimeMillis() - startTime;
log.info("校准收藏数完成,共更新 {} 条笔记,耗时 {} ms", updatedCount, costTime);
} catch (Exception e) {
log.error("校准收藏数失败", e);
}
}
}

16
src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionApplicationMapper.java

@ -0,0 +1,16 @@
package com.youlai.boot.mini.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.mini.model.entity.MiniAdoptionApplication;
import com.youlai.boot.mini.model.query.AdoptionApplicationQuery;
import com.youlai.boot.mini.model.vo.AdoptionApplicationVO;
import org.apache.ibatis.annotations.Param;
public interface MiniAdoptionApplicationMapper extends BaseMapper<MiniAdoptionApplication> {
IPage<AdoptionApplicationVO> getApplicationPage(Page<AdoptionApplicationVO> page, @Param("query") AdoptionApplicationQuery query);
AdoptionApplicationVO getApplicationDetail(@Param("applicationUuid") String applicationUuid);
}

20
src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryCollectMapper.java

@ -0,0 +1,20 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAdoptionDiaryCollect;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 领养日记收藏表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAdoptionDiaryCollectMapper extends BaseMapper<MiniAdoptionDiaryCollect> {
Integer selectUserCollectCount(Long diaryId, Long userId);
void insertOrUpdateCollect(MiniAdoptionDiaryCollect collect);
void deleteCollect(@Param("diaryId") Long diaryId, @Param("userId") Long userId, @Param("currentTime") long currentTime);
}

30
src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryCommentLikeMapper.java

@ -0,0 +1,30 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAdoptionDiaryCommentLike;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 领养日记评论点赞表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAdoptionDiaryCommentLikeMapper extends BaseMapper<MiniAdoptionDiaryCommentLike> {
/**
* 查询用户是否点赞该评论
*/
Integer selectUserLikeCount(@Param("commentId") Long commentId, @Param("userId") Long userId);
/**
* 新增或更新点赞记录原子操作
*/
int insertOrUpdateLike(MiniAdoptionDiaryCommentLike like);
/**
* 逻辑删除点赞记录取消点赞
*/
int deleteLike(@Param("commentId") Long commentId, @Param("userId") Long userId, @Param("currentTime") Long currentTime);
}

83
src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryCommentMapper.java

@ -0,0 +1,83 @@
package com.youlai.boot.mini.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.entity.MiniAdoptionDiaryComment;
import com.youlai.boot.mini.model.entity.MiniAdoptionDiaryCommentLike;
import com.youlai.boot.mini.model.query.DiaryFirstLevelCommentQueryParam;
import com.youlai.boot.mini.model.query.DiarySecondLevelCommentQueryParam;
import com.youlai.boot.mini.model.vo.DiaryFirstLevelCommentVO;
import com.youlai.boot.mini.model.vo.DiarySecondLevelCommentVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 领养日记评论表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAdoptionDiaryCommentMapper extends BaseMapper<MiniAdoptionDiaryComment> {
/**
* 根据UUID查询评论ID
*/
Long selectIdByUuid(String uuid);
/**
* 根据UUID查询父评论必要字段
*/
MiniAdoptionDiaryComment selectParentCommentInfoByUuid(String uuid);
/**
* 根据ID查询评论UUID
*/
String selectUuidById(Long id);
/**
* 原子增加日记评论数
*/
int incrementCommentCount(Long diaryId);
/**
* 原子减少日记评论数
*/
int decrementCommentCount(@Param("diaryId") Long diaryId);
/**
* 带权限校验的原子删除评论
*/
int deleteCommentWithPermissionCheck(@Param("diaryUuid") String diaryUuid, @Param("commentUuid") String commentUuid, @Param("userId") Long userId);
/**
* 分页查询领养日记一级评论列表
*/
IPage<DiaryFirstLevelCommentVO> getFirstLevelComment(IPage<DiaryFirstLevelCommentVO> page, @Param("query") DiaryFirstLevelCommentQueryParam query);
/**
* 批量查询用户对指定评论的点赞状态
*/
List<MiniAdoptionDiaryCommentLike> batchGetUserCommentLikes(@Param("commentIds") List<Long> commentIds, @Param("userId") Long userId);
/**
* 分页查询领养日记二级评论列表
*/
IPage<DiarySecondLevelCommentVO> getSecondLevelComment(IPage<DiarySecondLevelCommentVO> page, @Param("query") DiarySecondLevelCommentQueryParam query);
/**
* 原子增加评论点赞数
*/
int incrementLikeCount(@Param("commentId") Long commentId);
/**
* 原子减少评论点赞数
*/
int decrementLikeCount(@Param("commentId") Long commentId);
/**
* 查询评论点赞数
*/
Long selectLikeCount(@Param("commentId") Long commentId);
}

20
src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryLikeMapper.java

@ -0,0 +1,20 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAdoptionDiaryLike;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 领养日记点赞表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAdoptionDiaryLikeMapper extends BaseMapper<MiniAdoptionDiaryLike> {
Integer selectUserLikeCount(@Param("diaryId") Long diaryId, @Param("userId") Long userId);
void insertOrUpdateLike(MiniAdoptionDiaryLike like);
void deleteLike(@Param("diaryId") Long diaryId, @Param("userId") Long userId, @Param("currentTime") long currentTime);
}

46
src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryMapper.java

@ -0,0 +1,46 @@
package com.youlai.boot.mini.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.mini.model.entity.MiniAdoptionDiary;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.query.OwnAdoptionDiaryQuery;
import com.youlai.boot.mini.model.vo.AdoptionDiaryDetailsVO;
import com.youlai.boot.mini.model.vo.AdoptionDiaryVO;
import com.youlai.boot.mini.model.vo.StrayAnimalShortVO;
import jakarta.validation.constraints.NotBlank;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 领养日记表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAdoptionDiaryMapper extends BaseMapper<MiniAdoptionDiary> {
/**
* 分页查询领养日记列表
*/
IPage<AdoptionDiaryVO> getDiaryPage(Page<AdoptionDiaryVO> page, @Param("query") OwnAdoptionDiaryQuery query);
Long selectIdByUuid(String diaryUuid);
void incrementLikeCount(@Param("diaryId") Long diaryId);
void decrementLikeCount(@Param("diaryId") Long diaryId);
Long selectLikeCount(@Param("diaryId") Long diaryId);
void incrementCollectCount(@Param("diaryId") Long diaryId);
void decrementCollectCount(@Param("diaryId") Long diaryId);
Long selectCollectCount(Long diaryId);
List<AdoptionDiaryVO> getWaterfall(@Param("cursor") Long cursor, @Param("pageSize") int pageSize, @Param("miniUserId") Long miniUserId);
void increaseDiaryViewCount(String diaryUuid);
}

20
src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryMediaMapper.java

@ -0,0 +1,20 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAdoptionDiaryMedia;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.vo.MiniAdoptionDiaryMediaVO;
import java.util.List;
import java.util.Map;
/**
* 领养日记资源表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAdoptionDiaryMediaMapper extends BaseMapper<MiniAdoptionDiaryMedia> {
List<MiniAdoptionDiaryMediaVO> getMediaByDiaryIdAndType(Map<String, Object> param);
}

21
src/main/java/com/youlai/boot/mini/mapper/MiniAdoptionDiaryViewMapper.java

@ -0,0 +1,21 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAdoptionDiaryView;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 领养日记浏览记录表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAdoptionDiaryViewMapper extends BaseMapper<MiniAdoptionDiaryView> {
void insertOrUpdateView(MiniAdoptionDiaryView view);
List<Long> selectViewedDiaryIdsByUserAndTime(@Param("userId") Long userId, @Param("startTime") Long startTime);
}

14
src/main/java/com/youlai/boot/mini/mapper/MiniAiGenerationTaskMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAiGenerationTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* AI生成任务统一表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAiGenerationTaskMapper extends BaseMapper<MiniAiGenerationTask> {
}

14
src/main/java/com/youlai/boot/mini/mapper/MiniAiTaskMediaMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniAiTaskMedia;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* AI任务媒体文件表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniAiTaskMediaMapper extends BaseMapper<MiniAiTaskMedia> {
}

14
src/main/java/com/youlai/boot/mini/mapper/MiniContentAuditAppealMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniContentAuditAppeal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 内容申诉表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniContentAuditAppealMapper extends BaseMapper<MiniContentAuditAppeal> {
}

14
src/main/java/com/youlai/boot/mini/mapper/MiniReportMapper.java

@ -0,0 +1,14 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniReport;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 举报记录表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniReportMapper extends BaseMapper<MiniReport> {
}

8
src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalMapper.java

@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.query.OwnStrayAnimalQuery;
import com.youlai.boot.mini.model.vo.StrayAnimalDetailsVO;
import com.youlai.boot.mini.model.vo.StrayAnimalNearbyVO;
import com.youlai.boot.mini.model.vo.AdoptedAnimalVO;
import com.youlai.boot.mini.model.vo.StrayAnimalShortVO;
import org.apache.ibatis.annotations.Param;
@ -30,4 +31,11 @@ public interface MiniStrayAnimalMapper extends BaseMapper<MiniStrayAnimal> {
StrayAnimalDetailsVO getStrayAnimalDetails(@Param("animalUuid") String animalUuid, @Param("miniUserId") Long miniUserId);
List<StrayAnimalNearbyVO> listByMapBounds(MapSearchDTO mapSearch);
List<StrayAnimalShortVO> getWaterfall(@Param("cursor") Long cursor, @Param("pageSize") Integer pageSize, @Param("animalType") String animalType, @Param("miniUserId") Long miniUserId);
Long selectIdByUuid(@Param("strayAnimalUuId") String strayAnimalUuId);
IPage<AdoptedAnimalVO> getMyAdoptedPage(Page<AdoptedAnimalVO> page, @Param("userId") Long userId);
}

30
src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteCollectMapper.java

@ -0,0 +1,30 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniStrayAnimalNoteCollect;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 用户收藏表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniStrayAnimalNoteCollectMapper extends BaseMapper<MiniStrayAnimalNoteCollect> {
/**
* 查询用户是否收藏该笔记
*/
Integer selectUserCollectCount(@Param("noteId") Long noteId, @Param("userId") Long userId);
/**
* 新增或更新收藏记录原子操作
*/
int insertOrUpdateCollect(MiniStrayAnimalNoteCollect collect);
/**
* 逻辑删除收藏记录取消收藏
*/
int deleteCollect(@Param("noteId") Long noteId, @Param("userId") Long userId, @Param("currentTime") Long currentTime);
}

30
src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteCommentLikeMapper.java

@ -0,0 +1,30 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniStrayAnimalNoteCommentLike;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 流浪动物笔记评论点赞表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniStrayAnimalNoteCommentLikeMapper extends BaseMapper<MiniStrayAnimalNoteCommentLike> {
/**
* 查询用户是否点赞该评论
*/
Integer selectUserLikeCount(@Param("commentId") Long commentId, @Param("userId") Long userId);
/**
* 新增或更新点赞记录原子操作
*/
int insertOrUpdateLike(MiniStrayAnimalNoteCommentLike like);
/**
* 逻辑删除点赞记录取消点赞
*/
int deleteLike(@Param("commentId") Long commentId, @Param("userId") Long userId, @Param("currentTime") Long currentTime);
}

62
src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteCommentMapper.java

@ -0,0 +1,62 @@
package com.youlai.boot.mini.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.entity.MiniStrayAnimalNoteComment;
import com.youlai.boot.mini.model.entity.MiniStrayAnimalNoteCommentLike;
import com.youlai.boot.mini.model.query.AnimalNoteFirstLevelCommentQueryParam;
import com.youlai.boot.mini.model.query.AnimalNoteSecondLevelCommentQueryParam;
import com.youlai.boot.mini.model.vo.AnimalNoteFirstLevelCommentVO;
import com.youlai.boot.mini.model.vo.AnimalNoteSecondLevelCommentVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 流浪动物笔记评论表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniStrayAnimalNoteCommentMapper extends BaseMapper<MiniStrayAnimalNoteComment> {
int incrementCommentCount(Long strayAnimalNoteId);
int batchCalibrateAllCommentCounts();
Long selectIdByUuid(@Param("uuid") String uuid);
MiniStrayAnimalNoteComment selectParentCommentInfoByUuid(@Param("uuid") String uuid);
String selectUuidById(@Param("id") Long id);
int deleteCommentWithPermissionCheck(@Param("noteUuid") String noteUuid, @Param("commentUuid") String commentUuid, @Param("userId") Long userId);
int decrementCommentCount(@Param("noteId") Long noteId);
IPage<AnimalNoteFirstLevelCommentVO> getFirstLevelComment(IPage<AnimalNoteFirstLevelCommentVO> page, @Param("query") AnimalNoteFirstLevelCommentQueryParam query);
IPage<AnimalNoteSecondLevelCommentVO> getSecondLevelComment(IPage<AnimalNoteSecondLevelCommentVO> page, @Param("query") AnimalNoteSecondLevelCommentQueryParam query);
List<MiniStrayAnimalNoteCommentLike> batchGetUserCommentLikes(@Param("noteCommentIds") List<Long> noteCommentIds, @Param("appUserId") Long appUserId);
/**
* 原子增加评论点赞数
*/
int incrementLikeCount(@Param("commentId") Long commentId);
/**
* 原子减少评论点赞数
*/
int decrementLikeCount(@Param("commentId") Long commentId);
/**
* 查询评论点赞数
*/
Long selectLikeCount(@Param("commentId") Long commentId);
/**
* 批量校准所有评论的点赞数
*/
int batchCalibrateAllCommentLikeCounts();
}

30
src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteLikeMapper.java

@ -0,0 +1,30 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniStrayAnimalNoteLike;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 流浪动物笔记点赞表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniStrayAnimalNoteLikeMapper extends BaseMapper<MiniStrayAnimalNoteLike> {
/**
* 查询用户是否点赞该笔记
*/
Integer selectUserLikeCount(@Param("noteId") Long noteId, @Param("userId") Long userId);
/**
* 新增或更新点赞记录原子操作
*/
int insertOrUpdateLike(MiniStrayAnimalNoteLike like);
/**
* 逻辑删除点赞记录取消点赞
*/
int deleteLike(@Param("noteId") Long noteId, @Param("userId") Long userId, @Param("currentTime") Long currentTime);
}

53
src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteMapper.java

@ -1,7 +1,8 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniStrayAnimalNote;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.entity.MiniStrayAnimalNote;
import org.apache.ibatis.annotations.Param;
/**
* 流浪信息笔记 Mapper 接口
@ -11,4 +12,54 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/
public interface MiniStrayAnimalNoteMapper extends BaseMapper<MiniStrayAnimalNote> {
/**
* 根据UUID查询笔记ID
*/
Long selectIdByUuid(@Param("uuid") String uuid);
/**
* 原子增加笔记点赞数
*/
int incrementLikeCount(@Param("noteId") Long noteId);
/**
* 原子减少笔记点赞数
*/
int decrementLikeCount(@Param("noteId") Long noteId);
/**
* 查询笔记点赞数
*/
Long selectLikeCount(@Param("noteId") Long noteId);
/**
* 原子增加笔记收藏数
*/
int incrementCollectCount(@Param("noteId") Long noteId);
/**
* 原子减少笔记收藏数
*/
int decrementCollectCount(@Param("noteId") Long noteId);
/**
* 查询笔记收藏数
*/
Long selectCollectCount(@Param("noteId") Long noteId);
/**
* 批量校准所有笔记的点赞数
*/
int batchCalibrateAllLikeCounts();
/**
* 批量校准所有笔记的收藏数
*/
int batchCalibrateAllCollectCounts();
/**
* 原子增加笔记浏览量
*/
int incrementViewCount(@Param("noteUuid") String noteUuid);
}

27
src/main/java/com/youlai/boot/mini/mapper/MiniStrayAnimalNoteViewMapper.java

@ -0,0 +1,27 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniStrayAnimalNoteView;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 动物笔记浏览记录表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniStrayAnimalNoteViewMapper extends BaseMapper<MiniStrayAnimalNoteView> {
/**
* 新增或更新浏览记录重复则更新最后浏览时间
*/
int insertOrUpdateView(MiniStrayAnimalNoteView view);
/**
* 查询指定用户的浏览过的笔记ID
*/
List<Long> selectViewedNoteIdsByUserAndTime(@Param("userId") Long userId, @Param("startTime") Long startTime);
}

28
src/main/java/com/youlai/boot/mini/mapper/MiniUserFollowMapper.java

@ -0,0 +1,28 @@
package com.youlai.boot.mini.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.mini.model.entity.MiniUserFollow;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.vo.FollowUserVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户关注表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserFollowMapper extends BaseMapper<MiniUserFollow> {
List<Long> selectFollowingIds(@Param("userId") Long userId);
List<Long> selectFollowerIds(@Param("userId") Long userId);
IPage<FollowUserVO> selectFollowingPage(Page<FollowUserVO> page, @Param("userId") Long userId);
IPage<FollowUserVO> selectFollowerPage(Page<FollowUserVO> page, @Param("userId") Long userId);
}

20
src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCollectMapper.java

@ -0,0 +1,20 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniUserPostCollect;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 用户作品收藏表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserPostCollectMapper extends BaseMapper<MiniUserPostCollect> {
Integer selectUserCollectCount(@Param("postId") Long postId, @Param("userId") Long userId);
void insertOrUpdateCollect(MiniUserPostCollect collect);
void deleteCollect(@Param("postId") Long postId, @Param("userId") Long userId, @Param("currentTime") long currentTime);
}

21
src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentLikeMapper.java

@ -0,0 +1,21 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniUserPostCommentLike;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 用户作品评论点赞表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserPostCommentLikeMapper extends BaseMapper<MiniUserPostCommentLike> {
Integer selectUserLikeCount(@Param("commentId") Long commentId, @Param("userId") Long userId);
int insertOrUpdateLike(MiniUserPostCommentLike like);
int deleteLike(@Param("commentId") Long commentId, @Param("userId") Long userId, @Param("currentTime") Long currentTime);
}

48
src/main/java/com/youlai/boot/mini/mapper/MiniUserPostCommentMapper.java

@ -0,0 +1,48 @@
package com.youlai.boot.mini.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.entity.MiniUserPostComment;
import com.youlai.boot.mini.model.entity.MiniUserPostCommentLike;
import com.youlai.boot.mini.model.query.PostFirstLevelCommentQueryParam;
import com.youlai.boot.mini.model.query.PostSecondLevelCommentQueryParam;
import com.youlai.boot.mini.model.vo.PostFirstLevelCommentVO;
import com.youlai.boot.mini.model.vo.PostSecondLevelCommentVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户作品评论表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserPostCommentMapper extends BaseMapper<MiniUserPostComment> {
Long selectIdByUuid(String uuid);
MiniUserPostComment selectParentCommentInfoByUuid(String uuid);
String selectUuidById(Long id);
int incrementCommentCount(Long postId);
int decrementCommentCount(Long postId);
int deleteCommentWithPermissionCheck(@Param("postUuid") String postUuid, @Param("commentUuid") String commentUuid, @Param("userId") Long userId);
IPage<PostFirstLevelCommentVO> getFirstLevelComment(IPage<PostFirstLevelCommentVO> page, @Param("query") PostFirstLevelCommentQueryParam query);
List<MiniUserPostCommentLike> batchGetUserCommentLikes(@Param("commentIds") List<Long> commentIds, @Param("userId") Long userId);
IPage<PostSecondLevelCommentVO> getSecondLevelComment(IPage<PostSecondLevelCommentVO> page, @Param("query") PostSecondLevelCommentQueryParam query);
int incrementLikeCount(@Param("commentId") Long commentId);
int decrementLikeCount(@Param("commentId") Long commentId);
Long selectLikeCount(@Param("commentId") Long commentId);
}

20
src/main/java/com/youlai/boot/mini/mapper/MiniUserPostLikeMapper.java

@ -0,0 +1,20 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniUserPostLike;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
/**
* 用户作品点赞表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserPostLikeMapper extends BaseMapper<MiniUserPostLike> {
Integer selectUserLikeCount(@Param("postId") Long postId, @Param("userId") Long userId);
void insertOrUpdateLike(MiniUserPostLike like);
void deleteLike(@Param("postId") Long postId, @Param("userId") Long userId, @Param("currentTime") long currentTime);
}

41
src/main/java/com/youlai/boot/mini/mapper/MiniUserPostMapper.java

@ -0,0 +1,41 @@
package com.youlai.boot.mini.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.boot.mini.model.entity.MiniUserPost;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.query.OwnUserPostQuery;
import com.youlai.boot.mini.model.vo.UserPostVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户作品表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserPostMapper extends BaseMapper<MiniUserPost> {
IPage<UserPostVO> getUserPostPage(Page<UserPostVO> page, @Param("query") OwnUserPostQuery query);
Long selectIdByUuid(String postUuid);
void incrementLikeCount(@Param("postId") Long postId);
void decrementLikeCount(@Param("postId") Long postId);
Long selectLikeCount(@Param("postId") Long postId);
void incrementCollectCount(@Param("postId") Long postId);
void decrementCollectCount(@Param("postId") Long postId);
Long selectCollectCount(@Param("postId") Long postId);
List<UserPostVO> getWaterfall(@Param("cursor") Long cursor, @Param("pageSize") int pageSize, @Param("miniUserId") Long miniUserId);
void incrementViewCount(String postUuid);
}

20
src/main/java/com/youlai/boot/mini/mapper/MiniUserPostMediaMapper.java

@ -0,0 +1,20 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniUserPostMedia;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.mini.model.vo.UserPostMediaVO;
import java.util.List;
import java.util.Map;
/**
* 用户作品资源表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserPostMediaMapper extends BaseMapper<MiniUserPostMedia> {
List<UserPostMediaVO> getMediaByPostIdAndType(Map<String, Object> param);
}

21
src/main/java/com/youlai/boot/mini/mapper/MiniUserPostViewMapper.java

@ -0,0 +1,21 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniUserPostView;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户作品浏览记录表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserPostViewMapper extends BaseMapper<MiniUserPostView> {
List<Long> selectViewedPostIdsByUserAndTime(@Param("userId") Long userId, @Param("startTime") Long startTime);
int insertOrUpdateView(MiniUserPostView view);
}

17
src/main/java/com/youlai/boot/mini/mapper/MiniUserSubscribeMapper.java

@ -0,0 +1,17 @@
package com.youlai.boot.mini.mapper;
import com.youlai.boot.mini.model.entity.MiniUserSubscribe;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
/**
* 用户订阅状态表 Mapper 接口
*
* @author jwy
* @since
*/
public interface MiniUserSubscribeMapper extends BaseMapper<MiniUserSubscribe> {
String getOpenidByUserId(Long aLong);
}

28
src/main/java/com/youlai/boot/mini/model/dto/DeleteAdoptionDiaryDTO.java

@ -0,0 +1,28 @@
package com.youlai.boot.mini.model.dto;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Data
public class DeleteAdoptionDiaryDTO {
@NotEmpty(message = "uuid不能为空")
@ArraySchema(
arraySchema = @Schema(
description = "领养日记uuid列表",
example = "[\"uuid1\",\"uuid2\"]",
requiredMode = Schema.RequiredMode.REQUIRED
),
schema = @Schema(
description = "领养日记uuid",
example = "uuid1"
)
)
private List<@NotBlank(message = "uuid不能为空") String> diaryUuidList;
}

36
src/main/java/com/youlai/boot/mini/model/dto/DeleteAdoptionDiaryMediaDTO.java

@ -0,0 +1,36 @@
package com.youlai.boot.mini.model.dto;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Data
public class DeleteAdoptionDiaryMediaDTO {
@NotEmpty(message = "媒体资源uuid不能为空")
@ArraySchema(
arraySchema = @Schema(
description = "领养日记媒体资源uuid列表",
example = "[\"uuid1\",\"uuid2\"]",
requiredMode = Schema.RequiredMode.REQUIRED
),
schema = @Schema(
description = "媒体资源uuid",
example = "uuid1"
)
)
private List<@NotBlank(message = "uuid不能为空") String> diaryMediaUuidList;
@NotBlank(message = "领养日记uuid不能为空")
@Schema(
description = "领养日记uuid",
example = "2d7890db8a6e47016ccef2d679d5a2c8",
requiredMode = Schema.RequiredMode.REQUIRED
)
private String diaryUuid;
}

20
src/main/java/com/youlai/boot/mini/model/dto/DeleteUserPostDTO.java

@ -0,0 +1,20 @@
package com.youlai.boot.mini.model.dto;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Data
public class DeleteUserPostDTO {
@NotEmpty(message = "uuid不能为空")
@ArraySchema(
arraySchema = @Schema(description = "用户作品uuid列表", requiredMode = Schema.RequiredMode.REQUIRED),
schema = @Schema(description = "用户作品uuid")
)
private List<@NotBlank(message = "uuid不能为空") String> postUuidList;
}

24
src/main/java/com/youlai/boot/mini/model/dto/DeleteUserPostMediaDTO.java

@ -0,0 +1,24 @@
package com.youlai.boot.mini.model.dto;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Data
public class DeleteUserPostMediaDTO {
@NotEmpty(message = "媒体资源uuid不能为空")
@ArraySchema(
arraySchema = @Schema(description = "用户作品媒体资源uuid列表", requiredMode = Schema.RequiredMode.REQUIRED),
schema = @Schema(description = "媒体资源uuid")
)
private List<@NotBlank(message = "uuid不能为空") String> mediaUuidList;
@NotBlank(message = "作品uuid不能为空")
@Schema(description = "用户作品uuid", requiredMode = Schema.RequiredMode.REQUIRED)
private String postUuid;
}

31
src/main/java/com/youlai/boot/mini/model/dto/GenerateResponse.java

@ -0,0 +1,31 @@
package com.youlai.boot.mini.model.dto;
import lombok.Data;
/**
* AI生成接口响应DTO
*
* @author youlai
*/
@Data
public class GenerateResponse {
/**
* 响应消息
*/
private String msg;
/**
* 响应码0表示成功
*/
private Integer code;
/**
* 生成结果数据
*/
private GenerationData data;
/**
* 请求ID
*/
private String request_id;
}

48
src/main/java/com/youlai/boot/mini/model/dto/GenerationData.java

@ -0,0 +1,48 @@
package com.youlai.boot.mini.model.dto;
import lombok.Data;
import java.util.List;
/**
* AI生成结果数据DTO
*
* @author youlai
*/
@Data
public class GenerationData {
/**
* 使用的模型ID
*/
private String model;
/**
* 生成的图片列表
*/
private List<ImageData> data;
/**
* 错误信息无错误则为null
*/
private Object error;
/**
* Token使用统计
*/
private Object usage;
/**
* 创建时间戳
*/
private Long created_at;
/**
* 使用的工具
*/
private String tool;
/**
* 创建时间戳
*/
private Long created;
}

26
src/main/java/com/youlai/boot/mini/model/dto/ImageData.java

@ -0,0 +1,26 @@
package com.youlai.boot.mini.model.dto;
import lombok.Data;
/**
* 生成的图片数据DTO
*
* @author youlai
*/
@Data
public class ImageData {
/**
* 图片URL地址
*/
private String url;
/**
* Base64编码的图片JSON
*/
private String b64_json;
/**
* 图片尺寸格式如 1664x2496
*/
private String size;
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save