# 售后工单接入 wx 内容安全机审 — 修改接口（小程序端）

- **端类型**：小程序端
- **日期**：2026-06-22
- **Issue**：[#4196](https://git.1814.love:8443/wx/HL/issues/4196)
- **PR**：[#4203](https://git.1814.love:8443/wx/HL/pulls/4203)
- **Commit**：[8882eb156](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
- **后端负责人**：yaosutu

---

## 1. 接口背景

用户在小程序发起售后工单（投诉/申诉）时，若附带图片或视频，前端需先通过微信内容安全 `msgSecCheck` 接口对媒体文件做内容审核，得到微信返回的 `traceId` 后，连同创建工单请求一并透传给后端。后端不直接调用微信安全接口，只存储 traceId，等待微信异步回调后更新审核状态。无图/无视频的工单跳过机审，直接置为 `APPROVED`。

---

## 2. 变更清单

| 变更类型 | 接口 | 字段 | 说明 |
|---------|------|------|------|
| ⚠️ 入参新增 | `POST /v3/mp/aftersale/ticket` | `mediaTraceIds` | 新增可选字段，机审 traceId 数组，最多 12 个 |
| ✨ 出参新增 | `GET /v3/mp/aftersale/ticket/{id}` | `auditStatus` | 工单 wx 内容安全机审状态 |
| ✨ 出参新增 | `GET /v3/mp/aftersale/ticket/list` | `auditStatus` | 工单列表每条记录新增机审状态 |

---

## 3. 接口详情

### 3.1 发起售后工单

| 属性 | 值 |
|-----|-----|
| 方法 + 路径 | `POST /v3/mp/aftersale/ticket` |
| 接口描述 | 用户发起售后工单（投诉/申诉），支持附件 + 机审 traceId 透传 |
| 认证 | 需要用户 JWT（小程序登录 token） |
| 幂等性 | 非幂等，每次调用创建一条新工单 |
| 限流 | 无独立限流（受网关全局限流） |

### 3.2 工单详情

| 属性 | 值 |
|-----|-----|
| 方法 + 路径 | `GET /v3/mp/aftersale/ticket/{id}` |
| 接口描述 | 获取单条售后工单详情 |
| 认证 | 需要用户 JWT，仅可查自己的工单 |
| 限流 | 无独立限流 |

### 3.3 工单列表

| 属性 | 值 |
|-----|-----|
| 方法 + 路径 | `GET /v3/mp/aftersale/ticket/list` |
| 接口描述 | 查询当前用户的售后工单列表（分页） |
| 认证 | 需要用户 JWT |
| 限流 | 无独立限流 |

---

## 4. 接口入参

### 4.1 路径参数 / Query 参数

`GET /v3/mp/aftersale/ticket/{id}`

| 参数名 | 类型 | 必填 | 说明 |
|-------|------|------|------|
| id | Long | 是 | 工单 ID |

### 4.2 请求体字段（POST /v3/mp/aftersale/ticket）

| 字段名 | 类型 | 必填 | 校验 | 说明 |
|-------|------|------|------|------|
| orderId | Long | 是 | 不能为空 | 关联订单 ID |
| category | String | 是 | 枚举：COMPLAINT / APPEAL | 工单分类：投诉 / 申诉 |
| type | String | 是 | 枚举，见 §6 | 具体类型（如 HOTEL / GUIDE / PRICE） |
| title | String | 是 | max=100 | 工单标题 |
| description | String | 是 | max=2000 | 问题描述 |
| attachments | List\<String\> | 否 | max=10 | 附件 URL 列表（图片/视频） |
| **mediaTraceIds** | **List\<String\>** | **否** | **max=12** | **微信内容安全 traceId 数组（前端透传，后端只存）** |

> `mediaTraceIds` 与 `attachments` 一一对应关系由前端维护。后端不校验对应关系，仅存储用于异步机审回流。无附件时可不传或传空数组。

---

## 5. 出参字段

### TicketMpRespVO（工单详情/列表单条）

| 字段名 | 类型 | 说明 |
|-------|------|------|
| id | Long | 工单 ID |
| orderId | Long | 关联订单 ID |
| orderNo | String | 订单编号 |
| category | String | 工单分类枚举：COMPLAINT / APPEAL |
| type | String | 工单类型 |
| title | String | 标题 |
| description | String | 描述 |
| status | String | 工单状态：SUBMITTED / PROCESSING / RESOLVED / CLOSED / WITHDRAWN |
| statusName | String | 工单状态中文名 |
| canWithdraw | Boolean | 是否可撤回 |
| **auditStatus** | **String** | **wx 内容安全机审状态，取值见 §6** |
| attachments | List\<String\> | 附件 URL |
| createdAt | String | 创建时间（ISO 8601） |
| updatedAt | String | 更新时间（ISO 8601） |

---

## 6. 枚举 / 数据字典

### auditStatus — wx 内容安全机审状态

| 值 | 含义 | 触发条件 |
|----|------|---------|
| `PENDING` | 机审中 | 创建工单时有 mediaTraceIds（异步等待微信回调） |
| `APPROVED` | 已通过 | 无 mediaTraceIds 的工单直接置此（免审）；微信回调结果合规也置此 |
| `MANUAL_REVIEW` | 人工复审 | 微信机审认为需人工介入 |
| `REJECTED` | 已驳回 | 微信机审判定内容违规 |

> **重要**：`PENDING` 状态为异步，工单创建成功后微信回调可能在数秒到数分钟内到达。前端建议在工单详情页轮询或基于页面刷新展示最新状态，不要把 `PENDING` 当作错误。

### category — 工单分类

| 值 | 含义 |
|----|------|
| `COMPLAINT` | 投诉 |
| `APPEAL` | 申诉 |

---

## 7. 错误码

| 错误码 | HTTP 状态 | 含义 | 触发场景 |
|-------|---------|------|---------|
| 581001 | 400 | 工单关联订单不存在 | orderId 无效 |
| 581002 | 400 | 工单已存在，不可重复提交 | 同一订单同分类已有活跃工单 |
| 581003 | 400 | 订单状态不允许发起售后 | 订单未处于可售后状态 |
| 200-001 | 401 | 未授权 | JWT 缺失或过期 |
| 200-002 | 403 | 无权操作 | 工单不属于当前用户 |

---

## 8. 示例

### 8.1 典型成功——带图片机审

**请求**
```http
POST /v3/mp/aftersale/ticket
Authorization: Bearer <user-jwt>
Content-Type: application/json

{
  "orderId": 1234567890,
  "category": "COMPLAINT",
  "type": "HOTEL",
  "title": "酒店降级安排",
  "description": "预订了四星酒店，实际安排了三星，差价未退。",
  "attachments": [
    "https://oss.example.com/media/hotel_photo_1.jpg",
    "https://oss.example.com/media/hotel_photo_2.jpg"
  ],
  "mediaTraceIds": [
    "trace_abc123def456",
    "trace_xyz789uvw012"
  ]
}
```

**响应**
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "id": 987654321,
    "orderId": 1234567890,
    "orderNo": "HL20260622001",
    "category": "COMPLAINT",
    "type": "HOTEL",
    "title": "酒店降级安排",
    "description": "预订了四星酒店，实际安排了三星，差价未退。",
    "status": "SUBMITTED",
    "statusName": "已提交",
    "canWithdraw": true,
    "auditStatus": "PENDING",
    "attachments": [
      "https://oss.example.com/media/hotel_photo_1.jpg",
      "https://oss.example.com/media/hotel_photo_2.jpg"
    ],
    "createdAt": "2026-06-22T10:30:00",
    "updatedAt": "2026-06-22T10:30:00"
  }
}
```

> `auditStatus` 初始为 `PENDING`，等待微信异步回调后更新为 `APPROVED` / `MANUAL_REVIEW` / `REJECTED`。

### 8.2 边界情况——无附件工单（直接 APPROVED）

**请求**
```http
POST /v3/mp/aftersale/ticket
Authorization: Bearer <user-jwt>
Content-Type: application/json

{
  "orderId": 1234567890,
  "category": "APPEAL",
  "type": "PRICE",
  "title": "价格异议",
  "description": "行程中临时要求加收费用，合同未约定。",
  "attachments": [],
  "mediaTraceIds": []
}
```

**响应**
```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "id": 987654322,
    "orderId": 1234567890,
    "orderNo": "HL20260622001",
    "category": "APPEAL",
    "type": "PRICE",
    "title": "价格异议",
    "description": "行程中临时要求加收费用，合同未约定。",
    "status": "SUBMITTED",
    "statusName": "已提交",
    "canWithdraw": true,
    "auditStatus": "APPROVED",
    "attachments": [],
    "createdAt": "2026-06-22T10:35:00",
    "updatedAt": "2026-06-22T10:35:00"
  }
}
```

> 无 mediaTraceIds 或传空数组时，`auditStatus` 直接为 `APPROVED`，无需等待回调。

### 8.3 业务失败——订单已有活跃工单

**请求**（同一订单同分类已存在未关闭工单）
```http
POST /v3/mp/aftersale/ticket
Authorization: Bearer <user-jwt>
Content-Type: application/json

