# frontend(mmg): 微信内容安全方案 — 前端集成指南 **关联 PR**: #1659 #1661 #1667 #1666 #1668 #1669(全 5 Phase 已合 dev) **日期**: 2026-05-05 **对接人**: mmg 后端方案已全链路闭环,前端需要按本指南接入新接口 + 调整 body/响应字段。本指南是 5 个 Phase 的整合视图,不需要去翻散落的 changelog。 --- ## 一、新增接口(前端直接调) ### 1. `POST /mp/security/text-check` 文本同步机审 提交评价/投诉/反馈/姓名修改等含用户输入文本的业务前,**先调本接口**拿审核结果决定是否拦截。 **请求**: ```js http.post('/mp/security/text-check', { content: "用户提交的文本内容", // 必填,≤2500 字 scene: 2 // 必填:1=资料/2=评论/3=论坛/4=社交日志 }) ``` **响应**: ```json { "code": 200, "data": { "pass": true, "suggest": "pass", // pass / review / risky / pass-degraded "label": 100, // Integer 类型,100=正常 "traceId": "xxx", "degraded": false, "detail": null // 命中时是关键词摘要字符串 } } ``` **前端拦截规则**: - `pass=true` 或 `suggest=pass` → 放行 - `suggest=review` → toast「内容疑似违规,请修改后重试」+ 阻断 - `suggest=risky` → toast「内容包含违规信息,请修改」+ 阻断 - `suggest=pass-degraded` 或网络异常 → **降级放行**(不阻断业务,后端事后人工兜底) ### 2. `POST /mp/security/media-check` 媒体异步机审 图片/音频上传到 OSS 后,**调本接口拿 traceId**,把 traceId 数组随业务 API 提交。 **请求**: ```js http.post('/mp/security/media-check', { mediaUrl: "https://oss.example.com/xxx.jpg", // 必填,已上传 OSS 的 https URL mediaType: "image" // 必填:image / audio }) ``` **响应**: ```json { "code": 200, "data": { "traceId": "967e945cd8a3e458effc23eb0e7c84e1" // 失败时为 null } } ``` **前端使用**: - 同步只返 `traceId`,**不阻断业务提交**(异步审核) - traceId 收集到数组里,业务 API 提交时随 `mediaTraceIds` 字段传给后端 - traceId=null(降级)时仍然继续业务,后端按未审核处置 --- ## 二、业务 API body 新增 `mediaTraceIds` 字段(5 个接口) | 业务接口 | body 新增字段 | |---|---| | `POST /mp/review/create` 评价 | `mediaTraceIds: string[]` | | `POST /mp/complaint/create` 投诉 | `mediaTraceIds: string[]` | | `POST /mp/order/{orderId}/refund` 退款 | `mediaTraceIds: string[]` | | `POST /mp/order/refund/{appId}/appeal` 申诉 | `mediaTraceIds: string[]` | | `POST /mp/order/refund/{orderId}/direct-appeal` 直接申诉 | `mediaTraceIds: string[]` | | `POST /mp/common/feedback` 客服反馈 | `mediaTraceIds: string[]` | **例**: ```js // 评价提交流程 const traceIds = [] for (const url of uploadedImageUrls) { const r = await http.post('/mp/security/media-check', { mediaUrl: url, mediaType: 'image' }) if (r.data.traceId) traceIds.push(r.data.traceId) // null 时跳过即可 } await http.post('/mp/review/create', { orderId: '...', content: '评价文本', images: uploadedImageUrls, mediaTraceIds: traceIds // 新加字段 }) ``` 未传 `mediaTraceIds` 的接口(发票/退款理由/昵称/姓名)按无图处理,不影响。 --- ## 三、查询响应新增字段 ### `/mp/review/my` 我的评价列表 + 详情 VO 新增 2 字段(私有列表才返,公开列表后端已自动过滤未审核内容): ```jsonc { "id": "...", "content": "...", "rating": 5, "auditStatus": "MANUAL_REVIEW", // 新增 (Phase 2A): wx 内容安全机审状态 "reportStatus": "NORMAL", // 新增 (Phase 2B): 举报状态 "hiddenReason": null, // 新增 (Phase 2B): HIDDEN 时返回隐藏原因 "createdAt": "..." } ``` **前端展示状态条文案**: | auditStatus | 状态条 | |---|---| | `PENDING` | 「审核中,通过后展示」灰色 | | `APPROVED` | (无提示,正常展示) | | `MANUAL_REVIEW` | 「审核中,预计 24h 内」黄色 | | `REJECTED` | 「内容未通过审核」红色 + 重新编辑按钮 | | reportStatus | 状态条 | |---|---| | `NORMAL` | (无提示) | | `PENDING_REVIEW` | (无提示,仍展示) | | `HIDDEN` | 「该评价已下架」+ `hiddenReason` | **公开列表**(产品评价/首页精选/旅行相册)后端已强制过滤 `audit_status='APPROVED' AND report_status!='HIDDEN'`,前端**无需特殊处理**。 --- ## 四、新接口:评价举报 `POST /mp/review/report` ```js http.post('/mp/review/report', { reviewId: '101010101010', // String 雪花 ID(必填,严禁 Number) reason: 'SPAM', // 必填,字典 review_report_reason detail: '广告链接,引导外部' // 可选(≤500 字) }) ``` **reason 字典**(建议从 `dict.getList('review_report_reason')` 取,避免硬编码): | reason | 文案 | |---|---| | `SPAM` | 广告/营销 | | `INSULT` | 辱骂/人身攻击 | | `PORN` | 色情低俗 | | `POLITICS` | 政治敏感 | | `FAKE` | 虚假评价 | | `LEAK_PRIVACY` | 泄露他人隐私 | | `OTHER` | 其他 | **响应**: 成功 200 / 失败业务码: | code | 文案 | 处理 | |---|---|---| | 551001 | 举报原因无效 | toast | | 551002 | 今日举报次数已达上限 | toast,引导明日再来 | | 551003 | 评价不存在 | toast | | 551004 | 不能举报自己的评价 | toast | | 551005 | 您已举报过该评价 | toast | | 551006 | 无法识别用户身份,请重新登录 | 引导重新登录 | 举报后业务流程: - 同 review 累计举报 ≥3 次 → `report_status=PENDING_REVIEW`(用户侧仍可见,运营人工审核中) - ≥10 次且 24h 内 → 自动 `HIDDEN`(用户侧立即下架) - 命中举报触发再机审:risky → 强制 HIDDEN --- ## 五、出行人姓名(前端不做硬拦截) 按官方契约 §8,姓名是实名信息,**前端不应做内容机审**(怕误伤真实姓「操/苟」、少数民族罕字、港澳台/外籍拼音)。 **前端不需要改**: - ❌ 不要在创建出行人前调 text-check - ❌ 不要本地正则/黑名单拦截 **后端三层兜底已自动处理**: - 第 1 层:异步机审(不阻塞业务,命中标 MANUAL_REVIEW 不删数据) - 第 2 层:下单选出行人时**仅挡 REJECTED**(人工裁决),其他状态全放行 - 第 3 层:本地正则(长度/字符集/重复字符防垃圾) **新增错误码**(出行人下单时): | code | 文案 | |---|---| | 100701 | 姓名长度异常(2-30 字符) | | 100702 | 姓名含非法字符 | | 100703 | 姓名含敏感词 | | 100704 | 姓名格式不正确 | | 100705 | 此出行人信息异常,请联系客服核实 | 同款适用:紧急联系人 / 用户实名。 --- ## 六、头像 / 昵称(前端流程同现状) - **昵称**:前端调 text-check 文本同步机审(按"一"节流程),命中拦截 - **头像**:前端 `fileApi.upload` 上传 OSS → `securityApi.checkMedia` 拿 traceId → `userApi.updateUserInfo({avatar})` **不传 traceId**(后端会在 /mp/file/upload 内部异步审,traceId 关联到 fileId 后通过 webhook 自动反查 user 表) REJECTED 时后端自动重置默认头像,前端展示用户资料时拿到的就是已重置后的 URL。 --- ## 七、调用顺序总结 ### 评价/投诉/退款申诉 (含图) ``` 1. 用户填表(选图) 2. 图上传 OSS → 拿 url[] 3. 文本调 /mp/security/text-check → 拿 suggest 决定是否阻断 4. 每张图调 /mp/security/media-check → 收集 traceId[] 5. 业务接口提交 (body 含 mediaTraceIds) ``` ### 客服反馈 / 心愿单 / 备注 (仅文本) ``` 1. 用户填表 2. 调 /mp/security/text-check → 决定阻断 3. 业务接口提交 (body 不含 mediaTraceIds) ``` ### 用户昵称 ``` 1. 用户填昵称 2. 调 /mp/security/text-check → 决定阻断 3. /mp/user/profile 提交 ``` ### 用户头像 ``` 1. 上传 OSS 2. 调 /mp/security/media-check → 拿 traceId (失败也继续) 3. /mp/user/profile 提交 avatar (不传 traceId) ``` ### 出行人 ``` 1. 用户填姓名 2. /mp/user/traveler 直接提交 (前端不调 security) 3. 后端三层兜底自动处理 ``` --- ## 八、降级容错 任何 `/mp/security/*` 接口 5xx 或网络异常: - 前端按 **降级放行** 处理(不阻断业务) - 用户体验优先于绝对合规 - 后端会通过 `pass-degraded` 标记记录,事后人工兜底审核 --- ## 九、前端工作量预估 按本指南改前端约: - 通用工具:`utils/contentSecurity.js` + `utils/ugc.js` 包装 text-check / media-check(已有则更新) - 6 个业务页面调用点:评价提交 / 投诉提交 / 退款申请 / 申诉提交 / 客服反馈 / 心愿提交 - 评价列表:私有列表加状态条文案(auditStatus / reportStatus) - 评价举报新页面:调 /mp/review/report - 字典:拉 review_report_reason 字典做举报原因下拉 - 头像/昵称:检查现有流程是否已按上述顺序 --- @mmg 有任何疑问随时找我。后端已部署到 dev 分支,等运维 nacos 配 wechat.miniapp.webhook.token 后即可联调。