用户生成内容(UGC)几乎是目前所有互联网产品的标配能力,无论是商品评论、帖子回复还是弹幕聊天,都离不开文本输入框。但只要开放输入,就会有人发广告、刷引流链接,甚至发布违法违规内容,平台方因此承担着直接的合规责任。与其事后删帖补救,不如在内容提交入口就做一次机器审核。本文将以 Spring Boot 项目为背景,演示如何整合第三方文本审核接口,对评论内容做自动合规检查。

文本审核的两种主流方案
在动手写代码之前,先明确技术选型。目前实现文本内容审核主要有两条路:一是接入云厂商的内容安全服务,例如阿里云内容安全、腾讯云天御、网易云易盾等;二是自建本地敏感词库,通过 DFA 算法或 AC 自动机做匹配。
云服务的优势很明显:词库由专业团队持续维护,覆盖涉政、色情、广告、辱骂等多个维度,还能识别变体写法(比如用拼音、谐音、特殊符号混淆的敏感词),准确率高且无需自己运营。缺点是按量计费,量大了成本不低,而且存在网络调用延迟和可用性风险。
本地敏感词库方案胜在零成本、毫秒级响应、不依赖外部服务,但词库质量全靠自己维护,对变形词的识别能力弱。生产环境中比较稳妥的做法是两者结合:云服务做主审核通道,本地词库做兜底,当云接口超时或异常时降级走本地匹配,既保证可用性又控制成本。下文按照这个思路展开。
接入阿里云内容安全 SDK
这里以阿里云内容安全为例。首先在 pom.xml 中引入官方 SDK 依赖:
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-green</artifactId>
<version>3.6.6</version>
</dependency>接着在 application.yml 中配置 AccessKey 和地域信息。要注意 AccessKeySecret 属于敏感信息,不要硬编码进代码仓库,建议通过环境变量或配置中心注入:
aliyun:
green:
access-key-id: ${ALIYUN_AK_ID}
access-key-secret: ${ALIYUN_AK_SECRET}
region: cn-hangzhou然后封装一个审核服务类,屏蔽 SDK 的调用细节,对外只暴露一个简单方法。这样设计的好处是,将来如果更换审核厂商,业务代码完全不用动,只需替换这个实现类:
@Service
public class TextAuditService {
@Value("${aliyun.green.access-key-id}")
private String accessKeyId;
@Value("${aliyun.green.access-key-secret}")
private String accessKeySecret;
@Value("${aliyun.green.region}")
private String region;
public AuditResult audit(String content) {
try {
DefaultProfile profile = DefaultProfile.getProfile(region, accessKeyId, accessKeySecret);
IAcsClient client = new DefaultAcsClient(profile);
TextScanRequest request = new TextScanRequest();
request.setAcceptFormat(FormatType.JSON);
Map<String, Object> task = new HashMap<>();
task.put("content", content);
request.setTasks(Collections.singletonList(task));
request.setScenes(Collections.singletonList("antispam"));
TextScanResponse response = client.getAcsResponse(request);
return parseResponse(response);
} catch (Exception e) {
// 云端调用失败,降级为本地敏感词审核
return localFallbackAudit(content);
}
}
private AuditResult parseResponse(TextScanResponse response) {
List<TextScanResponse.TextResult> results = response.getData();
if (results == null || results.isEmpty()) {
return AuditResult.pass();
}
for (TextScanResponse.TextResult result : results) {
if ("pass".equals(result.getSuggestion())) {
continue;
}
// review 表示需要人工复核,block 表示直接拦截
return new AuditResult(false, result.getSuggestion(), result.getReason());
}
return AuditResult.pass();
}
private AuditResult localFallbackAudit(String content) {
// 调用本地 DFA 敏感词匹配作为兜底
return SensitiveWordMatcher.match(content);
}
}审核接口的返回结果通常有三种建议值:pass 表示通过,review 表示疑似违规需人工复核,block 表示明确违规直接拦截。业务上一般对 pass 直接放行并落库展示,对 block 直接拒绝提交并给用户提示,对 review 则先落库但标记为不可见状态,等人工审核后再决定是否展示。
用 AOP 切面实现无侵入拦截
评论提交的接口可能不止一个,如果每个 Controller 里都写一段审核逻辑,代码重复且容易遗漏。更优雅的方式是自定义一个注解,配合 AOP 切面统一拦截。先定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAudit {
String contentField() default "content";
}然后编写切面,在方法执行前取出评论内容做审核,不合规则直接抛出业务异常,阻断请求:
@Aspect
@Component
public class AuditAspect {
@Autowired
private TextAuditService textAuditService;
@Around("@annotation(requireAudit)")
public Object around(ProceedingJoinPoint joinPoint, RequireAudit requireAudit) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg == null) {
continue;
}
Field field = ReflectionUtils.findField(arg.getClass(), requireAudit.contentField());
if (field != null) {
ReflectionUtils.makeAccessible(field);
Object value = ReflectionUtils.getField(field, arg);
if (value instanceof String) {
AuditResult result = textAuditService.audit((String) value);
if (!result.isPass()) {
throw new BizException("内容包含违规信息,请修改后重新提交");
}
}
}
}
return joinPoint.proceed();
}
}使用时只需在接口方法上加一个 @RequireAudit 注解,业务代码一行都不用改,这就是切面方案的最大价值。需要注意的是,切面里用了反射获取字段,如果 DTO 字段改名会导致静默失效,建议把注解加在 DTO 类上并通过字段注解标记审核目标,或者干脆约定统一的方法签名,减少反射带来的脆弱性。
异步化改造与注意事项
云审核接口毕竟是一次远程调用,网络抖动时可能拖慢评论提交的响应速度。对审核实时性要求不高的场景,可以把审核放到异步线程中执行:评论先落库并标记为待审核状态,后台线程完成审核后再更新状态。Spring Boot 中用 @Async 配合线程池即可实现:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("auditExecutor")
public Executor auditExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("audit-");
executor.initialize();
return executor;
}
}
@Async("auditExecutor")
public void auditAsync(Long commentId, String content) {
AuditResult result = textAuditService.audit(content);
commentMapper.updateAuditStatus(commentId,
result.isPass() ? AuditStatus.PASSED : AuditStatus.REJECTED);
}除了异步化,还有几个细节容易被忽略。第一是超时设置,SDK 默认超时可能长达几十秒,务必显式设置较短的连接和读取超时(比如 3 秒),配合兜底策略快速失败。第二是限流,云接口按量计费,如果有人恶意高频提交评论会产生大量审核费用,建议在网关层或接口层做频率限制。第三是日志留存,无论审核通过与拒绝,都应记录原始内容、审核结果和耗时,方便后续排查误判和申诉处理。
最后提一下误判问题。机器审核难免有误伤,比如正常讨论医疗话题的评论可能被标记为疑似违规。因此 review 状态的人工复核后台必不可少,同时给用户提供申诉入口,把申诉内容优先送入人工队列处理,这样才能在合规和用户体验之间取得平衡。
Spring Boot文本审核内容合规修改时间:2026-09-04 18:47:09