Browse Source

修改举报和申诉模块

glx_phase2
glx 4 days ago
parent
commit
ff17ff2847
  1. 2
      src/main/java/com/youlai/boot/admin/model/vo/ReportVO.java
  2. 4
      src/main/java/com/youlai/boot/admin/service/impl/AuditExecutorServiceImpl.java
  3. 38
      src/main/java/com/youlai/boot/admin/service/impl/BizAuditStatusHandlerImpl.java
  4. 7
      src/main/java/com/youlai/boot/admin/service/impl/ContentLookupServiceImpl.java
  5. 11
      src/main/java/com/youlai/boot/admin/service/impl/ReportManageServiceImpl.java
  6. 2
      src/main/java/com/youlai/boot/common/util/AliyunContentAuditUtil.java
  7. 4
      src/main/java/com/youlai/boot/mini/model/entity/MiniReport.java
  8. 7
      src/main/java/com/youlai/boot/mini/model/form/AppealSubmitForm.java
  9. 7
      src/main/java/com/youlai/boot/mini/model/form/ReportSubmitForm.java
  10. 30
      src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryCommentServiceImpl.java
  11. 164
      src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryServiceImpl.java
  12. 14
      src/main/java/com/youlai/boot/mini/service/impl/MiniContentAuditAppealServiceImpl.java
  13. 45
      src/main/java/com/youlai/boot/mini/service/impl/MiniReportServiceImpl.java
  14. 59
      src/main/java/com/youlai/boot/mini/service/impl/MiniUserServiceImpl.java
  15. 31
      src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalNoteCommentServiceImpl.java
  16. 185
      src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalServiceImpl.java
  17. 27
      src/main/java/com/youlai/boot/mini/service/impl/UserPostCommentServiceImpl.java
  18. 12
      src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java

2
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;

4
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<String, Object> 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);
}

38
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<SysUser>()
.eq(SysUser::getId, userId)

7
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 <T> AuditContentDTO buildContentResult(List<String> texts,
List<MediaUrlPair> mediaList) {
private <T> AuditContentDTO buildContentResult(List<String> texts, List<MediaUrlPair> mediaList) {
AuditContentDTO dto = new AuditContentDTO();
dto.setTexts(texts);
List<String> images = new ArrayList<>();
@ -129,8 +129,7 @@ public class ContentLookupServiceImpl implements ContentLookupService {
private record MediaUrlPair(String type, String url) {}
/** 通用媒体查询 */
private <M> List<MediaUrlPair> lookupMedia(com.baomidou.mybatisplus.core.mapper.BaseMapper<M> mapper,
LambdaQueryWrapper<M> wrapper) {
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) {

11
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<String, Object> 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;
}
}
}

2
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 -> {

4
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")

7
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 = "申诉原因")

7
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 = "举报原因: 违法违规 / 侵权冒犯 / 垃圾虚假 / 违规操作 / 其他")

30
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<MiniAdoptionDia
private final MiniAdoptionDiaryMapper miniAdoptionDiaryMapper;
private final UserMapper userMapper;
private final MiniAdoptionDiaryCommentLikeMapper miniAdoptionDiaryCommentLikeMapper;
private final AuditExecutorService auditExecutorService;
@Override
@Transactional(rollbackFor = Exception.class)
@ -286,7 +290,23 @@ public class AdoptionDiaryCommentServiceImpl extends ServiceImpl<MiniAdoptionDia
diaryComment.setRootId(rootId);
baseMapper.updateById(diaryComment);
// 6. 构建返回VO
// 6. 提前查好rootUuid,避免审核回调同步删除后查不到
String rootUuid = baseMapper.selectUuidById(rootId);
if (rootUuid == null) {
throw new MsgException("评论所属楼层已删除");
}
// 7. 评论内容审核(审核在独立事务中运行,不通过由回调删除,不影响主流程)
Long commentId = diaryComment.getId();
try {
AuditContentDTO auditContent = new AuditContentDTO();
auditContent.setTexts(Collections.singletonList(addCommentForm.getContent()));
auditExecutorService.executeAudit("diary_comment", commentId, auditContent, AuditConstants.TRIGGER_CREATE);
} catch (Exception e) {
log.error("创建日记评论审核任务失败, commentId={}", commentId, e);
}
// 构建返回VO
AdoptionDiaryCommentVO diaryCommentVO = new AdoptionDiaryCommentVO();
SysUser appUser = userMapper.selectById(appUserId);
diaryCommentVO.setNickname(appUser.getNickname());
@ -297,12 +317,6 @@ public class AdoptionDiaryCommentServiceImpl extends ServiceImpl<MiniAdoptionDia
diaryCommentVO.setAppUserId(appUser.getUuid());
diaryCommentVO.setContent(diaryComment.getContent());
diaryCommentVO.setParentUuId(addCommentForm.getParentUuId());
// 根据rootId查询UUID
String rootUuid = baseMapper.selectUuidById(diaryComment.getRootId());
if (rootUuid == null) {
throw new MsgException("评论所属楼层已删除");
}
diaryCommentVO.setRootId(rootUuid);
// 设置回复用户信息
@ -321,7 +335,7 @@ public class AdoptionDiaryCommentServiceImpl extends ServiceImpl<MiniAdoptionDia
diaryCommentVO.setLikeCount(0);
diaryCommentVO.setProvince(diaryComment.getProvince());
// 7. 更新日记评论数
// 8. 更新日记评论数
baseMapper.incrementCommentCount(diaryId);
return diaryCommentVO;

