内容安全服务

服务设计的初衷和背景

由于上半年工信部监管变得异常严格,很多 APP 因为各种原因被下架,特别是内容服务类的 APP,所以关于内容安全这块可以说是涉及到了公司的命脉,这块不能小视。

设计这个 safety_service(内容安全服务),主要基于业务的以下几个痛点:

  • 1.由于历史迭代。人员更替等的原因,原先各个模块下的视频、图片,文本等的审核业务代码分散,不便管理。
  • 2.为了方便统一入口,让上游调用方全部以 单点 触发审核任务,且查询结果也保证 单点 的出口。
  • 3.将第三方审核 API 的并发额度最大化利用。(阿里内容安全,头条内容审核,腾讯云等等各家厂商)
  • 4.复杂资源对象的整体审核。
1.原先各个模块下的视频、图片,文本等的审核业务代码分散:

比如在上传 UGC 作品处,会在上传完之后触发异步审核业务,审核结果成功与否再触发回调,去修改相对应的作品上线状态,推荐级别等属性值。

在用户个人中心修改头像、背景图、昵称、个人简介等等信息之后,又会触发图片的异步审核和文本的同步审核,

在话题帖子中,上传图片和发表文章,也会触发内容审核,

在视频或图片详情页等下方留言评论,评论也会加上人工审核(尤其今年上半年工信部审核变严后,基本上评论都要走文本审核)

....

每一处业务逻辑处,都是单独建表,自己写回调逻辑,自己埋点触发任务等。然后获取历史审核结果时也是从各自的审核记录表里边拉取记录。

但我们发现各个时期写的审核模块,其记录的审核结果的结构都不一致,导致结果有的很简略,有的就比较详细。且如果要汇总所有内容的审核结果,这样的统计代码写起来工作量更是翻了几倍。

2.为了方便统一入口,单点触发审核任务,且查询结果也保证单点的出口:

这样做的好处是,所有媒体内容的审核,都由单点触发,表结构也是一整套结构,需要用的审核结果字段可以动态按需获取。另外同样的资源内容也可以在单一服务中进行去重,不占用第三方审核服务的接口使用额度。

使得不管开辟多少个新服务,都能让代码更简洁!

3.将第三方审核 API 的并发额度最大化利用。

这其实是由于第三方额度的限制。

比如阿里的内容安全 API,普通企业版开通后,对于视频、图片、文本的并发路数是由限制的。

image-20210801214459750

(参考:https://help.aliyun.com/document_detail/70409.html?spm=5176.14908214.0.0.3d407e9cY1QTQ8

https://www.aliyun.com/price/product/?spm=a2c4g.11186623.2.22.c034c437j2Y0Mt#/lvwang/detail/cdibag

)

也就是所有调用阿里接口的地方,其实是共用一套额度。如果比方说上传作品处接口瞬时流量增大,并发量赠大,占用掉了所有并发额度。那么个人主页处再想提交审核任务,阿里的API 会返回错误码 588(EXCEED DATA) 并发额度超出。

做了这个 内容审核 service 之后,由单点统一管控并发额度,所有地方的任务都均匀地提交审核,并响应回调结果。这样上游业务方只关心提交任务,并写好回调逻辑即可。至于中间是否因瞬时额度超出而导致任务提交失败,或者需要重试等等,均不关心!

4.复杂资源对象的整体审核。

传统的审核任务是以细化的媒体资源为单位的,比如我现在审核一个用户上传的视频,视频是涉黄涉恐或是广告,然后根据结果去执行后续的业务逻辑。

但有些场景,比如涉及到作品推荐级别,作品可见性的场景。我们要求作品的复合属性需要全部审核通过

比如用户上传了一个视频,视频属性包括

  • 视频 url 链接
  • 视频标题
  • 视频描述,
  • 设置的封面图

这四个属性。

业务方需要当视频的各个属性都审核通过时,才设置作品为 “线上可见”,或者推荐级别为推荐或精选等等。

