343 lines
12 KiB
Java
343 lines
12 KiB
Java
/*
|
||
* 自然写教学资源管理与内容分发系统软件 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<String> 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<String> getWarnings() { return warnings; }
|
||
public void addWarning(String w) { this.warnings.add(w); }
|
||
}
|
||
|
||
// ============================================================
|
||
// 审核业务方法
|
||
// ============================================================
|
||
|
||
/**
|
||
* 执行审核操作 PUT /api/v1/resource/audit/{id}
|
||
*
|
||
* @param request 审核请求
|
||
* @return 审核结果
|
||
*/
|
||
public Map<String, Object> 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<String, Object> 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<String, Object> batchAudit(
|
||
List<String> 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<String> 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<String, Object> 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<String, Object> 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<byte[]> 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<String, Object> queryAuditRecords(
|
||
String resourceId,
|
||
String auditorId,
|
||
int page,
|
||
int pageSize
|
||
) {
|
||
logger.info(String.format(
|
||
"查询审核记录: resource=%s, auditor=%s, page=%d",
|
||
resourceId, auditorId, page
|
||
));
|
||
|
||
// List<AuditRecord> records = auditRecordMapper.selectByCondition(
|
||
// resourceId, auditorId, page, pageSize
|
||
// );
|
||
// int total = auditRecordMapper.countByCondition(resourceId, auditorId);
|
||
|
||
Map<String, Object> result = new HashMap<>();
|
||
result.put("code", 0);
|
||
result.put("data", Map.of(
|
||
"total", 0,
|
||
"page", page,
|
||
"items", new ArrayList<>()
|
||
));
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 获取待审核资源数量(仪表盘统计用)
|
||
*/
|
||
public Map<String, Object> getAuditStats() {
|
||
// int pendingCount = resourceMapper.countByStatus("PENDING");
|
||
// int approvedToday = auditRecordMapper.countTodayByAction("APPROVE");
|
||
// int rejectedToday = auditRecordMapper.countTodayByAction("REJECT");
|
||
|
||
Map<String, Object> result = new HashMap<>();
|
||
result.put("code", 0);
|
||
result.put("data", Map.of(
|
||
"pending_count", 0,
|
||
"approved_today", 0,
|
||
"rejected_today", 0
|
||
));
|
||
return result;
|
||
}
|
||
}
|