/* * 自然写教学资源管理与内容分发系统软件 V1.0 * service/AuditService.java - 内容审核服务 */ package com.writech.resource.service; import java.util.*; import java.util.logging.Logger; /** * 内容审核服务 * * 教师上传的资源需经过管理员审核后才能被其他用户检索和使用。 * 审核流程支持: * - 自动预审(AI内容安全检测) * - 人工审核(管理员审核通过/驳回/退回修改) * - 审核记录全程留痕 * - 批量审核 */ public class AuditService { private static final Logger logger = Logger.getLogger(AuditService.class.getName()); // ============================================================ // 审核数据模型 // ============================================================ /** 审核操作类型 */ public enum AuditAction { APPROVE, // 审核通过 REJECT, // 驳回 RETURN, // 退回修改 WITHDRAW // 上传者撤回 } /** 审核记录(对应MySQL audit_record表) */ public static class AuditRecord { private String id; private String resourceId; private String resourceName; private String auditorId; // 审核人ID private String auditorName; // 审核人姓名 private AuditAction action; private String comment; // 审核意见 private String preStatus; // 审核前状态 private String postStatus; // 审核后状态 private Date createdAt; // Getter/Setter public String getId() { return id; } public void setId(String id) { this.id = id; } public String getResourceId() { return resourceId; } public void setResourceId(String rid) { this.resourceId = rid; } public String getResourceName() { return resourceName; } public void setResourceName(String name) { this.resourceName = name; } public String getAuditorId() { return auditorId; } public void setAuditorId(String id) { this.auditorId = id; } public AuditAction getAction() { return action; } public void setAction(AuditAction action) { this.action = action; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public Date getCreatedAt() { return createdAt; } } /** 审核请求 */ public static class AuditRequest { private String resourceId; private AuditAction action; private String comment; private String auditorId; public String getResourceId() { return resourceId; } public void setResourceId(String id) { this.resourceId = id; } public AuditAction getAction() { return action; } public void setAction(AuditAction action) { this.action = action; } public String getComment() { return comment; } public void setComment(String c) { this.comment = c; } public String getAuditorId() { return auditorId; } public void setAuditorId(String id) { this.auditorId = id; } } /** 自动预审结果 */ public static class PreAuditResult { private boolean safe; private double safeScore; // 安全评分(0-1) private List warnings; // 警告信息 private String category; // 内容分类 public PreAuditResult(boolean safe, double score) { this.safe = safe; this.safeScore = score; this.warnings = new ArrayList<>(); } public boolean isSafe() { return safe; } public double getSafeScore() { return safeScore; } public List getWarnings() { return warnings; } public void addWarning(String w) { this.warnings.add(w); } } // ============================================================ // 审核业务方法 // ============================================================ /** * 执行审核操作 PUT /api/v1/resource/audit/{id} * * @param request 审核请求 * @return 审核结果 */ public Map performAudit(AuditRequest request) { logger.info(String.format( "执行审核: resource=%s, action=%s, auditor=%s", request.getResourceId(), request.getAction(), request.getAuditorId() )); // 查询资源当前状态 // ResourceMetadata resource = resourceMapper.selectById(request.getResourceId()); // if (resource == null) { // return errorResponse(404, "资源不存在"); // } // 状态机校验:只有PENDING状态可被审核 // if (resource.getAuditStatus() != AuditStatus.PENDING) { // return errorResponse(400, "当前状态不可审核"); // } // 创建审核记录 AuditRecord record = new AuditRecord(); record.setId(UUID.randomUUID().toString().replace("-", "")); record.setResourceId(request.getResourceId()); record.setAuditorId(request.getAuditorId()); record.setAction(request.getAction()); record.setComment(request.getComment()); record.setPreStatus("PENDING"); // 根据审核动作更新资源状态 String newStatus; switch (request.getAction()) { case APPROVE: newStatus = "APPROVED"; // 审核通过后,同步更新Elasticsearch索引状态 // updateEsAuditStatus(request.getResourceId(), "APPROVED"); // 预热CDN缓存(使资源可被终端下载) // cdnService.preheatResource(request.getResourceId()); break; case REJECT: newStatus = "REJECTED"; break; case RETURN: newStatus = "PENDING"; // 退回修改后重新提交 break; default: newStatus = "PENDING"; } record.setPostStatus(newStatus); // 持久化 // auditRecordMapper.insert(record); // resourceMapper.updateAuditStatus(request.getResourceId(), newStatus); // 通知上传者审核结果(消息推送) // notifyUploader(request.getResourceId(), request.getAction(), request.getComment()); Map result = new HashMap<>(); result.put("code", 0); result.put("message", "审核操作成功"); result.put("data", Map.of( "resource_id", request.getResourceId(), "new_status", newStatus, "audit_record_id", record.getId() )); return result; } /** * 批量审核 * * @param resourceIds 资源ID列表 * @param action 审核动作 * @param comment 审核意见 * @param auditorId 审核人 * @return 批量审核结果 */ public Map batchAudit( List resourceIds, AuditAction action, String comment, String auditorId ) { logger.info(String.format( "批量审核: count=%d, action=%s", resourceIds.size(), action )); int successCount = 0; int failCount = 0; List failedIds = new ArrayList<>(); for (String resourceId : resourceIds) { try { AuditRequest request = new AuditRequest(); request.setResourceId(resourceId); request.setAction(action); request.setComment(comment); request.setAuditorId(auditorId); Map result = performAudit(request); if ((int) result.get("code") == 0) { successCount++; } else { failCount++; failedIds.add(resourceId); } } catch (Exception e) { failCount++; failedIds.add(resourceId); logger.warning("批量审核失败: resource=" + resourceId + ", error=" + e.getMessage()); } } Map result = new HashMap<>(); result.put("code", 0); result.put("data", Map.of( "total", resourceIds.size(), "success", successCount, "failed", failCount, "failed_ids", failedIds )); return result; } /** * AI自动预审 * * 在人工审核前,自动进行内容安全检测: * - 文本内容是否包含违禁词 * - 图片是否包含不当内容 * - 文件格式是否合规 * - 文件大小是否超限 * * @param resourceId 资源ID * @return 预审结果 */ public PreAuditResult performPreAudit(String resourceId) { logger.info("AI预审: resource=" + resourceId); PreAuditResult result = new PreAuditResult(true, 1.0); // 1. 文件格式和大小检查 // ResourceMetadata resource = resourceMapper.selectById(resourceId); // if (resource.getFileSize() > MAX_FILE_SIZE) { // result = new PreAuditResult(false, 0.0); // result.addWarning("文件大小超过限制"); // return result; // } // 2. 文本内容安全检测(提取PDF/PPT中的文字进行违禁词检查) // String textContent = extractTextContent(resource.getFileKey()); // ContentSafetyResult textSafety = contentSafetyApi.checkText(textContent); // if (!textSafety.isSafe()) { // result.addWarning("文本内容包含敏感词: " + textSafety.getDetails()); // } // 3. 图片内容安全检测(提取文档中的图片进行AI审核) // List images = extractImages(resource.getFileKey()); // for (byte[] image : images) { // ImageSafetyResult imageSafety = contentSafetyApi.checkImage(image); // if (!imageSafety.isSafe()) { // result.addWarning("图片内容不合规: " + imageSafety.getCategory()); // } // } // 综合评分 if (!result.getWarnings().isEmpty()) { double penalty = result.getWarnings().size() * 0.2; double finalScore = Math.max(0.0, 1.0 - penalty); result = new PreAuditResult(finalScore >= 0.6, finalScore); } logger.info(String.format( "预审完成: resource=%s, safe=%b, score=%.2f", resourceId, result.isSafe(), result.getSafeScore() )); return result; } /** * 查询审核记录列表 * * @param resourceId 资源ID(可选,为空则查所有) * @param auditorId 审核人ID(可选) * @param page 页码 * @param pageSize 每页大小 * @return 审核记录列表 */ public Map queryAuditRecords( String resourceId, String auditorId, int page, int pageSize ) { logger.info(String.format( "查询审核记录: resource=%s, auditor=%s, page=%d", resourceId, auditorId, page )); // List records = auditRecordMapper.selectByCondition( // resourceId, auditorId, page, pageSize // ); // int total = auditRecordMapper.countByCondition(resourceId, auditorId); Map result = new HashMap<>(); result.put("code", 0); result.put("data", Map.of( "total", 0, "page", page, "items", new ArrayList<>() )); return result; } /** * 获取待审核资源数量(仪表盘统计用) */ public Map getAuditStats() { // int pendingCount = resourceMapper.countByStatus("PENDING"); // int approvedToday = auditRecordMapper.countTodayByAction("APPROVE"); // int rejectedToday = auditRecordMapper.countTodayByAction("REJECT"); Map result = new HashMap<>(); result.put("code", 0); result.put("data", Map.of( "pending_count", 0, "approved_today", 0, "rejected_today", 0 )); return result; } }