🧩 一、为什么要做内容审核
在实际业务(如用户发帖、评论、任务描述、AI生成内容等)中,往往需要防止以下问题:
- 🧨 用户发布涉政、色情、辱骂等敏感内容;
- 🖼️ 上传违规图片或视频;
- 🤖 机器人刷垃圾广告、推广链接;
- ⚙️ 需要为不同场景设置灵活的开关与白名单。
这篇文章带你从 架构设计 到 代码实现 ,一步步构建出企业级的 内容审核中间层。
📘 二、核心功能概览
✅ 支持类型 :文本、图片、视频
✅ 实现方式 :自定义注解 + AOP 自动拦截
✅ 集成能力 :百度内容安全 API(AipContentCensor)
✅ 动态配置 :从配置中心获取审核开关与白名单(infraConfigApi)
✅ 精准提示 :自动定位哪个字段违规,展示自定义提示信息
✅ 业务无侵入:通过注解实现自动校验
🧱 三、完整架构设计图
plain
Controller → Service → (AOP 拦截)
↓
MyValidAspect
↓
BaiduCheck工具类
↓
百度内容审核接口(文本/图片/视频)
⚙️ 四、核心实现代码(附详细注释)
1️⃣ 自定义注解:@ContentCheck
java
@Target({ElementType.TYPE_USE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ContentCheck {
/** 内容类型枚举 */
enum ContentType {
TEXT, // 文本内容
IMAGE // 图片内容
}
/** 检查失败时的提示信息 */
String message() default "内容检查不合规";
/** 默认是文本类型 */
ContentType value() default ContentType.TEXT;
}
🔹 说明:
- 可标记在字段或方法上;
- 每个字段都可以定义自己的
message()提示内容; - 区分文本与图片类型。
2️⃣ 百度内容安全工具类:BaiduCheck
java
@Slf4j
public class BaiduCheck {
// 百度控制台申请的 API_KEY、SECRET_KEY
public static final String API_KEY = "xxxxxxxxxxxxx";
public static final String SECRET_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
// 初始化内容审核客户端
static AipContentCensor client = new AipContentCensor("222222222", API_KEY, SECRET_KEY);
/**
* 文本审核(简单判断:true 为安全)
*/
public static boolean baiduTextCheck(String text) throws IOException {
if (StringUtils.isEmpty(text)) {
return true;
}
JSONObject jsonObject = client.textCensorUserDefined(text);
return jsonObject.getInt("conclusionType") <= 1;
}
/**
* 文本审核(返回第一个命中的敏感词;null 表示安全)
*/
public static String baiduTextCheck01(String text) {
if (StringUtils.isEmpty(text)) {
return null;
}
JSONObject jsonOriginObject = client.textCensorUserDefined(text);
JsonNode jsonObject = JsonUtils.parseTree(jsonOriginObject.toString());
log.info("文本:{}, 审核结果:{}", text, jsonObject.toString());
if (jsonObject.has("conclusionType") && jsonObject.get("conclusionType").asInt() > 1) {
try {
// ⚙️ 白名单优先:type=14 的内容被百度标记为"合规"
if (jsonObject.get("data") != null) {
for (JsonNode next : jsonObject.get("data")) {
if (next.get("type").asInt() == 14) {
return null;
}
}
}
// 返回第一个命中的词
return jsonObject.get("data").get(0).get("hits").get(0).get("words").get(0).asText();
} catch (Exception e) {
return "";
}
}
return null;
}
/**
* 图片检测(任一违规则返回 false)
*/
public static boolean baiduImageCheck(String... urls) {
try {
for (String url : urls) {
JSONObject jsonObject = client.imageCensorUserDefined(url, EImgType.URL, null);
if (jsonObject.getInt("conclusionType") > 1) {
return false;
}
}
} catch (Exception e) {
log.error("图片检测异常", e);
}
return true;
}
/**
* 视频检测(违规即返回 false)
*/
public static boolean baiduSortVideoCheck(String name, String videoUrl, String id) {
JSONObject jsonObject = client.videoCensorUserDefined(name, videoUrl, id, null);
return jsonObject.getInt("conclusionType") <= 1;
}
/**
* 生成 Access Token,用于 NLP 检查
*/
static String getAccessToken() throws IOException {
OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build();
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"),
"grant_type=client_credentials&client_id=" + API_KEY + "&client_secret=" + SECRET_KEY);
Request request = new Request.Builder()
.url("https://aip.baidubce.com/oauth/2.0/token")
.method("POST", body)
.build();
Response response = HTTP_CLIENT.newCall(request).execute();
return new JSONObject(response.body().string()).getString("access_token");
}
}
3️⃣ 核心切面:MyValidAspect
🧭 业务集成点标注说明
infraConfigApi: 从配置中心读取审核开关、白名单
InfraConfigEnum.ConfigKeyEnum.TASK_SWITCH_CONFIG:动态配置字典的keyinfraConfigApi.getJsonObjectByConfigKey(...): 动态控制开关
- 拿到的是一个json 从json里面取key为
textCheck的属性就是开关- 从json里取key为
textCheckWhiteList就是用户白名单数组CONTENT_CHECK_ERROR: 自定义业务错误码
- 可以自己定义
SecurityFrameworkUtils.getLoginUserId(): 获取当前用户ID
- 通过读者自己使用的方式获取
ServiceExceptionUtil.exception(): 统一异常抛出工具
- 直接使用 new RuntimeException()或者自己定义一个工具类
java
@Aspect
@Slf4j
public class MyValidAspect {
@Resource
private InfraConfigApi infraConfigApi; // ⚙️ 业务配置中心,用于获取开关与白名单
/**
* 拦截所有标注了 @ContentCheck 的方法
*/
@Before("@annotation(contentCheck)")
public void doBefore(JoinPoint joinPoint, ContentCheck contentCheck) {
StringBuffer sbText = new StringBuffer();
List<String> imageLst = new ArrayList<>();
Arrays.stream(joinPoint.getArgs()).forEach(e -> {
Class<?> clazz = e.getClass();
HashMap<ContentCheck, String> fieldValueHashMap = new HashMap<>();
// 反射扫描参数中的带注解字段
Arrays.stream(ReflectUtil.getFields(clazz))
.filter(field -> field.isAnnotationPresent(ContentCheck.class))
.forEach(field -> {
ContentCheck annotation = field.getAnnotation(ContentCheck.class);
String fieldValue = (String) ReflectUtil.getFieldValue(e, field);
if (StrUtil.isEmpty(fieldValue)) {
return;
}
// 根据类型分类收集
if (annotation.value() == ContentCheck.ContentType.TEXT) {
sbText.append(fieldValue);
fieldValueHashMap.put(annotation, fieldValue);
} else if (annotation.value() == ContentCheck.ContentType.IMAGE) {
imageLst.addAll(Arrays.asList(fieldValue.split(",")));
}
});
// === 📘 文本检测逻辑 ===
String sensitiveStr = null;
if (sbText.length() > 0) {
sensitiveStr = textCheck01(sbText.toString());
}
// 审核通过则直接返回
if (sensitiveStr == null) {
textSuccessResult = true;
return;
}
// 图片的校验
// if (textSuccessResult && !imageLst.isEmpty() && BaiduCheck.baiduImageCheck(imageLst.toArray(new String[0]))) {
// return true;
// }
// 命中违规内容:匹配字段并取对应 message 提示
String msg = contentCheck.message();
for (Map.Entry<ContentCheck, String> next : fieldValueHashMap.entrySet()) {
if (next.getValue().contains(sensitiveStr)) {
msg = next.getKey().message();
break;
}
}
// 抛出统一业务异常
throw ServiceExceptionUtil.exception(CONTENT_CHECK_ERROR, msg);
});
}
/**
* 普通文本检测(受配置中心控制)
*/
public boolean textCheck(String content) {
JSONObject json = infraConfigApi.getJsonObjectByConfigKey(InfraConfigEnum.ConfigKeyEnum.TASK_SWITCH_CONFIG);
if (json.getBool("textCheck").equals(false)) {
return true; // 配置关闭时直接跳过
}
try {
return BaiduCheck.baiduTextCheck(content);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 文本检测(带白名单逻辑)
*/
public String textCheck01(String content) {
JSONObject json = infraConfigApi.getJsonObjectByConfigKey(InfraConfigEnum.ConfigKeyEnum.TASK_SWITCH_CONFIG);
if (json.getBool("textCheck").equals(false)) {
return null;
}
try {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); // ⚙️ 当前登录用户ID(业务相关)
JSONArray textCheckWhiteList = json.getJSONArray("textCheckWhiteList");
if (loginUserId != null && CollectionUtil.isNotEmpty(textCheckWhiteList)) {
List<Long> list = textCheckWhiteList.toList(Long.class);
if (list.contains(loginUserId)) {
return null; // 白名单用户跳过审核
}
}
} catch (Exception e) {
log.error("内容检查白名单error: {}", e.getMessage());
}
return BaiduCheck.baiduTextCheck01(content);
}
}
🧩 五、使用示例


✅ 六、运行效果

🧠 七、总结
| 功能模块 | 说明 |
|---|---|
@ContentCheck |
声明式注解,定义检测规则 |
MyValidAspect |
AOP 拦截执行统一检测 |
BaiduCheck |
封装百度AI审核接口 |
infraConfigApi |
动态控制开关 & 白名单 |
SecurityFrameworkUtils |
用户上下文信息获取 |
✨ 优势总结:
- 📦 无侵入接入:业务层零改动;
- 🧩 配置化管理:审核可动态启停;
- 🛡️ 白名单机制:灵活控制;
- ⚙️ 可扩展:后续可接入阿里云、腾讯云多引擎审核;
- 🧠 智能提示:精准定位违规字段。