273 lines
7.7 KiB
Markdown
273 lines
7.7 KiB
Markdown
# 用户举报 Flutter 对接
|
||
|
||
本文描述 Flutter App 对接用户举报和房间举报的 HTTP 契约。通用文件上传接口用于上传举报图片并拿到 URL,提交举报接口只接收 JSON。
|
||
|
||
## 基础地址
|
||
|
||
本地开发地址:
|
||
|
||
```text
|
||
http://127.0.0.1:13000
|
||
```
|
||
|
||
线上环境使用当前 App 配置里的 gateway base URL。下面所有路径都拼在 gateway base URL 后。
|
||
|
||
## 请求头
|
||
|
||
| Header | 必填 | 示例 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `Authorization` | 是 | `Bearer <access_token>` | 登录 access token;举报发起人只从 token 读取,Flutter 不传举报发起人 ID。 |
|
||
| `X-App-Code` | 否 | `lalu` | App 编码;登录态接口最终以 token 内的 `app_code` 为准。 |
|
||
| `Content-Type` | 提交举报必填 | `application/json` | 提交举报使用 JSON。 |
|
||
| `Content-Type` | 上传图片必填 | `multipart/form-data` | 上传举报图片使用 multipart。 |
|
||
|
||
统一响应 envelope:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_abc",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
Flutter 只在 `code == "OK"` 时读取 `data`。失败时记录 `request_id`,用于后端排查。
|
||
|
||
## 调用顺序
|
||
|
||
```text
|
||
1. 用户在举报弹窗选择举报类型。
|
||
2. 如有举报图,逐张调用 POST /api/v1/files/upload,收集每次返回的 data.url。
|
||
3. 调用 POST /api/v1/reports 提交举报。
|
||
4. 提交成功后关闭弹窗,并用 data.report_id 写本地日志。
|
||
```
|
||
|
||
## 上传举报图片
|
||
|
||
上传接口只负责把文件写入对象存储并返回 URL,不创建举报单。
|
||
|
||
```http
|
||
POST /api/v1/files/upload
|
||
Authorization: Bearer <access_token>
|
||
X-App-Code: lalu
|
||
Content-Type: multipart/form-data
|
||
|
||
file=<binary>
|
||
```
|
||
|
||
请求字段:
|
||
|
||
| 字段 | 必填 | 类型 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `file` | 是 | multipart file | 单文件最大 20MB。多图逐张上传,Flutter 把每张图返回的 `data.url` 放进提交举报接口的 `image_urls`。 |
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_upload_report_image",
|
||
"data": {
|
||
"url": "https://cdn.example.com/app/files/10001/20260529/file_xxx.jpg",
|
||
"object_key": "app/files/10001/20260529/file_xxx.jpg",
|
||
"content_type": "image/jpeg",
|
||
"size_bytes": 345678
|
||
}
|
||
}
|
||
```
|
||
|
||
Flutter 提交举报时只需要使用 `data.url`。
|
||
|
||
## 提交举报
|
||
|
||
```http
|
||
POST /api/v1/reports
|
||
Authorization: Bearer <access_token>
|
||
X-App-Code: lalu
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"user_id": "10002",
|
||
"report_type": "pornography",
|
||
"reason": "用户在房间内发布违规内容",
|
||
"image_urls": [
|
||
"https://cdn.example.com/app/files/10001/20260529/file_xxx.jpg"
|
||
]
|
||
}
|
||
```
|
||
|
||
举报房间示例:
|
||
|
||
```http
|
||
POST /api/v1/reports
|
||
Authorization: Bearer <access_token>
|
||
X-App-Code: lalu
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"room_id": "lalu_abc123",
|
||
"report_type": "fraud",
|
||
"reason": "房间引导线下交易",
|
||
"image_urls": []
|
||
}
|
||
```
|
||
|
||
请求字段:
|
||
|
||
| 字段 | 必填 | 类型 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| `user_id` | 条件必填 | string | 被举报用户 ID。和 `room_id` 二选一,只能传一个。 |
|
||
| `room_id` | 条件必填 | string | 被举报房间 ID。和 `user_id` 二选一,只能传一个。 |
|
||
| `report_type` | 是 | string | 举报类型,取值见下方枚举。 |
|
||
| `reason` | 否 | string | 举报理由;Flutter 可允许用户不填。 |
|
||
| `image_urls` | 否 | string[] | 举报图 URL 数组;无图可传 `[]` 或不传。 |
|
||
|
||
目标校验规则:
|
||
|
||
| 场景 | 结果 |
|
||
| --- | --- |
|
||
| 只传 `user_id` | 举报用户。 |
|
||
| 只传 `room_id` | 举报房间。 |
|
||
| `user_id` 和 `room_id` 都不传 | `400 INVALID_ARGUMENT`。 |
|
||
| `user_id` 和 `room_id` 同时传 | `400 INVALID_ARGUMENT`,Flutter 需要拆成一次明确的用户举报或房间举报。 |
|
||
|
||
举报发起人不在请求体里传。gateway 必须从 `Authorization` 对应的 access token 读取当前登录用户,避免客户端伪造举报发起人。
|
||
|
||
## 举报类型
|
||
|
||
| UI 文案 | `report_type` |
|
||
| --- | --- |
|
||
| Politics | `politics` |
|
||
| Pornography | `pornography` |
|
||
| Graphic Violence | `graphic_violence` |
|
||
| Verbal Abuse | `verbal_abuse` |
|
||
| Fraud | `fraud` |
|
||
| Illegal Content | `illegal_content` |
|
||
| Minors | `minors` |
|
||
| In-Person Transactions | `in_person_transactions` |
|
||
| Other | `other` |
|
||
|
||
## 返回体模拟数据
|
||
|
||
举报用户成功:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_report_user",
|
||
"data": {
|
||
"report_id": "rpt_10001_1778000000000",
|
||
"target_type": "user",
|
||
"user_id": "10002",
|
||
"room_id": "",
|
||
"report_type": "pornography",
|
||
"status": "submitted",
|
||
"created_at_ms": 1778000000000
|
||
}
|
||
}
|
||
```
|
||
|
||
举报房间成功:
|
||
|
||
```json
|
||
{
|
||
"code": "OK",
|
||
"message": "ok",
|
||
"request_id": "req_report_room",
|
||
"data": {
|
||
"report_id": "rpt_10001_1778000001000",
|
||
"target_type": "room",
|
||
"user_id": "",
|
||
"room_id": "lalu_abc123",
|
||
"report_type": "fraud",
|
||
"status": "submitted",
|
||
"created_at_ms": 1778000001000
|
||
}
|
||
}
|
||
```
|
||
|
||
字段说明:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `report_id` | string | 举报单 ID;Flutter 可用于本地日志和客服排查。 |
|
||
| `target_type` | string | `user` 或 `room`,由服务端按本次传入目标生成。 |
|
||
| `user_id` | string | 被举报用户 ID;房间举报返回空字符串。 |
|
||
| `room_id` | string | 被举报房间 ID;用户举报返回空字符串。 |
|
||
| `report_type` | string | 本次提交的举报类型。 |
|
||
| `status` | string | 首次提交固定返回 `submitted`。 |
|
||
| `created_at_ms` | int64 | 举报创建时间,Unix epoch milliseconds。 |
|
||
|
||
## 错误处理
|
||
|
||
失败返回仍使用统一 envelope:
|
||
|
||
```json
|
||
{
|
||
"code": "INVALID_ARGUMENT",
|
||
"message": "invalid argument",
|
||
"request_id": "req_abc"
|
||
}
|
||
```
|
||
|
||
| HTTP 状态码 | `code` | Flutter 处理 |
|
||
| --- | --- | --- |
|
||
| `400` | `INVALID_JSON` | JSON 格式错误,检查本地序列化逻辑。 |
|
||
| `400` | `INVALID_ARGUMENT` | `user_id/room_id` 二选一不满足、`report_type` 非法、图片 URL 格式非法或字段超长。 |
|
||
| `401` | `UNAUTHORIZED` | token 缺失、无效、过期或会话失效,重新登录。 |
|
||
| `403` | `PROFILE_REQUIRED` | 用户资料未完成,跳转资料补全流程。 |
|
||
| `404` | `NOT_FOUND` | 被举报用户不存在,刷新页面状态。 |
|
||
| `502` | `UPSTREAM_ERROR` | 内部服务暂不可用,可提示稍后重试。 |
|
||
|
||
## Flutter 数据模型建议
|
||
|
||
```dart
|
||
class SubmitReportRequest {
|
||
SubmitReportRequest({
|
||
this.userId,
|
||
this.roomId,
|
||
required this.reportType,
|
||
this.reason,
|
||
this.imageUrls = const [],
|
||
});
|
||
|
||
final String? userId;
|
||
final String? roomId;
|
||
final String reportType;
|
||
final String? reason;
|
||
final List<String> imageUrls;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
if (userId != null && userId!.isNotEmpty) 'user_id': userId,
|
||
if (roomId != null && roomId!.isNotEmpty) 'room_id': roomId,
|
||
'report_type': reportType,
|
||
if (reason != null && reason!.trim().isNotEmpty) 'reason': reason!.trim(),
|
||
if (imageUrls.isNotEmpty) 'image_urls': imageUrls,
|
||
};
|
||
}
|
||
```
|
||
|
||
提交前本地校验:
|
||
|
||
```dart
|
||
void validateReportTarget({String? userId, String? roomId}) {
|
||
final hasUser = userId != null && userId.trim().isNotEmpty;
|
||
final hasRoom = roomId != null && roomId.trim().isNotEmpty;
|
||
if (hasUser == hasRoom) {
|
||
throw ArgumentError('user_id and room_id must be exactly one');
|
||
}
|
||
}
|
||
```
|
||
|
||
## 实现边界
|
||
|
||
- Flutter 不传举报发起人 ID,服务端只认 token 中的当前用户。
|
||
- `user_id` 表示被举报用户,不是举报发起人。
|
||
- `room_id` 表示被举报房间,不要求同时传房间内某个用户。
|
||
- `user_id` 和 `room_id` 必须二选一,不能同时提交一个混合举报。
|
||
- 举报图片先上传再提交 URL;提交举报接口不接收 multipart 文件。
|
||
- `message` 不作为业务分支依据,Flutter 只按 `code` 和 HTTP 状态处理。
|