164
src/main/java/com/youlai/boot/mini/service/impl/AdoptionDiaryServiceImpl.java

@ -9,6 +9,15 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
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.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.AuditExecutorService;
import com.youlai.boot.admin.service.ContentAuditConfigService;
import com.youlai.boot.admin.service.ContentAuditService;
import com.youlai.boot.admin.service.ContentAuditTaskService;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.common.util.FileUtils;
import com.youlai.boot.common.util.JavaVCUtils;
@ -63,6 +72,10 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
private final MiniAdoptionDiaryLikeMapper miniAdoptionDiaryLikeMapper;
private final MiniAdoptionDiaryCollectMapper miniAdoptionDiaryCollectMapper;
private final MiniStrayAnimalMapper miniStrayAnimalMapper;
private final ContentAuditConfigService contentAuditConfigService;
private final AuditExecutorService auditExecutorService;
private final ContentAuditService contentAuditService;
private final ContentAuditTaskService contentAuditTaskService;
// 布隆过滤器常量
private static final String BLOOM_VIEW_KEY_PREFIX = "mini:diary:view:bloom:";
@ -175,6 +188,85 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
return urlList;
}
/**
* 判断指定模块的审核是否已开启
*/
private boolean isAuditEnabled(String moduleCode) {
MiniContentAuditConfig config = contentAuditConfigService.getOne(
new LambdaQueryWrapper<MiniContentAuditConfig>()
.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<String> texts = new ArrayList<>();
if (formData.getTitle() != null) {
texts.add(formData.getTitle());
}
if (formData.getContent() != null) {
texts.add(formData.getContent());
}
content.setTexts(texts);
List<String> images = new ArrayList<>();
List<String> 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<MiniAdoptionDiaryMappe
throw new MsgException("您不是该动物的领养人,无法发布领养日记");
}
// 查询审核配置,决定初始审核状态
boolean auditEnabled = isAuditEnabled("adoption_diary");
Integer initAuditStatus = auditEnabled
? AuditConstants.BIZ_AUDIT_STATUS_REVIEWING
: AuditConstants.BIZ_AUDIT_STATUS_PASSED;
// 1. 保存领养日记基本信息
MiniAdoptionDiary diary = new MiniAdoptionDiary();
diary.setUuid(UUID.randomUUID().toString());
@ -201,6 +299,7 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
diary.setLikeCount(0);
diary.setCommentCount(0);
diary.setCollectCount(0);
diary.setAuditStatus(initAuditStatus);
diary.setCreateBy(userId);
diary.setCreateTime(new Date(currentTimestamp));
diary.setCreateTimestamp(currentTimestamp);
@ -216,11 +315,24 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
miniAdoptionDiaryMediaMapper.update(null, wrapper);
}
// 3.返回领养日记uuid
// 3. 创建内容审核任务(审核在独立事务中运行,失败降级为人工审核)
Long diaryId = diary.getId();
try {
AuditContentDTO auditContent = buildAuditContent(formData);
Map<String, Object> 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<MiniAdoptionDiaryMappe
// 更新基本信息
diary.setTitle(formData.getTitle());
diary.setContent(formData.getContent());
diary.setAuditStatus(AuditConstants.BIZ_AUDIT_STATUS_REVIEWING); // 编辑后重新审核
diary.setUpdateBy(SecurityUtils.getUserId());
diary.setUpdateTime(new Date(currentTimestamp));
diary.setUpdateTimestamp(currentTimestamp);
@ -243,6 +356,19 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
.set(MiniAdoptionDiaryMedia::getDiaryId, diary.getId());
miniAdoptionDiaryMediaMapper.update(null, wrapper);
}
// 编辑后重新审核(审核在独立事务中运行,失败降级为人工审核)
Long diaryId = diary.getId();
try {
AuditContentDTO auditContent = buildAuditContent(formData);
Map<String, Object> 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<MiniAdoptionDiaryMappe
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMediaSource(String diaryUuid, List<MultipartFile> images, List<MultipartFile> videos) {
MiniAdoptionDiary diary = lambdaQuery().eq(MiniAdoptionDiary::getUuid, diaryUuid).one();
if (diary == null) {
return;
}
long currentTimestamp = System.currentTimeMillis();
List<String> newImageUrls = new ArrayList<>();
List<String> newVideoUrls = new ArrayList<>();
// 处理图片
if (CollUtil.isNotEmpty(images)) {
for (MultipartFile image : images) {
@ -320,6 +451,7 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
media.setCreateTime(new Date(currentTimestamp));
media.setCreateBy(SecurityUtils.getUserId());
miniAdoptionDiaryMediaMapper.insert(media);
newImageUrls.add(url);
} catch (Exception e) {
log.error("领养日记补充图片上传失败", e);
}
@ -361,12 +493,42 @@ public class AdoptionDiaryServiceImpl extends ServiceImpl<MiniAdoptionDiaryMappe
FileUtils.bufferedImageToInputStream(thumbnail, "png"));
media.setThumbnailUrl(thumbnailUrl);
miniAdoptionDiaryMediaMapper.insert(media);
newVideoUrls.add(url);
FileUtils.delete(videoPath);
} catch (Exception e) {
log.error("领养日记补充视频上传失败", e);
}
}
}
// 补充媒体后重新审核(审核在独立事务中运行,失败降级为人工审核)
if (!newImageUrls.isEmpty() || !newVideoUrls.isEmpty()) {
Long diaryId = diary.getId();
// 标记为审核中
MiniAdoptionDiary updateStatus = new MiniAdoptionDiary();
updateStatus.setId(diaryId);
updateStatus.setAuditStatus(AuditConstants.BIZ_AUDIT_STATUS_REVIEWING);
updateStatus.setUpdateTime(new Date());
updateStatus.setUpdateTimestamp(System.currentTimeMillis());
updateById(updateStatus);
try {
AuditContentDTO auditContent = new AuditContentDTO();
auditContent.setImages(newImageUrls);
auditContent.setVideos(newVideoUrls);
Map<String, Object> 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

14
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<MiniContentAudit>()
.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<MiniContentAuditAppeal>()
.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);
}
}

