当前位置: 首页 > news >正文

SpringBoot + 百度内容安全实战:自定义注解 + AOP 实现统一内容审核(支持文本 / 图片 / 视频 + 白名单 + 动态开关)

🧩 一、为什么要做内容审核

在实际业务(如用户发帖、评论、任务描述、AI生成内容等)中,往往需要防止以下问题:

  • 🧨 用户发布涉政、色情、辱骂等敏感内容;
  • 🖼️ 上传违规图片或视频;
  • 🤖 机器人刷垃圾广告、推广链接;
  • ⚙️ 需要为不同场景设置灵活的开关与白名单。

这篇文章带你从 架构设计代码实现,一步步构建出企业级的 内容审核中间层


📘 二、核心功能概览

支持类型:文本、图片、视频
实现方式:自定义注解 + AOP 自动拦截
集成能力:百度内容安全 API(AipContentCensor
动态配置:从配置中心获取审核开关与白名单(infraConfigApi
精准提示:自动定位哪个字段违规,展示自定义提示信息
业务无侵入:通过注解实现自动校验


🧱 三、完整架构设计图

Controller → Service → (AOP 拦截)↓MyValidAspect↓BaiduCheck工具类↓百度内容审核接口(文本/图片/视频)

⚙️ 四、核心实现代码(附详细注释)

1️⃣ 自定义注解:@ContentCheck

@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

@Slf4j
public class BaiduCheck {// 百度控制台申请的 API_KEY、SECRET_KEYpublic 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:动态配置字典的key
    • infraConfigApi.getJsonObjectByConfigKey(...): 动态控制开关
      • 拿到的是一个json 从json里面取key为textCheck的属性就是开关
      • 从json里取key为textCheckWhiteList 就是用户白名单数组
  • CONTENT_CHECK_ERROR: 自定义业务错误码
    • 可以自己定义
  • SecurityFrameworkUtils.getLoginUserId(): 获取当前用户ID
    • 通过读者自己使用的方式获取
  • ServiceExceptionUtil.exception(): 统一异常抛出工具
    • 直接使用 new RuntimeException()或者自己定义一个工具类
@Aspect
@Slf4j
public class MyValidAspect {@Resourceprivate 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声明式注解,定义检测规则
MyValidAspectAOP 拦截执行统一检测
BaiduCheck封装百度AI审核接口
infraConfigApi动态控制开关 & 白名单
SecurityFrameworkUtils用户上下文信息获取

优势总结:

  • 📦 无侵入接入:业务层零改动;
  • 🧩 配置化管理:审核可动态启停;
  • 🛡️ 白名单机制:灵活控制;
  • ⚙️ 可扩展:后续可接入阿里云、腾讯云多引擎审核;
  • 🧠 智能提示:精准定位违规字段。
http://www.dtcms.com/a/540326.html

相关文章:

  • 心智结构与组织学习
  • NAS 私有云零信任部署:cpolar 加密访问 + 本地存储,破解安全与便捷难题
  • C++面向对象继承全面解析:不能被继承的类、多继承、菱形虚拟继承与设计模式实践
  • 只做财经的网站厦门高端网站建设公
  • 星宿网站建设系统开发过程中原型有哪些作用
  • Angular 入门项目
  • 架构解析:衡石科技如何基于AI+Data Agent重构智能数据分析平台
  • 云栖实录:重构可观测 - 打造大模型驱动的云监控 2.0 与 AIOps 新范式
  • AR技术如何确保数据准确无误?
  • Python-openai对话LLM
  • 智慧码垛系统介绍
  • Axure高保真View Design框架元件库
  • 网站 linux 服务器昆明企业网站开发公司
  • 网站介绍经过下拉怎么做wordpress统计访问ip
  • 济南品牌网站建设低价wordpress建站要钱吗
  • DHT11温湿度传感器Linux驱动开发完整流程
  • EMD-SVM 太阳能功率预测
  • FFMPEG-1:下载与安装,文件组成,ffmpeg -h 命令汇总,练习使用 ffmpeg、ffplay、ffprobe,
  • 蚂蚁开源高性能扩散语言模型框架dInfe,推理速度提升十倍
  • DVWA靶场通关笔记
  • transformer结构
  • Spring Boot 移除 Undertow:技术背景、迁移方案与避坑指南(附源码级解析)
  • 龙岩食品有限公司搜索引擎优化的英文缩写
  • 【Linux】进程状态|优先级|进程切换|环境变量
  • 2025智能体元年:AI重塑产业格局
  • 计网4.4.1 路由算法
  • 038-Spring AI Alibaba VLLM Chat 功能完整案例
  • MCU的时钟树(Clock Tree)是什么?
  • 用 htmi5做网站海南最新消息新闻
  • 深入解析Linux MISC驱动框架