如果传统的分散编写代码,它们需要分别记录这四个单独的任务的回调结果,自己维持这个临时记录,然后不断轮询结果,判断当所有子任务都审核过的时候,才触发后续的业务逻辑(修改作品可见性,推荐级别等等)。

显然临时结果的存储以及后续判断状态的轮询,对于业务上游的调用方都是额外的负担,不仅要单独建表(或redis),还要写定时任务去一直轮询。

所以我在这个 service 里设计了 组任务(group_task) 的概念。

这个组任务的概念涉及到两个方面:

  1. 提交任务时,任务参数提交的是多个任务的数组,也就是可以提交一个或多个子任务,每个子任务必须是视频、图片地址或是文本内容。同时可以附带上相应的回调函数(callback)。
  2. 只有当一个 group_task 的所有子任务审核状态都为通过 (pass)时,group_task 的结果才为 pass,否则要么为审核中(submitted,还没审核完),要么为子任务中最严重的等级(若出现疑似(review),则组任务最终状态为 review,出现违规(block),则组任务最终状态为 block)。

这样就解决了复合对象的整体审核问题,调用方只关心提交任务和查询结果(或处理回调),不关心中途是如何记录子任务状态,也不用自己去写失败重试逻辑等。

后来经过内部讨论,决定整合所有内容安全的审核业务,独立出来这个服务( safety_service).

表结构ER图:

表结构ER图

设计流程图:

https://upload.cc/i1/2021/08/02/KQgYXU.png

具体详见博客文章:http://www.liuyang1.com/content-safety