45
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<MiniReport>()
.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<SysUser>()
.eq(SysUser::getUuid, targetUuid));
yield user != null ? user.getId() : null;
}
default -> null;
};
}
}

59
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<String, Object> 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<SysUser> 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,20 +122,28 @@ public class MiniUserServiceImpl implements MiniUserService {
throw new MsgException("用户不存在");
}
LambdaUpdateWrapper<SysUser> 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());
newAvatarUrl = aliyunFileService.uploadFile(objectName, image.getInputStream());
} catch (Exception e) {
log.error("user avatar upload failed", e);
throw new MsgException("头像上传失败");
}
// 更新数据库头像字段
LambdaUpdateWrapper<SysUser> 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();
@ -128,13 +151,17 @@ public class MiniUserServiceImpl implements MiniUserService {
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("user avatar upload failed", e);
throw new MsgException("头像上传失败");
log.error("创建头像审核任务失败, userId={}", userId, e);
}
}
}

31
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<MiniStrayAnim
private final MiniStrayAnimalNoteCommentLikeMapper miniStrayAnimalNoteCommentLikeMapper;
private final MiniStrayAnimalNoteMapper miniStrayAnimalNoteMapper;
private final UserMapper userMapper;
private final AuditExecutorService auditExecutorService;
@Transactional(rollbackFor = Exception.class)
@Override
@ -160,7 +164,24 @@ public class StrayAnimalNoteCommentServiceImpl extends ServiceImpl<MiniStrayAnim
baseMapper.updateById(animalNoteComment);
}
// 7. 构建返回VO
// 7. 提前查好rootUuid,避免审核回调同步删除后查不到
Long rootId = animalNoteComment.getRootId();
String rootUuid = baseMapper.selectUuidById(rootId);
if (rootUuid == null) {
throw new MsgException("评论所属楼层已删除");
}
// 8. 评论内容审核(审核在独立事务中运行,不通过由回调删除,不影响主流程)
Long commentId = animalNoteComment.getId();
try {
AuditContentDTO auditContent = new AuditContentDTO();
auditContent.setTexts(Collections.singletonList(addCommentForm.getContent()));
auditExecutorService.executeAudit("note_comment", commentId, auditContent, AuditConstants.TRIGGER_CREATE);
} catch (Exception e) {
log.error("创建笔记评论审核任务失败, commentId={}", commentId, e);
}
// 构建返回VO
StrayAnimalNoteCommentVO animalNoteCommentVO = new StrayAnimalNoteCommentVO();
SysUser appUser = userMapper.selectById(appUserId);
animalNoteCommentVO.setNickname(appUser.getNickname());
@ -171,12 +192,6 @@ public class StrayAnimalNoteCommentServiceImpl extends ServiceImpl<MiniStrayAnim
animalNoteCommentVO.setAppUserId(appUser.getUuid());
animalNoteCommentVO.setContent(animalNoteComment.getContent());
animalNoteCommentVO.setParentUuId(addCommentForm.getParentUuId());
// 根据rootId查询UUID
String rootUuid = baseMapper.selectUuidById(animalNoteComment.getRootId());
if (rootUuid == null) {
throw new MsgException("评论所属楼层已删除");
}
animalNoteCommentVO.setRootId(rootUuid);
// 设置回复用户信息
@ -195,7 +210,7 @@ public class StrayAnimalNoteCommentServiceImpl extends ServiceImpl<MiniStrayAnim
animalNoteCommentVO.setLikeCount(0);
animalNoteCommentVO.setProvince(animalNoteComment.getProvince());
// 8. 更新笔记评论数
// 9. 更新笔记评论数
baseMapper.incrementCommentCount(noteId);
return animalNoteCommentVO;

