From ff17ff2847f625b293973d5008cf4829ca281d16 Mon Sep 17 00:00:00 2001 From: glx <783262171@qq.com> Date: Tue, 9 Jun 2026 15:26:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BE=E6=8A=A5=E5=92=8C?= =?UTF-8?q?=E7=94=B3=E8=AF=89=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../youlai/boot/admin/model/vo/ReportVO.java | 2 +- .../impl/AuditExecutorServiceImpl.java | 4 + .../impl/BizAuditStatusHandlerImpl.java | 38 +++- .../impl/ContentLookupServiceImpl.java | 7 +- .../service/impl/ReportManageServiceImpl.java | 11 +- .../common/util/AliyunContentAuditUtil.java | 2 - .../boot/mini/model/entity/MiniReport.java | 4 +- .../mini/model/form/AppealSubmitForm.java | 7 +- .../mini/model/form/ReportSubmitForm.java | 7 +- .../impl/AdoptionDiaryCommentServiceImpl.java | 30 ++- .../impl/AdoptionDiaryServiceImpl.java | 164 +++++++++++++++- .../MiniContentAuditAppealServiceImpl.java | 14 +- .../service/impl/MiniReportServiceImpl.java | 45 ++++- .../service/impl/MiniUserServiceImpl.java | 81 +++++--- .../StrayAnimalNoteCommentServiceImpl.java | 31 ++- .../service/impl/StrayAnimalServiceImpl.java | 185 +++++++++++++++++- .../impl/UserPostCommentServiceImpl.java | 27 ++- .../service/impl/UserPostServiceImpl.java | 52 +++-- 18 files changed, 583 insertions(+), 128 deletions(-) diff --git a/src/main/java/com/youlai/boot/admin/model/vo/ReportVO.java b/src/main/java/com/youlai/boot/admin/model/vo/ReportVO.java index 3aa2437..434da1c 100644 --- a/src/main/java/com/youlai/boot/admin/model/vo/ReportVO.java +++ b/src/main/java/com/youlai/boot/admin/model/vo/ReportVO.java @@ -22,7 +22,7 @@ public class ReportVO { private String targetType; @Schema(description = "被举报内容ID") - private String targetId; + private Long targetId; @Schema(description = "举报人用户ID") private Long reporterUserId; diff --git a/src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java b/src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java index 7dffa8e..013bd15 100644 --- a/src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java +++ b/src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java @@ -15,6 +15,8 @@ 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.*; @@ -45,6 +47,7 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { * @return {status, auditId};配置关闭或无配置时返回 null */ @Override + @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public Map executeAudit(String moduleCode, Long bizId, AuditContentDTO content, String triggerType) { // 1) 查询审核配置 MiniContentAuditConfig config = findAuditConfig(moduleCode); @@ -398,6 +401,7 @@ public class AuditExecutorServiceImpl implements AuditExecutorService { auditStatus = AuditConstants.STATUS_REVIEWING; } + // 更新业务状态 if (!AuditConstants.STATUS_REVIEWING.equals(auditStatus)) { contentAuditService.updateAuditStatus(auditId, auditStatus); } diff --git a/src/main/java/com/youlai/boot/admin/service/impl/BizAuditStatusHandlerImpl.java b/src/main/java/com/youlai/boot/admin/service/impl/BizAuditStatusHandlerImpl.java index d998c4f..da22065 100644 --- a/src/main/java/com/youlai/boot/admin/service/impl/BizAuditStatusHandlerImpl.java +++ b/src/main/java/com/youlai/boot/admin/service/impl/BizAuditStatusHandlerImpl.java @@ -45,7 +45,7 @@ public class BizAuditStatusHandlerImpl implements BizAuditStatusHandler { 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); } } @@ -97,6 +97,9 @@ public class BizAuditStatusHandlerImpl implements BizAuditStatusHandler { } 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); @@ -104,9 +107,20 @@ public class BizAuditStatusHandlerImpl implements BizAuditStatusHandler { 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); @@ -114,9 +128,21 @@ public class BizAuditStatusHandlerImpl implements BizAuditStatusHandler { 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); @@ -124,10 +150,18 @@ public class BizAuditStatusHandlerImpl implements BizAuditStatusHandler { 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 + "/default_avatar.png"; + String defaultAvatar = "https://" + bucketName + "." + endpoint + "/mini_home_page_default_picture.png"; userMapper.update(null, new LambdaUpdateWrapper() .eq(SysUser::getId, userId) diff --git a/src/main/java/com/youlai/boot/admin/service/impl/ContentLookupServiceImpl.java b/src/main/java/com/youlai/boot/admin/service/impl/ContentLookupServiceImpl.java index 6b8fa9f..7035a2c 100644 --- a/src/main/java/com/youlai/boot/admin/service/impl/ContentLookupServiceImpl.java +++ b/src/main/java/com/youlai/boot/admin/service/impl/ContentLookupServiceImpl.java @@ -1,6 +1,7 @@ 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.*; @@ -108,8 +109,7 @@ public class ContentLookupServiceImpl implements ContentLookupService { } /** 分离媒体文件的图片和视频 URL */ - private AuditContentDTO buildContentResult(List texts, - List mediaList) { + private AuditContentDTO buildContentResult(List texts, List mediaList) { AuditContentDTO dto = new AuditContentDTO(); dto.setTexts(texts); List images = new ArrayList<>(); @@ -129,8 +129,7 @@ public class ContentLookupServiceImpl implements ContentLookupService { private record MediaUrlPair(String type, String url) {} /** 通用媒体查询 */ - private List lookupMedia(com.baomidou.mybatisplus.core.mapper.BaseMapper mapper, - LambdaQueryWrapper wrapper) { + private List lookupMedia(BaseMapper mapper, LambdaQueryWrapper wrapper) { List mediaList = mapper.selectList(wrapper); List result = new ArrayList<>(); for (M media : mediaList) { diff --git a/src/main/java/com/youlai/boot/admin/service/impl/ReportManageServiceImpl.java b/src/main/java/com/youlai/boot/admin/service/impl/ReportManageServiceImpl.java index cc1799d..7a6b748 100644 --- a/src/main/java/com/youlai/boot/admin/service/impl/ReportManageServiceImpl.java +++ b/src/main/java/com/youlai/boot/admin/service/impl/ReportManageServiceImpl.java @@ -81,7 +81,7 @@ public class ReportManageServiceImpl implements ReportManageService { if ("processed".equals(form.getResult())) { // 受理举报:自动查询目标内容 → 创建审核 → 执行机审 String moduleCode = report.getTargetType(); - Long bizId = parseBizId(report.getTargetId()); + Long bizId = report.getTargetId(); AuditContentDTO content = contentLookupService.lookupContent(moduleCode, bizId); Map auditResult = auditExecutorService.executeAudit( @@ -106,13 +106,4 @@ public class ReportManageServiceImpl implements ReportManageService { reportMapper.updateById(report); } - private Long parseBizId(String targetId) { - try { - return Long.parseLong(targetId); - } catch (NumberFormatException e) { - log.warn("targetId无法转为Long: {}", targetId); - return null; - } - } - } diff --git a/src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java b/src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java index c3c3d47..8f4290f 100644 --- a/src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java +++ b/src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java @@ -212,8 +212,6 @@ public class AliyunContentAuditUtil { }); } - // 图片审核不支持批量,使用 imageModeration 逐个调用 - // ===================== 图片审核(异步) ===================== public ImageAsyncModerationResponse imageAsyncModeration(String imageUrl, String service) { return executeWithFailover(client -> { diff --git a/src/main/java/com/youlai/boot/mini/model/entity/MiniReport.java b/src/main/java/com/youlai/boot/mini/model/entity/MiniReport.java index 1ac8918..4e1f88c 100644 --- a/src/main/java/com/youlai/boot/mini/model/entity/MiniReport.java +++ b/src/main/java/com/youlai/boot/mini/model/entity/MiniReport.java @@ -34,14 +34,14 @@ public class MiniReport implements Serializable { @TableField("target_id") @Schema(description = "被举报内容ID") - private String targetId; + private Long targetId; @TableField("reporter_user_id") @Schema(description = "举报人用户ID") private Long reporterUserId; @TableField("reason_category") - @Schema(description = "举报原因:违法违规,侵权冒犯,垃圾虚假,违规操作,其他") + @Schema(description = "举报原因:illegal_or_violent违法违规,spam_or_fake侵权冒犯,spam_or_fake垃圾虚假,abusive_behavior违规操作,other其他") private String reasonCategory; @TableField("evidence") diff --git a/src/main/java/com/youlai/boot/mini/model/form/AppealSubmitForm.java b/src/main/java/com/youlai/boot/mini/model/form/AppealSubmitForm.java index 7763c3b..27fa4a3 100644 --- a/src/main/java/com/youlai/boot/mini/model/form/AppealSubmitForm.java +++ b/src/main/java/com/youlai/boot/mini/model/form/AppealSubmitForm.java @@ -2,7 +2,6 @@ package com.youlai.boot.mini.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; @@ -11,9 +10,9 @@ import lombok.Setter; @Setter public class AppealSubmitForm { - @NotNull(message = "审核ID不能为空") - @Schema(description = "审核汇总ID") - private Long auditId; + @NotBlank(message = "审核UUID不能为空") + @Schema(description = "审核汇总UUID") + private String auditUuid; @NotBlank(message = "申诉原因不能为空") @Schema(description = "申诉原因") diff --git a/src/main/java/com/youlai/boot/mini/model/form/ReportSubmitForm.java b/src/main/java/com/youlai/boot/mini/model/form/ReportSubmitForm.java index 1764760..0170be4 100644 --- a/src/main/java/com/youlai/boot/mini/model/form/ReportSubmitForm.java +++ b/src/main/java/com/youlai/boot/mini/model/form/ReportSubmitForm.java @@ -2,7 +2,6 @@ package com.youlai.boot.mini.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; @@ -15,9 +14,9 @@ public class ReportSubmitForm { @Schema(description = "举报目标类型: animal_note / adoption_diary / user_post / note_comment / diary_comment / post_comment / username") private String targetType; - @NotBlank(message = "被举报内容ID不能为空") - @Schema(description = "被举报内容ID") - private String targetId; + @NotBlank(message = "被举报内容UUID不能为空") + @Schema(description = "被举报内容UUID") + private String targetUuid; @NotBlank(message = "举报原因类别不能为空") @Schema(description = "举报原因: 违法违规 / 侵权冒犯 / 垃圾虚假 / 违规操作 / 其他") diff --git a/src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryCommentServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryCommentServiceImpl.java index f02213e..dfc105e 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryCommentServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryCommentServiceImpl.java @@ -3,6 +3,9 @@ package com.youlai.boot.mini.service.impl; import cn.hutool.core.util.IdUtil; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.youlai.boot.admin.constant.AuditConstants; +import com.youlai.boot.admin.model.dto.AuditContentDTO; +import com.youlai.boot.admin.service.AuditExecutorService; import com.youlai.boot.common.exception.MsgException; import com.youlai.boot.common.util.IPUtils; import com.youlai.boot.common.util.HttpContext; @@ -47,6 +50,7 @@ public class AdoptionDiaryCommentServiceImpl extends ServiceImpl() + .eq(MiniContentAuditConfig::getModuleCode, moduleCode) + .eq(MiniContentAuditConfig::getDeleted, false) + .last("LIMIT 1") + ); + return config != null && !Boolean.TRUE.equals(config.getAuditEnable()); + } + + /** + * 从表单数据构建审核内容DTO + */ + private AuditContentDTO buildAuditContent(AdoptionDiaryForm formData) { + AuditContentDTO content = new AuditContentDTO(); + + List texts = new ArrayList<>(); + if (formData.getTitle() != null) { + texts.add(formData.getTitle()); + } + if (formData.getContent() != null) { + texts.add(formData.getContent()); + } + content.setTexts(texts); + + List images = new ArrayList<>(); + List videos = new ArrayList<>(); + if (formData.getMediaUrlList() != null) { + for (String url : formData.getMediaUrlList()) { + String lowerUrl = url.toLowerCase(); + if (lowerUrl.matches(".*\\.(mp4|mov|avi|wmv|flv|mkv|webm)(\\?.*)?$")) { + videos.add(url); + } else { + images.add(url); + } + } + } + content.setImages(images); + content.setVideos(videos); + + return content; + } + + /** + * 审核异常降级:直接创建人工审核记录及对应任务,确保不丢审 + */ + private void createManualReviewFallback(Long diaryId, AdoptionDiaryForm formData) { + createManualReviewFallback(diaryId, buildAuditContent(formData)); + } + + /** + * 审核异常降级(通用版):根据已构建的审核内容直接创建人工审核记录 + */ + private void createManualReviewFallback(Long diaryId, AuditContentDTO auditContent) { + MiniContentAudit audit = new MiniContentAudit(); + audit.setUuid(UUID.randomUUID().toString()); + audit.setModuleCode("adoption_diary"); + audit.setBizId(diaryId); + audit.setAuditType("manual"); + audit.setStatus(AuditConstants.STATUS_MANUAL_REVIEW); + audit.setCreateBy(SecurityUtils.getUserId()); + audit.setCreateTime(new Date()); + audit.setCreateTimestamp(System.currentTimeMillis()); + contentAuditService.save(audit); + + contentAuditTaskService.batchCreateTasks(audit.getId(), "manual", + auditContent.getTexts(), auditContent.getImages(), auditContent.getVideos()); + + contentAuditTaskService.lambdaUpdate() + .eq(MiniContentAuditTask::getContentAuditId, audit.getId()) + .set(MiniContentAuditTask::getStatus, AuditConstants.STATUS_MANUAL_REVIEW) + .update(); + + log.info("降级人工审核记录已创建, diaryId={}, auditId={}", diaryId, audit.getId()); + } + @Override @Transactional(rollbackFor = Exception.class) public String saveAdoptionDiary(AdoptionDiaryForm formData) { @@ -189,6 +281,12 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl auditResult = auditExecutorService.executeAudit("adoption_diary", diaryId, auditContent, AuditConstants.TRIGGER_CREATE); + if (auditResult != null) { + log.info("领养日记审核任务已创建, diaryId={}, auditResult={}", diaryId, auditResult); + } + } catch (Exception e) { + log.error("创建领养日记审核任务失败, 降级为人工审核, diaryId={}", diaryId, e); + createManualReviewFallback(diaryId, formData); + } + return diary.getUuid(); } @Override + @Transactional(rollbackFor = Exception.class) public void updateAdoptionDiary(String diaryUuid, AdoptionDiaryForm formData) { MiniAdoptionDiary diary = lambdaQuery().eq(MiniAdoptionDiary::getUuid, diaryUuid).one(); if (diary == null) { @@ -230,6 +342,7 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl auditResult = auditExecutorService.executeAudit("adoption_diary", diaryId, auditContent, AuditConstants.TRIGGER_CREATE); + if (auditResult != null) { + log.info("领养日记编辑审核任务已创建, diaryId={}, auditResult={}", diaryId, auditResult); + } + } catch (Exception e) { + log.error("创建领养日记编辑审核任务失败, 降级为人工审核, diaryId={}", diaryId, e); + createManualReviewFallback(diaryId, formData); + } } @Override @@ -292,12 +418,17 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl images, List videos) { MiniAdoptionDiary diary = lambdaQuery().eq(MiniAdoptionDiary::getUuid, diaryUuid).one(); if (diary == null) { return; } long currentTimestamp = System.currentTimeMillis(); + + List newImageUrls = new ArrayList<>(); + List newVideoUrls = new ArrayList<>(); + // 处理图片 if (CollUtil.isNotEmpty(images)) { for (MultipartFile image : images) { @@ -320,6 +451,7 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl auditResult = auditExecutorService.executeAudit("adoption_diary", diaryId, auditContent, AuditConstants.TRIGGER_CREATE); + if (auditResult != null) { + log.info("领养日记补充媒体审核任务已创建, diaryId={}, auditResult={}", diaryId, auditResult); + } + } catch (Exception e) { + log.error("创建领养日记补充媒体审核任务失败, 降级为人工审核, diaryId={}", diaryId, e); + AuditContentDTO fallbackContent = new AuditContentDTO(); + fallbackContent.setImages(newImageUrls); + fallbackContent.setVideos(newVideoUrls); + createManualReviewFallback(diaryId, fallbackContent); + } + } } @Override diff --git a/src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java index 5ad530c..2680054 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java @@ -30,18 +30,20 @@ public class MiniContentAuditAppealServiceImpl implements MiniContentAuditAppeal public void submitAppeal(AppealSubmitForm form) { Long userId = SecurityUtils.getUserId(); - // 检查审核记录是否存在且状态为 failed(只有不通过的内容才能申诉) - MiniContentAudit audit = contentAuditService.getById(form.getAuditId()); + // 根据UUID查出审核记录内部ID + MiniContentAudit audit = contentAuditService.getOne(new LambdaQueryWrapper() + .eq(MiniContentAudit::getUuid, form.getAuditUuid())); if (audit == null) { throw new RuntimeException("审核记录不存在"); } + Long auditId = audit.getId(); if (!AuditConstants.STATUS_REJECTED.equals(audit.getStatus())) { throw new RuntimeException("当前审核状态不允许申诉"); } // 检查是否已有待处理/已通过的申诉 Long existingCount = appealMapper.selectCount(new LambdaQueryWrapper() - .eq(MiniContentAuditAppeal::getAuditId, form.getAuditId()) + .eq(MiniContentAuditAppeal::getAuditId, auditId) .in(MiniContentAuditAppeal::getStatus, "pending", "approved")); if (existingCount != null && existingCount > 0) { throw new RuntimeException("已存在有效的申诉记录,请勿重复提交"); @@ -50,7 +52,7 @@ public class MiniContentAuditAppealServiceImpl implements MiniContentAuditAppeal // 创建申诉记录 MiniContentAuditAppeal appeal = new MiniContentAuditAppeal(); appeal.setUuid(IdUtil.fastSimpleUUID()); - appeal.setAuditId(form.getAuditId()); + appeal.setAuditId(auditId); appeal.setUserId(userId); appeal.setReason(form.getReason()); appeal.setEvidence(form.getEvidence()); @@ -61,9 +63,9 @@ public class MiniContentAuditAppealServiceImpl implements MiniContentAuditAppeal appealMapper.insert(appeal); // 更新审核状态为申诉中 - contentAuditService.updateAuditStatus(form.getAuditId(), AuditConstants.STATUS_APPEALING); + contentAuditService.updateAuditStatus(auditId, AuditConstants.STATUS_APPEALING); - log.info("用户提交申诉成功, auditId={}, userId={}", form.getAuditId(), userId); + log.info("用户提交申诉成功, auditId={}, userId={}", auditId, userId); } } diff --git a/src/main/java/com/youlai/boot/mini/service/impl/MiniReportServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/MiniReportServiceImpl.java index 6972acc..b5250a1 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/MiniReportServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/MiniReportServiceImpl.java @@ -3,10 +3,12 @@ package com.youlai.boot.mini.service.impl; import cn.hutool.core.util.IdUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.youlai.boot.framework.security.util.SecurityUtils; -import com.youlai.boot.mini.mapper.MiniReportMapper; +import com.youlai.boot.mini.mapper.*; import com.youlai.boot.mini.model.entity.MiniReport; import com.youlai.boot.mini.model.form.ReportSubmitForm; import com.youlai.boot.mini.service.MiniReportService; +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.stereotype.Service; @@ -20,16 +22,29 @@ import java.util.Date; public class MiniReportServiceImpl implements MiniReportService { private final MiniReportMapper reportMapper; + private final MiniUserPostMapper miniUserPostMapper; + private final MiniAdoptionDiaryMapper miniAdoptionDiaryMapper; + private final MiniStrayAnimalNoteMapper miniStrayAnimalNoteMapper; + private final MiniUserPostCommentMapper miniUserPostCommentMapper; + private final MiniAdoptionDiaryCommentMapper miniAdoptionDiaryCommentMapper; + private final MiniStrayAnimalNoteCommentMapper miniStrayAnimalNoteCommentMapper; + private final UserMapper userMapper; @Override @Transactional(rollbackFor = Exception.class) public void submitReport(ReportSubmitForm form) { Long userId = SecurityUtils.getUserId(); + // 根据UUID解析业务主键ID + Long targetId = resolveTargetBizId(form.getTargetType(), form.getTargetUuid()); + if (targetId == null) { + throw new RuntimeException("被举报内容不存在或已删除"); + } + // 检查是否已有待处理的重复举报(同一用户 + 同一目标) Long existingCount = reportMapper.selectCount(new LambdaQueryWrapper() .eq(MiniReport::getTargetType, form.getTargetType()) - .eq(MiniReport::getTargetId, form.getTargetId()) + .eq(MiniReport::getTargetId, targetId) .eq(MiniReport::getReporterUserId, userId) .eq(MiniReport::getStatus, "pending")); if (existingCount != null && existingCount > 0) { @@ -40,7 +55,7 @@ public class MiniReportServiceImpl implements MiniReportService { MiniReport report = new MiniReport(); report.setUuid(IdUtil.fastSimpleUUID()); report.setTargetType(form.getTargetType()); - report.setTargetId(form.getTargetId()); + report.setTargetId(targetId); report.setReporterUserId(userId); report.setReasonCategory(form.getReasonCategory()); report.setEvidence(form.getEvidence()); @@ -51,8 +66,28 @@ public class MiniReportServiceImpl implements MiniReportService { report.setCreateTimestamp(System.currentTimeMillis()); reportMapper.insert(report); - log.info("用户提交举报成功, targetType={}, targetId={}, userId={}", - form.getTargetType(), form.getTargetId(), userId); + log.info("用户提交举报成功, targetType={}, targetUuid={}, targetId={}, userId={}", + form.getTargetType(), form.getTargetUuid(), targetId, userId); + } + + /** + * 根据目标类型和UUID解析业务主键ID。 + */ + private Long resolveTargetBizId(String targetType, String targetUuid) { + return switch (targetType) { + case "user_post" -> miniUserPostMapper.selectIdByUuid(targetUuid); + case "adoption_diary" -> miniAdoptionDiaryMapper.selectIdByUuid(targetUuid); + case "animal_note" -> miniStrayAnimalNoteMapper.selectIdByUuid(targetUuid); + case "post_comment" -> miniUserPostCommentMapper.selectIdByUuid(targetUuid); + case "diary_comment" -> miniAdoptionDiaryCommentMapper.selectIdByUuid(targetUuid); + case "note_comment" -> miniStrayAnimalNoteCommentMapper.selectIdByUuid(targetUuid); + case "username" -> { + SysUser user = userMapper.selectOne(new LambdaQueryWrapper() + .eq(SysUser::getUuid, targetUuid)); + yield user != null ? user.getId() : null; + } + default -> null; + }; } } diff --git a/src/main/java/com/youlai/boot/mini/service/impl/MiniUserServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/MiniUserServiceImpl.java index 3a6daea..5f03c3d 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/MiniUserServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/MiniUserServiceImpl.java @@ -1,14 +1,13 @@ package com.youlai.boot.mini.service.impl; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.youlai.boot.admin.constant.AuditConstants; +import com.youlai.boot.admin.model.dto.AuditContentDTO; +import com.youlai.boot.admin.service.AuditExecutorService; import com.youlai.boot.common.exception.MsgException; import com.youlai.boot.common.util.RandomNumberUtils; -import com.youlai.boot.file.model.FileInfo; -import com.youlai.boot.file.service.FileService; import com.youlai.boot.file.service.impl.AliyunFileService; import com.youlai.boot.framework.security.util.SecurityUtils; -import com.youlai.boot.mini.model.entity.MiniStrayAnimalNoteMedia; -import com.youlai.boot.mini.model.enums.AnimalNoteMediaTypeEnum; import com.youlai.boot.mini.model.form.MiniUserUpdateForm; import com.youlai.boot.mini.model.vo.MiniUserInfoVO; import com.youlai.boot.mini.service.MiniUserService; @@ -23,11 +22,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.util.Date; -import java.util.List; -import java.util.UUID; +import java.util.Collections; +import java.util.Map; /** * C端用户服务实现类 @@ -43,6 +39,7 @@ public class MiniUserServiceImpl implements MiniUserService { private final UserService sysUserService; private final MiniPointAccountService pointAccountService; private final AliyunFileService aliyunFileService; + private final AuditExecutorService auditExecutorService; @Override public MiniUserInfoVO getCurrentUserInfo(Long userId) { @@ -81,6 +78,23 @@ public class MiniUserServiceImpl implements MiniUserService { throw new MsgException("用户不存在"); } + // 昵称文本先审核再保存(同步审核,不通过直接抛异常回滚,避免锁冲突) + if (StringUtils.hasText(form.getNickname())) { + try { + AuditContentDTO auditContent = new AuditContentDTO(); + auditContent.setTexts(Collections.singletonList(form.getNickname())); + Map result = auditExecutorService.executeAudit( + "user_nickname", userId, auditContent, AuditConstants.TRIGGER_CREATE); + if (result != null && AuditConstants.STATUS_REJECTED.equals(result.get("status"))) { + throw new MsgException("昵称包含违规内容,请修改"); + } + } catch (MsgException e) { + throw e; + } catch (Exception e) { + log.error("创建昵称审核任务失败, userId={}", userId, e); + } + } + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(SysUser::getId, userId); if (StringUtils.hasText(form.getNickname())) { @@ -97,6 +111,7 @@ public class MiniUserServiceImpl implements MiniUserService { } @Override + @Transactional(rollbackFor = Exception.class) public void saveFile(Long userId, MultipartFile image) { if (userId == null) { throw new MsgException("用户不存在"); @@ -107,35 +122,47 @@ public class MiniUserServiceImpl implements MiniUserService { throw new MsgException("用户不存在"); } - LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); - updateWrapper.eq(SysUser::getId, userId); - long currentTimestamp = System.currentTimeMillis(); if (image != null && !image.isEmpty()) { + String newAvatarUrl; try { String objectName = "user_avatar/image/" + currentTimestamp + RandomNumberUtils.createRandomLowerLetterAndNumber(8) + "." + FilenameUtils.getExtension(image.getOriginalFilename()); - String newAvatarUrl = aliyunFileService.uploadFile(objectName, image.getInputStream()); - - //更新数据库头像字段 - updateWrapper.set(SysUser::getAvatar, newAvatarUrl); - - // 删除旧头像(如果存在) - String oldAvatar = sysUser.getAvatar(); - if (StringUtils.hasText(oldAvatar)) { - try { - aliyunFileService.deleteFile(oldAvatar); - } catch (Exception e) { - // 删除旧头像失败只打日志 - log.warn("删除用户旧头像失败,userId={}, oldAvatar={}", userId, oldAvatar, e); - } - } + newAvatarUrl = aliyunFileService.uploadFile(objectName, image.getInputStream()); } catch (Exception e) { log.error("user avatar upload failed", e); throw new MsgException("头像上传失败"); } + + // 更新数据库头像字段 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SysUser::getId, userId); + updateWrapper.set(SysUser::getAvatar, newAvatarUrl); + boolean success = sysUserService.update(updateWrapper); + if (!success) { + throw new MsgException("头像更新失败,请重试"); + } + + // 删除旧头像(如果存在) + String oldAvatar = sysUser.getAvatar(); + if (StringUtils.hasText(oldAvatar)) { + try { + aliyunFileService.deleteFile(oldAvatar); + } catch (Exception e) { + log.warn("删除用户旧头像失败,userId={}, oldAvatar={}", userId, oldAvatar, e); + } + } + + // 图片审核(异步,不通过时回调重置为默认头像,不影响主流程) + try { + AuditContentDTO auditContent = new AuditContentDTO(); + auditContent.setImages(Collections.singletonList(newAvatarUrl)); + auditExecutorService.executeAudit("user_avatar", userId, auditContent, AuditConstants.TRIGGER_CREATE); + } catch (Exception e) { + log.error("创建头像审核任务失败, userId={}", userId, e); + } } } diff --git a/src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalNoteCommentServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalNoteCommentServiceImpl.java index 393a81b..4a51e16 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalNoteCommentServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalNoteCommentServiceImpl.java @@ -5,6 +5,9 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.youlai.boot.admin.constant.AuditConstants; +import com.youlai.boot.admin.model.dto.AuditContentDTO; +import com.youlai.boot.admin.service.AuditExecutorService; import com.youlai.boot.common.exception.MsgException; import com.youlai.boot.common.util.HttpContext; import com.youlai.boot.common.util.IPUtils; @@ -44,6 +47,7 @@ public class StrayAnimalNoteCommentServiceImpl extends ServiceImpl() + .eq(MiniContentAuditConfig::getModuleCode, moduleCode) + .eq(MiniContentAuditConfig::getDeleted, false) + .last("LIMIT 1") + ); + return config != null && !Boolean.TRUE.equals(config.getAuditEnable()); + } + + /** + * 从表单数据构建审核内容DTO + */ + private AuditContentDTO buildAuditContent(StrayAnimalForm formData) { + AuditContentDTO content = new AuditContentDTO(); + + List texts = new ArrayList<>(); + if (formData.getTitle() != null) { + texts.add(formData.getTitle()); + } + if (formData.getContent() != null) { + texts.add(formData.getContent()); + } + content.setTexts(texts); + + List images = new ArrayList<>(); + List videos = new ArrayList<>(); + if (formData.getMediaUrlList() != null) { + for (String url : formData.getMediaUrlList()) { + String lowerUrl = url.toLowerCase(); + if (lowerUrl.matches(".*\\.(mp4|mov|avi|wmv|flv|mkv|webm)(\\?.*)?$")) { + videos.add(url); + } else { + images.add(url); + } + } + } + content.setImages(images); + content.setVideos(videos); + + return content; + } + + /** + * 审核异常降级:直接创建人工审核记录及对应任务 + */ + private void createManualReviewFallback(Long animalId, StrayAnimalForm formData) { + createManualReviewFallback(animalId, buildAuditContent(formData)); + } + + /** + * 审核异常降级(通用版):根据已构建的审核内容直接创建人工审核记录 + */ + private void createManualReviewFallback(Long animalId, AuditContentDTO auditContent) { + MiniContentAudit audit = new MiniContentAudit(); + audit.setUuid(UUID.randomUUID().toString()); + audit.setModuleCode("animal_note"); + audit.setBizId(animalId); + audit.setAuditType("manual"); + audit.setStatus(AuditConstants.STATUS_MANUAL_REVIEW); + audit.setCreateBy(SecurityUtils.getUserId()); + audit.setCreateTime(new Date()); + audit.setCreateTimestamp(System.currentTimeMillis()); + contentAuditService.save(audit); + + contentAuditTaskService.batchCreateTasks(audit.getId(), "manual", + auditContent.getTexts(), auditContent.getImages(), auditContent.getVideos()); + + contentAuditTaskService.lambdaUpdate() + .eq(MiniContentAuditTask::getContentAuditId, audit.getId()) + .set(MiniContentAuditTask::getStatus, AuditConstants.STATUS_MANUAL_REVIEW) + .update(); + + log.info("降级人工审核记录已创建, animalId={}, auditId={}", animalId, audit.getId()); + } @Override public List saveFile(List images, List videos) { @@ -213,19 +305,36 @@ public class StrayAnimalServiceImpl extends ServiceImpl auditResult = auditExecutorService.executeAudit("animal_note", animalId, auditContent, AuditConstants.TRIGGER_CREATE); + if (auditResult != null) { + log.info("流浪动物审核任务已创建, animalId={}, auditResult={}", animalId, auditResult); + } + } catch (Exception e) { + log.error("创建流浪动物审核任务失败, 降级为人工审核, animalId={}", animalId, e); + createManualReviewFallback(animalId, formData); + } SaveStrayAnimalVO vo = new SaveStrayAnimalVO(); vo.setAnimalUuid(miniStrayAnimal.getUuid()); @@ -243,7 +352,10 @@ public class StrayAnimalServiceImpl extends ServiceImpl images, List videos, long currentTimestamp) { + private Map> saveMediaFiles(MiniStrayAnimalNote note, List images, List videos, long currentTimestamp) { + List imageUrls = new ArrayList<>(); + List videoUrls = new ArrayList<>(); + // 处理图片 if (images != null) { for (MultipartFile image : images) { @@ -267,6 +379,9 @@ public class StrayAnimalServiceImpl extends ServiceImpl 0) { + imageUrls.add(url); + } } catch (Exception e) { log.error("image upload failed", e); } @@ -309,6 +424,7 @@ public class StrayAnimalServiceImpl extends ServiceImpl> result = new HashMap<>(); + result.put("imageUrls", imageUrls); + result.put("videoUrls", videoUrls); + return result; } private MiniStrayAnimalNote saveNoteInfo(StrayAnimalForm formData, MiniStrayAnimal miniStrayAnimal) { @@ -335,13 +456,14 @@ public class StrayAnimalServiceImpl extends ServiceImpl auditResult = auditExecutorService.executeAudit("animal_note", animalId, auditContent, AuditConstants.TRIGGER_CREATE); + if (auditResult != null) { + log.info("流浪动物编辑审核任务已创建, animalId={}, auditResult={}", animalId, auditResult); + } + } catch (Exception e) { + log.error("创建流浪动物编辑审核任务失败, 降级为人工审核, animalId={}", animalId, e); + createManualReviewFallback(animalId, formData); + } } private MiniStrayAnimal getValidAnimal(String animalUuid) { @@ -527,6 +665,7 @@ public class StrayAnimalServiceImpl extends ServiceImpl images, List videos) { if ((images == null || images.isEmpty()) && (videos == null || videos.isEmpty())) { throw new MsgException("需要上传媒体资源"); @@ -562,8 +701,34 @@ public class StrayAnimalServiceImpl extends ServiceImpl> urlMap = saveMediaFiles(note, images, videos, System.currentTimeMillis()); + List newImageUrls = urlMap.getOrDefault("imageUrls", Collections.emptyList()); + List newVideoUrls = urlMap.getOrDefault("videoUrls", Collections.emptyList()); + + // 补充媒体后重新审核(审核在独立事务中运行,失败降级为人工审核) + if (!newImageUrls.isEmpty() || !newVideoUrls.isEmpty()) { + // 标记为审核中 + miniStrayAnimal.setAuditStatus(AuditConstants.BIZ_AUDIT_STATUS_REVIEWING); + miniStrayAnimal.setUpdateTime(new Date()); + miniStrayAnimal.setUpdateTimestamp(System.currentTimeMillis()); + miniStrayAnimalMapper.updateById(miniStrayAnimal); + try { + AuditContentDTO auditContent = new AuditContentDTO(); + auditContent.setImages(newImageUrls); + auditContent.setVideos(newVideoUrls); + Map auditResult = auditExecutorService.executeAudit("animal_note", animalId, auditContent, AuditConstants.TRIGGER_CREATE); + if (auditResult != null) { + log.info("流浪动物补充媒体审核任务已创建, animalId={}, auditResult={}", animalId, auditResult); + } + } catch (Exception e) { + log.error("创建流浪动物补充媒体审核任务失败, 降级为人工审核, animalId={}", animalId, e); + AuditContentDTO fallbackContent = new AuditContentDTO(); + fallbackContent.setImages(newImageUrls); + fallbackContent.setVideos(newVideoUrls); + createManualReviewFallback(animalId, fallbackContent); + } + } } @Override diff --git a/src/main/java/com/youlai/boot/mini/service/impl/UserPostCommentServiceImpl.java b/src/main/java/com/youlai/boot/mini/service/impl/UserPostCommentServiceImpl.java index 3a64649..a45988d 100644 --- a/src/main/java/com/youlai/boot/mini/service/impl/UserPostCommentServiceImpl.java +++ b/src/main/java/com/youlai/boot/mini/service/impl/UserPostCommentServiceImpl.java @@ -5,6 +5,9 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker; 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.constant.AuditConstants; +import com.youlai.boot.admin.model.dto.AuditContentDTO; +import com.youlai.boot.admin.service.AuditExecutorService; import com.youlai.boot.common.exception.MsgException; import com.youlai.boot.common.util.IPUtils; import com.youlai.boot.common.util.HttpContext; @@ -45,6 +48,7 @@ public class UserPostCommentServiceImpl extends ServiceImpl