监测指标:

  1. 平均从提交任务,到返回结果至更新表 status 字段所耗时:(平均文本1-3 秒,图片2-4 秒,视频取决于时长 1~2min +)
  2. 任务超时率(一定时间内未接收回调并更新表 status)
  3. 任务提交失败率。(常见错误码 588 EXCEED DATA , 超出瞬时并发检测路数限制)
  4. 任务回调的失败率。(见附表:阿里API返回错误码
  5. 任务平均重试次数

SQL 设计:

1. 组任务审核记录表:xxx_resource_safety_validation_group_task

```DROP TABLE IF EXISTS xxx_resource_safety_validation_group_task;

CREATE TABLE xxx_resource_safety_validation_group_task (

`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',

`group_id` varchar(100) NOT NULL COMMENT '审核组 ID',

`group_task_status`     varchar(50)  NOT NULL DEFAULT 'submitted' COMMENT 'task状态,processing——正在处理中, failed——审核失败, submitted——已提交, success——审核成功',

`resource_hash_mappings` mediumtext COMMENT '关联的 resource_hash,多个之间用逗号分割',

`callback` varchar(255) NOT NULL DEFAULT '' COMMENT '调用方传入的结果回调url',

`raw_task_info` mediumtext COMMENT '原任务信息,encode(task_params)',

`ctime`           datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`utime`           datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',

PRIMARY KEY (`id`),

UNIQUE KEY `uniq_group_id` (`group_id`),

KEY `index_group_task_status` (`group_task_status`),

KEY `index_utime` (`utime`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx资源内容安全,组审核任务';

2. 子任务审核结果(sharding:20张表):xxx_resource_safety_validation_results_new
-- 分表二十张
DROP TABLE IF EXISTS `xxx_resource_safety_validation_results_new`;
CREATE TABLE `xxx_resource_safety_validation_results_new`
(
    `id`              int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `validation_type` varchar(50)  NOT NULL DEFAULT 'aliyun' COMMENT '审核方式,aliyun——阿里内容安全接口',
    `task_id`         varchar(100) NOT NULL COMMENT '阿里内容安全 taskId',
    `task_status`     varchar(50)  NOT NULL DEFAULT 'submitted' COMMENT 'task状态,processing——正在处理中, failed——审核失败, submitted——已提交, success——审核成功',
    `task_result`     varchar(100) NOT NULL DEFAULT '' COMMENT '任务执行结果详情,DOWNLOAD_TIMEOUT —— 资源链接下载失败, ...',
    `resource_hash`    varchar(40) NOT NULL COMMENT 'sha1(resource_type + resource_url + text_content)',
    `content_hash` varchar(50) NOT NULL COMMENT '内容去重hash,地址不同可能 hash 相同,防止重复内容提交',
    `resource_type`   varchar(100) NOT NULL COMMENT '资源类型,text--文本类型,image--图片类型,video--视频类型,voice--音频类型',
    `callback` varchar(255) NOT NULL DEFAULT '' COMMENT '调用方传入的结果回调url',
    `validation_result`    varchar(32) NOT NULL DEFAULT '' COMMENT '正常——pass, review——疑似, block——违规',
    `validation_label`    varchar(2000) NOT NULL DEFAULT '' COMMENT '审核不过的结果json,包含label: porn(涉黄), terrorism(涉恐),...',
    `ctime`           datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `utime`           datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uniq_task_id` (`task_id`),
    UNIQUE KEY `uniq_resource_hash` (`resource_hash`),
    KEY               `index_validation_result` (`validation_result`),
    KEY               `index_ctime` (`ctime`),
    KEY               `index_utime` (`utime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx资源内容安全审核结果_0';
3.子任务resource_hash - 详情映射表(sharding:20张表):xxx_resource_safety_validation_resource_hash_to_detail
DROP TABLE IF EXISTS `xxx_resource_safety_validation_resource_hash_to_detail`;
CREATE TABLE `xxx_resource_safety_validation_resource_hash_to_detail`
(
    `id`              int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `resource_hash`    varchar(40) NOT NULL COMMENT 'sha1(resource_type + resource_url + text_content)',
    `resource_url`    varchar(255) NOT NULL DEFAULT '' COMMENT '资源链接(非文本类型)',
    `text_content` mediumtext COMMENT '文本详情',
    `ext_info` varchar(255) NOT NULL DEFAULT '' COMMENT '调用方附带的额外信息',
    `ctime`           datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `utime`           datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uniq_resource_hash` (`resource_hash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx资源内容安全,资源“hash-详情”映射表';
4.三方平台审核任务ID - resource_hash 映射表(sharding:20张表):xxx_resource_safety_validation_task_id_to_resource
DROP TABLE IF EXISTS `xxx_resource_safety_validation_task_id_to_resource`;
CREATE TABLE `xxx_resource_safety_validation_task_id_to_resource`
(
    `task_id` varchar(50) NOT NULL COMMENT '阿里的回调是以 taskId 做唯一标识,此表方便反查定位 resource_hash 来更新记录',
    `resource_hash`    varchar(40) NOT NULL COMMENT 'sha1(resource_type + resource_url + text_content)',
    PRIMARY KEY (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx资源内容安全,“task_id-resource_hash”映射表';
5.资源内容特征hash - resource_hash 映射表(sharding:20张表):xxx_resource_safety_validation_content_hash_to_resource
DROP TABLE IF EXISTS `xxx_resource_safety_validation_content_hash_to_resource`;
CREATE TABLE `xxx_resource_safety_validation_content_hash_to_resource`
(
    `content_hash` varchar(50) NOT NULL COMMENT '内容去重hash,地址不同可能 hash 相同,防止重复内容提交',
    `resource_hash`    varchar(40) NOT NULL COMMENT 'sha1(resource_type + resource_url + text_content)',
    PRIMARY KEY (`content_hash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='xxx资源内容安全,“content_hash-resource_hash”映射表';

阿里API 相关问题:

1.返回 code 592, download_timeout:

image-20210723173446201

实际测下来发现图片尺寸和大小未超,反馈阿里问是不是下载超时 3s,给的 CDN 地址按道理不会下载很慢,所以让那边帮忙查 log 看具体是不是下载超时?

2.任务回调结果失败,定期补偿重试?重试三次仍失败则标记任务失败。

由于传过去的 URL 图片类型是 CDN 地址,但 CDN 地址有可能 cdn 缓存没刷过,导致第一次下载超时(阿里设置的下载 5s 超时),所以子任务会有一定次数的重试机制。

当 task_status = failed 或 submitted 时,回调失败触发一次子任务重试,retry_count 字段 + 1

风控部分:

1.【灰产】有人用自动工具频繁上传同一视频,每次把标题做部分修改替换然后上传,这部分人需要识别出 UID,将其作品标识为 “仅自己可见”:

审核任务同步异步形式及检测标签范围:

资源类型同步/异步检测范围
图片【√】同步支持图片智能鉴黄、图片暴恐涉政、图文违规、图片二维码、图片不良场景、图片logo
【√】异步支持图片智能鉴黄、图片暴恐涉政、图文违规、图片二维码、图片不良场景、图片logo
视频【√】同步支持视频智能鉴黄、视频暴恐涉政、视频不良场景、视频logo、视频图文违规
【√】异步支持视频智能鉴黄、视频暴恐涉政、视频不良场景、视频logo、视频图文违规、视频语音违规
文本【√】同步支持antispam (包含规则为:色情、广告、灌水、渉政、辱骂等) :文本反垃圾算法
语音【√】短语音同步检测antispam:音频反垃圾算法
【√】异步支持antispam:音频反垃圾算法

阿里云内容安全 API 检测覆盖场景:

资源类型label | 场景名称描述检测结果分类
图片【porn】图片智能鉴黄检测图片是否包含色情、性感内容。正常、色情、性感
【terrorism】图片暴恐涉政检测图片是否包含暴恐或涉政类内容。正常、血腥、爆炸烟光、特殊装束、特殊标识、武器、涉政、打斗、聚众、游行、车祸现场、旗帜、地标
【ad】图文违规检测图片是否包含广告和文字违规信息。正常、文字含涉政内容、文字含涉黄内容、文字含辱骂内容、文字含暴恐内容、文字含违禁内容、文字含其他垃圾内容、牛皮癣广告、含二维码、含小程序码、其他广告说明 默认仅支持正常、广告分类。(如果需要其他分类,需要提交工单)。
【qrcode】图片二维码检测图片是否包含二维码或小程序码。正常、含二维码、含小程序码说明 默认仅支持正常、含二维码分类。(如果需要其他分类,需要提交工单)。
【live】图片不良场景检测图片是否包含黑屏、黑边、昏暗画面、画中画、吸烟、车内直播等不良场景。正常、图片中无内容(例如黑屏、白屏)、画中画、吸烟、车内直播
【logo】图片logo检测图片是否包含logo信息,例如台标,商标等。正常、含受管控的logo、含商标
视频【porn】视频智能鉴黄检测视频中是否包含色情内容。正常、色情
【terrorism】视频暴恐涉政检测视频中是否包含暴恐涉政内容。正常、暴恐涉政
【live】视频不良场景检测视频中是否包含不良场景。正常、不良场景(例如黑屏、白屏)
【logo】视频logo检测视频中是否包含特定的logo。正常、logo
【ad】视频图文违规检测视频中是否包含广告或违规的文字内容。正常、广告或文字违规
视频语音违规说明 该场景仅支持通过视频异步检测接口调用。如需使用,请参见 异步检测检测视频中的语音内容是否包含违规信息。说明 默认识别语言为中文。(如果需要识别英文内容,需要提交工单)。正常、含垃圾信息、广告、涉政、暴恐、辱骂、色情、灌水、违禁、自定义(例如命中自定义关键词)
文本【antispam】反垃圾算法检测文本内容是否触发反垃圾规则色情、广告、灌水、渉政、辱骂等
音频【antispam】反垃圾算法检测音频内容是否触发反垃圾规则色情、广告、灌水、渉政、辱骂等

审核结果常量:

字段名取值
suggestionpass:正常,可以直接放行。
review:疑似,建议自行人工审核确认。
block:违规,可以直接删除或者限制公开。
sceneporn: 涉黄
terrorism: 涉恐
ad: 广告
qrcode: 图片二维码
live: 图片不良场景
logo: 图片logo
labelnormal: 正常
sexy: 性感
porn: 黄色
bloody: 血腥
explosion: 爆炸烟光
outfit: 特殊装束
logo: 特殊标识
weapon: 武器
politics: 涉政
violence: 打斗
crowd: 聚众
parade: 游行
carcrash: 车祸现场
flag: 旗帜
location: 地标
others: 其他
abuse: 文字含辱骂内容
contraband: 文字含违禁内容
spam: 文字含垃圾内容
npx: 牛皮癣广告
programCode: 小程序码
meaningless: 图片中无内容(例如,黑屏、白屏)
PIP: 画中画
smoking: 吸烟
drivelive: 车内直播
TV: 含受管控的logo
trademark: 含商标
flood:(文字)灌水
customized: 命中阿里内容安全后台配置的自定义关键词

阿里云 API 输入文件限制:

资源类型同步/异步输入限制
视频同步视频要求: 1.视频文件链接支持以下协议:HTTP和HTTPS。 2.视频文件支持以下格式:AVI、FLV、MP4、MPG、ASF、WMV、MOV、WMA、RMVB、RM、FLASH、TS。 3.视频大小限制:单个视频大小不超过200 MB。(如果有大视频的检测需求(最大支持2 GB),可以提交工单进行调整。) 4.视频流支持以下协议:RTMP、HLS、HTTP-FLV、RTSP。 5.视频流时长限制:单个视频流检测任务最长支持24小时,超过24小时任务自动结束。
异步视频要求: 1.视频文件链接支持以下协议:HTTP和HTTPS。 2.视频文件支持以下格式:AVI、FLV、MP4、MPG、ASF、WMV、MOV、WMA、RMVB、RM、FLASH、TS。 3.视频大小限制:单个视频大小不超过200 MB。(如果有大视频的检测需求(最大支持2 GB),可以提交工单进行调整。) 4.视频流支持以下协议:RTMP、HLS、HTTP-FLV、RTSP。 5.视频流时长限制:单个视频流检测任务最长支持24小时,超过24小时任务自动结束。 6.视频检测的时间依赖于视频的下载时间。需要保证被检测的视频文件所在的存储服务稳定可靠。
视频按帧审核视频截帧要求: 1.视频帧链接支持以下协议:HTTP和HTTPS。 2.视频帧支持以下格式:PNG、JPG、JPEG、BMP、GIF、WEBP。 视频帧大小限制为10 MB以内。3. 视频帧像素建议不低于256*256,像素过低可能会影响识别效果。4. 视频检测接口的响应时间依赖视频帧的下载时间。需要保证被检测视频帧所在的存储服务稳定可靠。
图片同步图片要求: 1.图片链接支持以下协议:HTTP和HTTPS。 2.图片支持以下格式:PNG、JPG、JPEG、BMP、GIF、WEBP。3. 图片大小限制为20 MB以内(适用于同步和异步调用),高度或者宽度不能超过30,000像素(px),且图像总像素不超过2.5亿px。 4.图片下载时间限制为3秒内,如果下载时间超过3秒,返回下载超时。 5.图片像素建议不低于256*256,像素过低可能会影响识别效果。 6.图片检测接口的响应时间依赖图片的下载时间。需要保证被检测图片所在的存储服务稳定可靠。
异步图片要求: 1.图片链接支持以下协议:HTTP和HTTPS。2. 图片支持以下格式:PNG、JPG、JPEG、BMP、GIF、WEBP。 3.图片大小限制为20 MB以内(适用于同步和异步调用),高度或者宽度不能超过30,000像素(px),且图像总像素不超过2.5亿px。 4.图片下载时间限制为3秒内,如果下载时间超过3秒,返回下载超时。 5.图片像素建议不低于256*256,像素过低可能会影响识别效果。 6.图片检测接口的响应时间依赖图片的下载时间。需要保证被检测图片所在的存储服务稳定可靠。
文本同步无明确规定大小限制,但限于 POST 方式大小限制,一般我们使用时不要超过 2M。具体我们使用时限制文本字数 2000字,如果过长可以进行截断然后分次审核。
音频同步音频文件要求: 1. 支持的音频文件大小小于20 MB。 2.支持的语音文件时长小于1分钟。 3.支持的音频文件格式:MP3、WAV、AAC、WMA、OGG、M4A、M3U8。 4.支持以下包含音频的视频文件格式:AVI、FLV、MP4、MPG、ASF、WMV、MOV、RMVB、RM。
异步音频文件要求: 1.支持的音频文件大小小于200 MB。 2.支持的音频文件格式:MP3、WAV、AAC、WMA、OGG、M4A、AMR、AUDIO、M3U8。 3.支持以下包含音频的视频文件格式:AVI、FLV、MP4、MPG、ASF、WMV、MOV、RMVB、RM。
异步(音频流)语音流要求: 1.语音流时长小于24小时。 2.支持的语音流协议:HTTP、RTMP、RTSP。 3.支持的语音流格式:M3U8、FLV。

阿里API返回错误码

错误代码EnumHTTP code描述
2xx成功
4xx客户端请求有误
5xx后端处理有误
OK200请求成功。
PROCESSING280任务正在执行中,建议您等待一段时间(例如5s)后再查询结果。
BAD_REQUEST400请求有误,通常由于请求参数不正确导致,请仔细检查请求参数。
NOT_ALLOWED401请求失败,通常是由于使用了不安全的图片、视频、语音链接地址。
FORBIDDEN403请求访问失败,通常由于您的图片、视频、语音链接无法访问导致,请确认公网是否可访问,并且无防盗链策略。
NOT_FOUND404待检测内容未找到,通常是由于您的图片、视频、语音内容无法下载导致,请确认内容可通过公网访问到。
DOWNLOAD_FAILED480下载失败,请确认待检测内容的大小、分辨率(如果有)在API的限制范围内。
GENERAL_ERROR500一般是服务端临时出错。建议重试,若持续返回该错误码,请通过工单联系我们。
DB_FAILED580数据库操作失败。建议重试,若持续返回该错误码,请通过工单联系我们。
TIMEOUT581超时。建议重试,若持续返回该错误码,请通过工单联系我们。
CACHE_FAILED585缓存出错。建议重试,若持续返回该错误码,请通过工单联系我们。
ALGO_FAILED586算法出错。请通过工单联系我们。
MQ_FAILED587中间件出错。请通过工单联系我们。
EXCEED_QUOTA588请求频率超出配额。默认配额:图片检测50张/秒,视频检测20路/秒,语音检测20路/秒,文本检测100条/秒。如果需要调整配额,请通过工单联系我们。说明 关于价格说明,请参见 内容安全产品定价
TOO_LARGE589待检测内容过大,请确保检测的内容在API的限制范围内。建议重试,若持续返回该错误码,请通过工单联系我们。
BAD_FORMAT590待检测内容格式错误,请确保检测的内容在API的限制范围内。
CONNECTION_POOL_FULL591连接池满。请通过工单联系我们。
DOWNLOAD_TIMEOUT592下载超时,下载时间限制为3s,请确保检测的内容大小在API的限制范围内。
EXPIRED594任务过期,如taskId过期。
CATCH_FRAME_FAILED595截帧失败,请通过工单联系我们。
PERMISSION_DENY596账号未授权、账号欠费、账号未开通、账号被禁等原因,具体可以参考返回的msg。

todolist

todo
2.3 测试头条内容审核服务,腾讯内容审核服务的接口,加入多平台实现。
3.2 任务回调失败时,往往是 download timeout, algo failed 等错误。拿到回调失败通知时,尝试重试一到两次。