185
src/main/java/com/youlai/boot/mini/service/impl/StrayAnimalServiceImpl.java

@ -10,6 +10,15 @@ 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.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.AuditExecutorService;
import com.youlai.boot.admin.service.ContentAuditConfigService;
import com.youlai.boot.admin.service.ContentAuditService;
import com.youlai.boot.admin.service.ContentAuditTaskService;
import com.youlai.boot.common.constant.CommonConstants;
import com.youlai.boot.common.exception.MsgException;
import com.youlai.boot.common.result.Result;
@ -116,10 +125,93 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
private final MiniStrayAnimalConverter miniStrayAnimalConverter;
private final ContentAuditConfigService contentAuditConfigService;
private final AuditExecutorService auditExecutorService;
private final ContentAuditService contentAuditService;
private final ContentAuditTaskService contentAuditTaskService;
public String getDefaultCatCoverHost() {
return "https://" + bucketName + "." + endpoint;
}
/**
* 判断指定模块的审核是否已开启
*/
private boolean isAuditEnabled(String moduleCode) {
MiniContentAuditConfig config = contentAuditConfigService.getOne(
new LambdaQueryWrapper<MiniContentAuditConfig>()
.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<String> texts = new ArrayList<>();
if (formData.getTitle() != null) {
texts.add(formData.getTitle());
}
if (formData.getContent() != null) {
texts.add(formData.getContent());
}
content.setTexts(texts);
List<String> images = new ArrayList<>();
List<String> 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<String> saveFile(List<MultipartFile> images, List<MultipartFile> videos) {
@ -213,19 +305,36 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
// 1. 参数校验
// validateInput(images, videos);
// 2. 保存流浪动物基本信息
MiniStrayAnimal miniStrayAnimal = saveAnimalInfo(formData);
// 2. 查询审核配置,决定初始审核状态
boolean auditEnabled = isAuditEnabled("animal_note");
Integer initAuditStatus = auditEnabled
? AuditConstants.BIZ_AUDIT_STATUS_REVIEWING
: AuditConstants.BIZ_AUDIT_STATUS_PASSED;
// 3. 保存流浪动物基本信息(含审核状态)
MiniStrayAnimal miniStrayAnimal = saveAnimalInfo(formData, initAuditStatus);
// 3. 保存笔记信息
// 4. 保存笔记信息
MiniStrayAnimalNote note = saveNoteInfo(formData, miniStrayAnimal);
// 4. 处理并保存媒体文件
// 5. 处理并保存媒体文件
// saveMediaFiles(note, images, videos, miniStrayAnimal.getCreateTimestamp());
saveMediaFiles(note, formData);
// 5. 积分
// 6. 积分
pointRecordService.giveRegisterAnimalReward(miniStrayAnimal.getMiniUserId(), miniStrayAnimal.getId());
// 6. 提交审核
// 7. 提交审核(审核在独立事务中运行,失败降级为人工审核)
Long animalId = miniStrayAnimal.getId();
try {
AuditContentDTO auditContent = buildAuditContent(formData);
Map<String, Object> 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<MiniStrayAnimalMapper, M
}
}
private void saveMediaFiles(MiniStrayAnimalNote note, List<MultipartFile> images, List<MultipartFile> videos, long currentTimestamp) {
private Map<String, List<String>> saveMediaFiles(MiniStrayAnimalNote note, List<MultipartFile> images, List<MultipartFile> videos, long currentTimestamp) {
List<String> imageUrls = new ArrayList<>();
List<String> videoUrls = new ArrayList<>();
// 处理图片
if (images != null) {
for (MultipartFile image : images) {
@ -267,6 +379,9 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
miniStrayAnimalNoteMedia.setCreateBy(note.getMiniUserId());
int result = miniStrayAnimalNoteMediaMapper.insert(miniStrayAnimalNoteMedia);
if (result > 0) {
imageUrls.add(url);
}
} catch (Exception e) {
log.error("image upload failed", e);
}
@ -309,6 +424,7 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
miniStrayAnimalNoteMedia.setThumbnailUrl(thumbnailUrl);
miniStrayAnimalNoteMediaMapper.insert(miniStrayAnimalNoteMedia);
videoUrls.add(url);
FileUtils.delete(videoPath);
@ -317,6 +433,11 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
}
}
}
Map<String, List<String>> 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<MiniStrayAnimalMapper, M
return note;
}
private MiniStrayAnimal saveAnimalInfo(StrayAnimalForm formData) {
private MiniStrayAnimal saveAnimalInfo(StrayAnimalForm formData, Integer auditStatus) {
long currentTimeUnix = System.currentTimeMillis();
MiniStrayAnimal entity = miniStrayAnimalConverter.toEntity(formData);
entity.setMiniUserId(SecurityUtils.getUserId());
entity.setUuid(UUID.randomUUID().toString());
entity.setAuditStatus(auditStatus);
entity.setCreateTimestamp(currentTimeUnix);
entity.setCreateTime(new Date(currentTimeUnix));
entity.setCreateBy(SecurityUtils.getUserId());
@ -416,8 +538,24 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
updateNoteInfo(miniStrayAnimalNote, formData, currentTimestamp);
// 3. 处理并保存媒体文件
saveMediaFiles(miniStrayAnimalNote, formData);
// 4. 提交审核
// 4. 提交审核(审核在独立事务中运行,失败降级为人工审核)
animal.setAuditStatus(AuditConstants.BIZ_AUDIT_STATUS_REVIEWING);
animal.setUpdateTime(new Date(currentTimestamp));
animal.setUpdateTimestamp(currentTimestamp);
miniStrayAnimalMapper.updateById(animal);
Long animalId = animal.getId();
try {
AuditContentDTO auditContent = buildAuditContent(formData);
Map<String, Object> 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<MiniStrayAnimalMapper, M
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMediaSource(String noteUuid, List<MultipartFile> images, List<MultipartFile> videos) {
if ((images == null || images.isEmpty()) && (videos == null || videos.isEmpty())) {
throw new MsgException("需要上传媒体资源");
@ -562,8 +701,34 @@ public class StrayAnimalServiceImpl extends ServiceImpl<MiniStrayAnimalMapper, M
}
}
saveMediaFiles(note, images, videos, System.currentTimeMillis());
Map<String, List<String>> urlMap = saveMediaFiles(note, images, videos, System.currentTimeMillis());
List<String> newImageUrls = urlMap.getOrDefault("imageUrls", Collections.emptyList());
List<String> 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<String, Object> 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

27
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<MiniUserPostCommentM
private final MiniUserPostMapper miniUserPostMapper;
private final UserMapper userMapper;
private final MiniUserPostCommentLikeMapper miniUserPostCommentLikeMapper;
private final AuditExecutorService auditExecutorService;
@Override
@Transactional(rollbackFor = Exception.class)
@ -118,7 +122,23 @@ public class UserPostCommentServiceImpl extends ServiceImpl<MiniUserPostCommentM
comment.setRootId(rootId);
baseMapper.updateById(comment);
// 6. 构建返回VO
// 6. 提前查好rootUuid,避免审核回调同步删除后查不到
String rootUuid = baseMapper.selectUuidById(rootId);
if (rootUuid == null) {
throw new MsgException("评论所属楼层已删除");
}
// 7. 评论内容审核(审核在独立事务中运行,不通过由回调删除,不影响主流程)
Long commentId = comment.getId();
try {
AuditContentDTO auditContent = new AuditContentDTO();
auditContent.setTexts(Collections.singletonList(formData.getContent()));
auditExecutorService.executeAudit("post_comment", commentId, auditContent, AuditConstants.TRIGGER_CREATE);
} catch (Exception e) {
log.error("创建评论审核任务失败, commentId={}", commentId, e);
}
// 构建返回VO
UserPostCommentVO vo = new UserPostCommentVO();
SysUser appUser = userMapper.selectById(appUserId);
vo.setNickname(appUser.getNickname());
@ -129,11 +149,6 @@ public class UserPostCommentServiceImpl extends ServiceImpl<MiniUserPostCommentM
vo.setAppUserId(appUser.getUuid());
vo.setContent(comment.getContent());
vo.setParentUuId(formData.getParentUuId());
String rootUuid = baseMapper.selectUuidById(comment.getRootId());
if (rootUuid == null) {
throw new MsgException("评论所属楼层已删除");
}
vo.setRootId(rootUuid);
if (comment.getReplyToUserId() != null && replyToUser != null) {

12
src/main/java/com/youlai/boot/mini/service/impl/UserPostServiceImpl.java

@ -246,7 +246,7 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
miniUserPostMediaMapper.update(null, wrapper);
}
// 3. 创建内容审核任务(失败或异常降级为人工审核,不阻塞主流程
// 3. 创建内容审核任务(审核在独立事务中运行,失败降级为人工审核)
Long postId = post.getId();
try {
AuditContentDTO auditContent = buildAuditContent(formData);
@ -306,12 +306,11 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
* 审核异常降级通用版根据已构建的审核内容直接创建人工审核记录
*/
private void createManualReviewFallback(Long postId, AuditContentDTO auditContent) {
try {
MiniContentAudit audit = new MiniContentAudit();
audit.setUuid(UUID.randomUUID().toString());
audit.setModuleCode("user_post");
audit.setBizId(postId);
audit.setAuditType("manual"); // 失败时 全转人工
audit.setAuditType("manual");
audit.setStatus(AuditConstants.STATUS_MANUAL_REVIEW);
audit.setCreateBy(SecurityUtils.getUserId());
audit.setCreateTime(new Date());
@ -328,9 +327,6 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
.update();
log.info("降级人工审核记录已创建, postId={}, auditId={}", postId, audit.getId());
} catch (Exception fallbackEx) {
log.error("降级人工审核记录创建也失败了, postId={}, 作品将处于漏审状态!", postId, fallbackEx);
}
}
@Override
@ -350,7 +346,7 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
post.setUpdateTimestamp(currentTimestamp);
updateById(post);
// 编辑后重新审核(失败或异常降级为人工审核,不阻塞主流程
// 编辑后重新审核(审核在独立事务中运行,失败降级为人工审核)
Long postId = post.getId();
try {
AuditContentDTO auditContent = buildAuditContent(formData);
@ -494,7 +490,7 @@ public class UserPostServiceImpl extends ServiceImpl<MiniUserPostMapper, MiniUse
}
}
// 补充媒体后重新审核(失败或异常降级为人工审核,不阻塞主流程
// 补充媒体后重新审核(审核在独立事务中运行,失败降级为人工审核)
if (!newImageUrls.isEmpty() || !newVideoUrls.isEmpty()) {
Long postId = post.getId();

Loading…
Cancel
Save