# 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 后即可联调。
