内容审核与发布安全
梳理子比主题 API 内容审核、百度审核接入、投稿评论私信审核、图片审核、论坛审核与人工审核边界。
模块边界
子比主题的内容审核不是一个独立发布系统,而是接在前台交互流程里的风险判断层。它会在用户上传图片、发布投稿、发表评论、发送私信、修改昵称签名、发布论坛帖子或创建论坛对象时调用审核接口;审核接口只负责判断内容风险,是否最终发布仍由主题的权限、人工审核、文章状态和论坛状态决定。
开发时要先记住这条边界:API 审核通过不等于用户拥有发布权限,也不等于可以绕过人工审核。主题里常见的直通条件是“API 审核通过 + 当前用户拥有免人工审核能力”,否则内容仍按主题原流程进入待审或阻断。
核心文件
| 文件 | 作用 |
|---|---|
inc/class/api-audit-class.php | ZibAudit 内容审核类,封装文本、图片、百度接口、token 缓存和审核结果转换 |
inc/options/admin-options.php | API 内容审核后台配置、各业务开关、百度 App Key 与 Secret Key |
action/new_posts.php | 前台投稿发布前的文字审核和待审状态判断 |
action/comment.php | 评论提交前的文字审核和 pre_comment_approved 处理 |
action/user.php | 用户昵称、签名保存前的文字审核 |
inc/functions/zib-attachment.php | 图片上传进入 attachment 前的图片审核 |
inc/functions/message/functions/ajax.php | 私信发送前的文字审核 |
inc/functions/bbs/action/ajax-posts.php | 论坛帖子发布前的文字审核和待审状态判断 |
inc/functions/bbs/action/ajax-plate.php | 论坛版块图像与文本审核 |
inc/functions/bbs/action/ajax-term.php | 论坛话题、标签、分类图像与文本审核 |
后台配置
API 内容审核配置位于主题后台的系统工具分组内。核心选项如下:
| 配置项 | 含义 |
|---|---|
api_audit_text_sdk | 文本审核接口,当前主题源码支持 baidu 或关闭 |
api_audit_img_sdk | 图像审核接口,当前主题源码支持 baidu 或关闭 |
audit_be_like_text | 文本“疑似”结果的处理策略 |
audit_be_like_img | 图片“疑似”结果的处理策略 |
audit_upload_img | 用户上传图片是否进入图片审核 |
audit_user_desc | 用户昵称和签名是否进入文本审核 |
audit_msg_private | 私信内容是否进入文本审核 |
audit_comment | 评论内容是否进入文本审核 |
audit_new_post | 前台投稿是否进入文本审核 |
audit_bbs_posts | 论坛发帖是否进入文本审核 |
audit_bbs_plate | 论坛版块标题和简介是否进入文本审核 |
audit_bbs_term | 论坛话题、标签、分类标题和简介是否进入文本审核 |
audit_sdk_baidu | 百度审核接口配置,包含 appkey 和 secretkey |
audit_baidu_access_token | 本地缓存的百度 access_token 和过期时间 |
这些开关只决定是否调用审核接口,不替代原本的登录态、nonce、能力、长度、分类、上传大小和文件类型校验。
ZibAudit 类
常用入口:
| 方法 | 用途 |
|---|---|
ZibAudit::ajax_text($content, $msg_prefix = '', $be_like = null) | Ajax 流程中审核文本,明确不合规时直接输出 JSON 并 exit() |
ZibAudit::ajax_image($file_id = 'file', $msg_prefix = '', $be_like = null) | Ajax 流程中审核 $_FILES[$file_id] 图片,明确不合规时直接输出 JSON 并 exit() |
ZibAudit::text($content, $sdk = '') | 只请求文本审核并返回结果,不直接中断 Ajax |
ZibAudit::image($content, $sdk = '') | 只请求图片审核并返回结果,不直接中断 Ajax |
ZibAudit::request($content, $type = 'text', $sdk = null) | 按文本或图片类型选择审核 SDK |
ZibAudit::is_audit($return, $type = 'text') | 将审核返回值转换成是否可直通的布尔判断 |
ZibAudit::baidu($content, $type) | 调用百度文本或图片审核接口 |
ZibAudit::baidu_get_token() | 获取并缓存百度 access_token |
ajax_text() 和 ajax_image() 适合前台 Ajax 保存流程,因为它们能在不合规时立即返回统一错误。text() 和 image() 适合后台测试、日志或需要自己决定后续状态的场景。
百度接口链路
百度配置来自:
$cofig = _pz('audit_sdk_baidu');主题使用 appkey 和 secretkey 请求:
https://aip.baidubce.com/oauth/2.0/token成功后缓存到:
update_option('audit_baidu_access_token', $res);文本审核接口:
https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined图片审核接口:
https://aip.baidubce.com/rest/2.0/solution/v1/img_censor/v2/user_defined图片内容可以是 URL,也可以是本地上传文件。对本地文件,主题会读取文件内容并转成 base64:
ZibAudit::image_base64($content);前台上传场景通常传的是 $_FILES[$file_id]['tmp_name'],不要把用户可控文件名或未校验 URL 当作可信审核对象。
审核结果
ZibAudit::_return() 会把接口返回整理成统一结构:
array(
'conclusion' => $conclusion,
'conclusion_type' => $conclusionType,
'msg' => $msg,
'data' => $data,
)百度常见结论类型:
conclusion_type | 含义 | 处理建议 |
|---|---|---|
1 | 合规 | 可以继续进入主题后续权限和发布状态判断 |
2 | 不合规 | 阻断保存或进入人工处理,不要自动发布 |
3 | 疑似 | 按站点配置和当前版本源码处理,保守场景建议进入人工审核 |
4 | 审核失败 | 不应当自动发布,优先提示重试或进入人工审核 |
扩展开发时不要自己硬编码“接口返回成功就发布”。如果处于主题现有流程,优先使用 ZibAudit::ajax_text()、ZibAudit::ajax_image() 和 ZibAudit::is_audit(),并保留后续的用户能力判断。
前台投稿审核
前台投稿在 action/new_posts.php 中处理。主题先做标题、内容、分类、发布权限等校验,再进入内容合规判断:
$is_audit = false;
if (zib_current_user_can('new_post_audit_no')) {
$is_audit = true;
} else {
if (_pz('audit_new_post')) {
$api_is_audit = ZibAudit::is_audit(ZibAudit::ajax_text($title . $content));
if ($api_is_audit && zib_current_user_can('new_post_audit_no_manual')) {
$is_audit = true;
}
}
}关键点:
new_post_audit_no表示用户拥有免审核权限,可以直接通过主题审核层。audit_new_post只决定是否调用 API 文本审核。- API 审核通过后,还要拥有
new_post_audit_no_manual,才会免人工审核。 - 不满足直通条件时,文章仍应按主题原流程进入
pending或对应待审状态。
新增投稿相关字段时,只在主题保存流程前后追加自己的校验和 Meta,不要单独创建一个绕过 zib_ajax_new_posts() 的发布入口。
评论审核
评论在 action/comment.php 中处理。主题会先检查评论内容长度,再判断当前用户是否免审、是否为快速回复、是否需要 API 审核:
if (zib_current_user_can('comment_audit_no', (!empty($_POST['comment_post_ID']) ? $_POST['comment_post_ID'] : 0))) {
$is_audit = true;
} else {
if (in_array(trim($_POST['comment']), zib_get_quick_often_items())) {
$is_audit = true;
}
if (!$is_audit && _pz('audit_comment')) {
$api_is_audit = ZibAudit::is_audit(ZibAudit::ajax_text(zib_comment_filters($_POST['comment'])));
if ($api_is_audit && zib_current_user_can('comment_audit_no_manual')) {
$is_audit = true;
}
}
}审核直通后,主题通过 pre_comment_approved 将评论设为已审核:
add_filter('pre_comment_approved', function () {
return 1;
});扩展评论时要保留 comment_audit_no、comment_audit_no_manual、快速回复、评论频率和 WordPress 原生评论审核机制,不要只看 API 结果。
快捷回复与审核白名单
评论快捷回复有“系统”和“我的”两种来源,但审核白名单只读取系统快捷回复。zib_get_quick_often_items() 会按开关合并:
| 开关 | 字段 | 场景 |
|---|---|---|
bbs_comment_quick_s | bbs_comment_quick_often | 论坛评论常用快捷回复 |
comment_quick_s | comment_quick_often | 普通文章评论常用快捷回复 |
用户自己的快捷回复保存在 user meta quick_often,由 user_edit_quick_often 和 save_user_quick_often 管理,只用于输入框里“我的”Tab 快速填充,不会被 zib_get_quick_often_items() 读取。因此扩展评论审核时,不要把个人快捷回复当成可信白名单。
如果确实要新增一组可免审核的系统短语,应保持“完全匹配后才免审核”的语义,并继续保留评论长度、人机验证、频率限制和 WordPress 原生审核机制。主题源码当前没有给 zib_get_quick_often_items() 预留过滤器;二开时不要直接把用户提交的 quick_often 写进系统配置。
用户资料与私信审核
用户资料保存会对昵称和签名进行审核:
if (_pz('audit_user_desc')) {
ZibAudit::ajax_text($_POST['desc'] . $_POST['name'], __('昵称或签名', 'zib_language'));
}私信发送会在黑名单判断之后、消息入库之前审核文本:
if (_pz('audit_msg_private')) {
ZibAudit::ajax_text(Zib_Private::get_content($_POST['receive'], 'mini'));
}这些入口适合阻断明显违规内容,不适合承担身份权限。保存用户资料仍要继续校验当前用户、字段长度、URL 格式和绑定关系;发送私信仍要继续校验发送者、接收者、黑名单、消息频率和会话状态。
图片上传审核
图片审核集中接在 zib_php_upload() 里:
if ('auto' == $ajax_audit) {
$ajax_audit = _pz('audit_upload_img', false);
}
if ($ajax_audit && stristr($_FILES[$file]['type'], 'image')) {
ZibAudit::ajax_image($file, $msg_prefix);
}多图上传时,主题还会逐个检查 file_1、file_2 等后续文件。审核通过后才进入:
media_handle_upload($file, $post_id, [], $overrides);所以自定义上传入口应优先复用 zib_php_upload()。如果必须自己处理上传,也要在写入 attachment 前调用 ZibAudit::ajax_image(),并继续保留 MIME、大小、登录态、nonce、媒体库隔离和附件归属逻辑。更多上传边界见 媒体上传。
论坛审核
论坛帖子发布在 inc/functions/bbs/action/ajax-posts.php 中判断:
if (zib_bbs_current_user_can('posts_save_audit_no') || ($post_id && zib_bbs_current_user_can('posts_audit', $post_id)) || ($plate && zib_bbs_current_user_can('posts_audit', $plate))) {
$is_audit = true;
} else {
if (_pz('audit_bbs_posts')) {
$api_is_audit = ZibAudit::is_audit(ZibAudit::ajax_text($post_title . $post_content));
if ($api_is_audit && zib_bbs_current_user_can('posts_save_audit_no_manual')) {
$is_audit = true;
}
}
}论坛对象还有两类审核入口:
| 场景 | 审核点 |
|---|---|
| 创建或编辑版块 | audit_upload_img 审核版块图像,audit_bbs_plate 审核标题和简介 |
| 创建或编辑话题、标签、分类 | audit_upload_img 审核图像,audit_bbs_term 审核标题和简介 |
论坛扩展要同时考虑版块权限、版主权限、帖子审核权限、用户发帖能力和对象状态。API 审核只是在保存前增加一道风险检查,不能替代 zib_bbs_current_user_can()。
新增 Ajax 审核示例
下面示例用于新增一个前台内容保存入口。它保留登录态、nonce、字段清洗、API 审核和统一 JSON 响应:
function zib_docs_save_profile_note()
{
if (!is_user_logged_in()) {
zib_send_json_error(__('请先登录', 'zib_language'));
}
zib_ajax_verify_nonce();
$user_id = get_current_user_id();
$note = !empty($_POST['note']) ? wp_kses_post(wp_unslash($_POST['note'])) : '';
if (zib_new_strlen($note) < 2) {
zib_send_json_error(__('内容过短', 'zib_language'));
}
if (_pz('api_audit_text_sdk')) {
ZibAudit::ajax_text($note, __('补充资料', 'zib_language'));
}
zib_update_user_meta($user_id, 'profile_note', $note);
zib_send_json_success(array(
'msg' => __('保存成功', 'zib_language'),
'note' => $note,
));
}
add_action('wp_ajax_docs_save_profile_note', 'zib_docs_save_profile_note');如果业务不是“失败即阻断”,而是“失败进入待审”,可以只拿审核结果,再决定状态:
function zib_docs_insert_pending_item($title, $content)
{
$is_audit = false;
if (_pz('audit_new_post')) {
$audit_result = ZibAudit::ajax_text($title . $content, __('内容', 'zib_language'));
$is_audit = ZibAudit::is_audit($audit_result);
}
$status = $is_audit && zib_current_user_can('new_post_audit_no_manual') ? 'publish' : 'pending';
return wp_insert_post(array(
'post_type' => 'post',
'post_title' => wp_strip_all_tags($title),
'post_content' => wp_kses_post($content),
'post_status' => $status,
), true);
}注意这里仍然检查 new_post_audit_no_manual。不要因为 API 审核返回合规就直接给所有用户发布权限。
图片审核示例
新增图片上传入口时,继续复用主题上传封装:
function zib_docs_upload_checked_image()
{
if (!is_user_logged_in()) {
zib_send_json_error(__('请先登录', 'zib_language'));
}
zib_ajax_verify_nonce('user_upload');
if (empty($_FILES['file']) || !stristr($_FILES['file']['type'], 'image')) {
zib_send_json_error(__('请选择图片文件', 'zib_language'));
}
$attach_id = zib_php_upload('file', 0, 'auto', __('图片', 'zib_language'));
if (!empty($attach_id['error'])) {
zib_send_json_error($attach_id['msg']);
}
zib_send_json_success(zib_prepare_attachment_for_js($attach_id));
}
add_action('wp_ajax_docs_upload_checked_image', 'zib_docs_upload_checked_image');'auto' 会读取 audit_upload_img,保持和主题后台配置一致。业务需要强制审核时可以传 true,但不要绕过 zib_php_upload() 里的媒体处理。
人工审核与通知
内容审核通常和人工审核、通知、日志一起使用:
| 场景 | 可用节点 |
|---|---|
| 投稿进入待审 | new_posts_pending |
| 投稿新增成功 | new_add_posts |
| 投稿编辑成功 | new_edit_posts |
| 评论进入待审 | WordPress 评论状态和后台评论审核 |
| 论坛帖子审核 | posts_audit、版主权限和论坛审核弹窗 |
| 论坛版块审核 | plate_audit、版块管理权限 |
如果要记录审核日志,可以挂保存后的 Action,并只保存必要结论,不要把第三方接口原始响应、命中词、密钥、服务器路径或用户隐私写进前端可见位置。
常见风险
| 风险 | 说明 |
|---|---|
| 把 API 审核当权限系统 | 审核接口只判断内容风险,不判断用户能否发布、编辑或上传 |
| API 合规后直接发布 | 主题原逻辑还需要免人工审核能力,否则应进入待审 |
绕过 zib_php_upload() | 会丢失图片审核、MIME、大小、attachment 和媒体库隔离 |
| 只审核标题不审核正文 | 投稿、帖子、版块、话题通常要拼接标题和正文一起审核 |
| 不处理审核失败 | 第三方接口超时或失败时不应默认发布 |
| 暴露接口原文 | 审核命中词、access token、错误响应不应直接给普通用户或前端页面 |
| 缓存动态接口 | 投稿、评论、私信、上传 Ajax 不能被 CDN 或页面缓存缓存 |
| 忽略管理员绕过 | ZibAudit 对管理员操作有特殊处理,测试时要用普通用户验证 |
调试入口
| 现象 | 优先检查 |
|---|---|
| 审核完全不触发 | 对应业务开关、api_audit_text_sdk、api_audit_img_sdk、当前用户是否管理员 |
| 百度提示配置缺失 | audit_sdk_baidu.appkey、audit_sdk_baidu.secretkey |
| 百度 token 获取失败 | 服务器网络、接口返回、audit_baidu_access_token 缓存 |
| 投稿仍进入待审 | 是否拥有 new_post_audit_no_manual,是否满足主题发布能力 |
| 评论仍待审 | comment_audit_no_manual、WordPress 评论设置、评论频率限制 |
| 图片上传变慢 | audit_upload_img、第三方接口耗时、服务器到接口网络 |
| 前端没有错误提示 | ajax_text() / ajax_image() 只有明确不合规时才会输出阻断错误 |
参考源码
本页根据 inc/class/api-audit-class.php、inc/options/admin-options.php、action/new_posts.php、action/comment.php、action/user.php、inc/functions/zib-attachment.php、inc/functions/message/functions/ajax.php、inc/functions/bbs/action/ajax-posts.php、inc/functions/bbs/action/ajax-plate.php、inc/functions/bbs/action/ajax-term.php 蒸馏整理。