小程序UGC内容安全检测接入实战:文本鉴黄、异步图片审核与误杀兜底 背景只要小程序里开放了用户生成内容评价、晒单、头像昵称、社区发帖、客服留言就绕不开平台合规要求。以微信生态为例运营规范明确要求平台对UGC内容履行安全审核义务实践中通常采用官方提供的内容安全接口security.msgSecCheck文本检测、security.mediaCheckAsync媒体异步检测完成初筛再叠加人工复审。本文复盘我们在一个带图文评价功能的商城项目中接入这套能力的全过程包括接口选型、签名与token管理、异步回调设计以及上线后踩到的4个坑。一、接口能力梳理与选型内容安全相关接口有几个关键差异选错会直接导致架构返工msgSecCheckv2文本检测同步返回。入参为openid、scene、version2、content返回里包含result.suggestpass/review/risky和detail.label标签枚举。单条文本长度有限制按官方文档当前为2500个汉字以内超长需分段。imgSecCheck旧版图片同步检测对图片大小有较严格限制1MB内大文件要自己压缩高峰期同步等待体验差新项目不建议作为主方案。mediaCheckAsync媒体异步检测支持图片和视频通过消息推送回调结果适合晒单图、头像这类不需要即时拦的场景。我们的最终分工评价文字走msgSecCheck同步拦截晒单图片走mediaCheckAsync内容先落库为审核中状态回调驱动状态流转用户头像因为要即时生效走前端压缩后同步检测的降级路径。二、access_token 的正确管理姿势所有接口都依赖access_token有效期7200秒且全系统共享一个配额——多个服务各自刷新会互相顶掉旧token表现为随机的40001错误。踩过这个坑后统一收敛到一个token服务typeTokenServicestruct{mu sync.Mutex tokenstringexpiresAt time.Time appIDstringappSecretstring}func(s*TokenService)Get(ctx context.Context)(string,error){s.mu.Lock()defers.mu.Unlock()// 提前300秒过期避免临界点失效ifs.token!time.Now().Before(s.expiresAt.Add(-300*time.Second)){returns.token,nil}url:fmt.Sprintf(https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappid%ssecret%s,s.appID,s.appSecret,)req,_:http.NewRequestWithContext(ctx,GET,url,nil)resp,err:http.DefaultClient.Do(req)iferr!nil{return,err}deferresp.Body.Close()varretstruct{AccessTokenstringjson:access_tokenExpiresInintjson:expires_inErrCodeintjson:errcodeErrMsgstringjson:errmsg}iferr:json.NewDecoder(resp.Body).Decode(ret);err!nil{return,err}ifret.ErrCode!0{return,fmt.Errorf(token api error: %d %s,ret.ErrCode,ret.ErrMsg)}s.tokenret.AccessToken s.expiresAttime.Now().Add(time.Duration(ret.ExpiresIn)*time.Second)returns.token,nil}多实例部署时单机锁不够用需要把token放到Redis并加分布式锁保证全局只有一个实例执行刷新其余实例读缓存。三、文本同步检测分段与标签处理长评价超过接口长度上限时不能硬截断否则后半段完全失控。我们的处理是按句号/换行等语义边界切片每片独立检测任一叶片命中 risky 即整体拒绝全部 pass 才放行出现 review 则转人工funccheckText(ctx context.Context,ts*TokenService,openid,contentstring)(string,error){token,err:ts.Get(ctx)iferr!nil{return,err}chunks:splitByRune(content,2000)// 留余量按语义边界切worst:passfor_,chunk:rangechunks{body,_:json.Marshal(map[string]interface{}{version:2,scene:2,// 2评论场景按官方枚举传openid:openid,content:chunk,})url:https://api.weixin.qq.com/wxa/msg_sec_check?access_tokentoken resp,err:http.Post(url,application/json,bytes.NewReader(body))iferr!nil{return,err}varretstruct{ErrCodeintjson:errcodeResultstruct{Suggeststringjson:suggest}json:result}json.NewDecoder(resp.Body).Decode(ret)resp.Body.Close()ifret.ErrCode!0{// 见坑340001要强制刷新token重试一次return,fmt.Errorf(sec check failed: %d,ret.ErrCode)}worstmergeSuggest(worst,ret.Result.Suggest)}returnworst,nil}// risky优先级最高review次之funcmergeSuggest(a,bstring)string{rank:map[string]int{pass:0,review:1,risky:2}ifrank[b]rank[a]{returnb}returna}四、图片异步检测状态机 回调幂等mediaCheckAsync的核心是回调不可靠假设——推送可能延迟、可能重复、可能因服务器抖动而丢失。图片记录设计成显式状态机pending(提交成功) → reviewing(检测中) → blocked(命中) ↘ pass(放行) 任何状态 → callback_timeout(超过SLA未收到回调走主动复查)提交时拿到官方返回的trace_id落库时与业务图片ID绑定。回调消息体里用trace_id反查记录处理逻辑必须幂等funcHandleMediaCallback(ctx context.Context,msg*MediaCheckCallback)error{// 用trace_id做唯一约束重复回调直接返回成功record,err:repo.GetByTraceID(ctx,msg.TraceID)iferr!nil{returnerr}ifrecord.IsFinal(){// 已是终态幂等直接ACKreturnnil}suggest:msg.Result.Suggestswitchsuggest{caserisky:returnrepo.Transit(ctx,record.ID,reviewing,blocked)casepass:returnrepo.Transit(ctx,record.ID,reviewing,pass)default:returnrepo.Transit(ctx,record.ID,reviewing,manual_review)}}状态流转用UPDATE ... WHERE statusreviewing的乐观条件兜底并发防止两条重复回调把状态改乱。另外必须建一个定时兜底任务对超过30分钟仍处于reviewing的记录主动调用结果查询或重新提交。上线第一周这个兜底任务救回了约3%因回调丢失卡在中间态的图片。五、踩坑记录坑1测试内容固定上线首日漏过变体联调时一直用官方文档给的固定测试文本全部命中、流程正常误以为召回没问题。上线后发现各种谐音、拆字、表情包夹字绕过初筛。补救措施对review级别记录全部留档定期回流补充自建敏感词库做二次匹配同时关注官方标签枚举更新把新标签纳入处置策略。坑2图片压缩过度导致判定失真为满足旧版同步接口的体积限制前端把晒单图压到300KB以下画面细节糊到连人工都看不清检测侧同样出现误判。改用异步接口后恢复正常画质上传只做尺寸最长边1280和格式统一转JPEGquality 0.82的轻量处理误杀率明显下降。坑340001错误直接失败没有重试access_token临界过期或被其他服务顶掉时接口返回40001。最初实现直接把错误抛给用户评价发送失败。正确做法是把40001/42001这类token类错误识别出来强制作废缓存token并刷新一次重试原请求只有重试仍失败才降级比如转审核中稍后处理而不是让业务直接报错。坑4误杀没有申诉通道正常用户被永久拦截一次促销词在文本模型上被标成review转人工客服没及时处理用户的好评卡了两天投诉到平台。之后做了两件事一是评价类内容即便判定review也先保存为仅自己可见给出内容审核中预计X小时内展示的明确预期而不是让用户以为内容丢了二是在客服后台提供一键复核入口人工确认安全后即时放行。审核系统不可能零误杀兜底体验比追求极致召回更重要。六、上线后的监控指标建议至少盯三个指标接口层错误率区分token类、频控类、内容类、各suggest级别占比risky比例突然飙升往往是被刷垃圾内容的信号、回调延迟与丢失率配合兜底任务的触发量观察。配合内容量增长还需要评估接口频控高频场景给检测调用加本地限流避免突刺流量触发官方频率限制后整段业务降级。小结内容安全接入的技术难度不在调接口而在三个工程细节token统一管理避免互相顶号、异步回调按状态机和幂等设计并接受回调会丢、为误杀和失败预留用户可感知的兜底路径。把这三点做扎实审核能力才不会成为业务的不稳定因素。