Compare commits
52 Commits
master
...
glx_phase2
| Author | SHA1 | Date |
|---|---|---|
|
|
c243ae918e | 3 days ago |
|
|
fb21d08d3d | 3 days ago |
|
|
e47f65ac45 | 3 days ago |
|
|
c0132e5eef | 3 days ago |
|
|
6bcc35ced8 | 3 days ago |
|
|
b21949693e | 4 days ago |
|
|
ff17ff2847 | 4 days ago |
|
|
1d7928e7fb | 5 days ago |
|
|
3455b64f54 | 5 days ago |
|
|
bfef04ae63 | 1 week ago |
|
|
b756b1a86d | 1 week ago |
|
|
3b7760051e | 1 week ago |
|
|
efff642504 | 2 weeks ago |
|
|
5970ad0847 | 2 weeks ago |
|
|
ee804cf371 | 2 weeks ago |
|
|
f163514d4c | 2 weeks ago |
|
|
b8b6015f56 | 2 weeks ago |
|
|
b4ca8a6e12 | 2 weeks ago |
|
|
252db3db99 | 2 weeks ago |
|
|
abbe200341 | 2 weeks ago |
|
|
98e4be3826 | 2 weeks ago |
|
|
481d72888d | 2 weeks ago |
|
|
9aa59d888a | 2 weeks ago |
|
|
89b0e34eb0 | 2 weeks ago |
|
|
e76df77ce5 | 3 weeks ago |
|
|
33b305b940 | 3 weeks ago |
|
|
9cda7d3986 | 3 weeks ago |
|
|
1b665c2f2c | 3 weeks ago |
|
|
141dca210e | 3 weeks ago |
|
|
a0add60c61 | 3 weeks ago |
|
|
19a4ccfcf9 | 3 weeks ago |
|
|
7e5e1873b6 | 3 weeks ago |
|
|
784b9b4a13 | 3 weeks ago |
|
|
77f2966237 | 3 weeks ago |
|
|
9fdb7d1900 | 3 weeks ago |
|
|
4797810162 | 3 weeks ago |
|
|
8659fbece5 | 3 weeks ago |
|
|
4d1a22f5de | 4 weeks ago |
|
|
3b73dc2135 | 4 weeks ago |
|
|
65fc5f0a81 | 4 weeks ago |
|
|
d138d40869 | 4 weeks ago |
|
|
e5489fe290 | 4 weeks ago |
|
|
90ef9bd7b2 | 4 weeks ago |
|
|
3a1a0c94c4 | 1 month ago |
|
|
60c76855e0 | 1 month ago |
|
|
3bd113ade7 | 1 month ago |
|
|
017f226ac4 | 1 month ago |
|
|
541e18749f | 1 month ago |
|
|
f9313761c3 | 1 month ago |
|
|
8ee2b6d6cb | 1 month ago |
|
|
30b8b59860 | 1 month ago |
|
|
cc5a360712 | 1 month ago |
264 changed files with 17970 additions and 48 deletions
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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> { |
||||
|
|
||||
|
} |
||||
@ -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> { |
||||
|
|
||||
|
} |
||||
@ -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> { |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
package com.youlai.boot.admin.service; |
||||
|
|
||||
|
public interface OssCallbackService { |
||||
|
|
||||
|
/** 处理OSS图片审核增量回调 */ |
||||
|
void handleImageCallback(String body); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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_review,audit 状态设为 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: NONE→passed, MEDIUM→rejected, HIGH→rejected |
||||
|
* normal: NONE→passed, MEDIUM→manual_review, HIGH→rejected |
||||
|
* cautious: NONE→passed, MEDIUM→manual_review, HIGH→manual_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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
@ -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, "")); |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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())); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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> { |
||||
|
|
||||
|
} |
||||
@ -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> { |
||||
|
|
||||
|
} |
||||
@ -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> { |
||||
|
|
||||
|
} |
||||
@ -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> { |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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(); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
|
|
||||
@ -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); |
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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…
Reference in new issue