{
  "orderId": 1234567890,
  "category": "COMPLAINT",
  "type": "GUIDE",
  "title": "导游服务差",
  "description": "导游态度恶劣。",
  "mediaTraceIds": []
}
```

**响应**
```json
{
  "code": 581002,
  "msg": "工单已存在，不可重复提交",
  "data": null
}
```

---

## 9. 业务边界

**适用场景**：
- 订单处于可售后状态（已出行、出行中、已完成等，具体由后端校验）
- 有图片/视频附件时前端必须先完成 `wx.msgSecCheck` 得到 traceId 再调此接口
- 无附件或不需要机审时，`mediaTraceIds` 可省略或传空数组

**不适用场景**：
- 已取消订单不可发起售后
- 同一订单同分类已有活跃（非 CLOSED/WITHDRAWN）工单时，不可重复发起

**特殊边界**：
- `mediaTraceIds` 最多 12 个，超出返回参数校验错误
- `auditStatus` 为 `PENDING` 时工单照常流转（客服可正常处理），机审结果不阻塞工单流程
- `auditStatus` 为 `REJECTED` 时，仅作展示标记，工单处理流程由客服决定是否关闭

---

## 10. 修改前后对比

### 字段级对比

**CreateTicketReqVO（入参）**

| 字段 | 变更前 | 变更后 |
|------|-------|-------|
| mediaTraceIds | 不存在 | 新增，List\<String\>，可选，max=12 |

**TicketMpRespVO（出参）**

| 字段 | 变更前 | 变更后 |
|------|-------|-------|
| auditStatus | 不存在 | 新增，String，wx 机审状态 |

### 行为级对比

| 场景 | 变更前 | 变更后 |
|------|-------|-------|
| 带图片发起工单 | 无机审，无审核状态 | 存 traceId，初始 auditStatus=PENDING，微信异步回调更新 |
| 无图片发起工单 | 无机审状态字段 | auditStatus 直接置 APPROVED |

---

## 11. 影响评估 / 回滚

| 维度 | 结论 |
|------|------|
| 破坏兼容性 | 否（入参新增可选字段，旧版不传兼容；出参新增字段，旧版忽略） |
| 前端同步上线 | 建议同步：入参传 mediaTraceIds 发挥机审能力；出参展示 auditStatus |
| 回滚方案 | 后端回滚 PR #4203 即可，无 DDL 数据损失风险（DDL 只加列不删） |

---

## 12. 注意事项

1. `mediaTraceIds` 由前端在调用微信 `wx.msgSecCheck` 后获得，后端不直接调微信安全接口，只做存储和转发。
2. `auditStatus=PENDING` 是异步状态，不代表工单异常，客服后台可正常处理。
3. 历史工单（此 PR 上线前创建的）`auditStatus` 字段为 `null`，前端展示时建议视 `null` 等同于 `APPROVED` 处理。
4. 微信机审结果 `REJECTED` 不自动关闭工单，仅打标记，由客服决定后续处理。

---

## 13. 关联 / 联系人

- **Issue**：[https://git.1814.love:8443/wx/HL/issues/4196](https://git.1814.love:8443/wx/HL/issues/4196)
- **PR**：[https://git.1814.love:8443/wx/HL/pulls/4203](https://git.1814.love:8443/wx/HL/pulls/4203)
- **Commit**：[https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15](https://git.1814.love:8443/wx/HL/commit/8882eb1568a7e5ab8f48a6db4b7e3d4d4b6f2c15)
- **关联 Issue**：[#4161 售后申诉中心](https://git.1814.love:8443/wx/HL/issues/4161)
- **后端负责人**：yaosutu
