提示信息
# 法律文档接入指南
> 涉及接口:隐私政策 · 用户协议 · 个人信息收集清单 · 第三方共享清单
>
> 公共传参、签名规则、错误码见《API认证与签名规范》
---
## 概览
| 文档类型 `type` | 名称 | 工信部/应用市场填写链接 |
|---|---|---|
| `privacy` | 隐私政策 | `https://your-domain.com/legal/privacy` |
| `terms` | 用户协议 | `https://your-domain.com/legal/terms` |
| `info_collection` | 个人信息收集清单 | `https://your-domain.com/legal/info_collection` |
| `third_party` | 第三方信息共享清单 | `https://your-domain.com/legal/third_party` |
> 这 4 个 HTML 页面无需登录、无需签名,浏览器直接访问即可。可以直接填到应用市场和工信部备案的相关位置。
---
## 一、冷启动:版本检查与弹窗逻辑
### 整体流程
```
App 冷启动
│
├─ GET /app/start(已有)
│ └─ 返回 privacy.version_code(当前要求的版本码)
│
└─ 本地读取 已同意版本码(本地持久化存储)
│
├─ 本地 == 服务端 → 正常进入 App
│
└─ 本地 < 服务端 → 弹出隐私政策弹窗
│
├─ 用户点「同意」
│ ├─ POST /user/agree-privacy (登录态下,服务端记录版本)
│ ├─ 本地持久化最新 version_code
│ └─ 正常进入 App
│
└─ 用户点「不同意」→ 退出 App(合规要求)
```
> **注意**:`/app/start` 返回的 `privacy` 字段包含版本码,客户端本地只需存 `accepted_version_code` 这一个整数即可。
> 未登录用户在注册/登录成功后,也需立即调用 `POST /user/agree-privacy` 补录同意记录。
---
## 二、获取文档内容(App 内展示)
### `GET /v1/doc/{type}`
**白名单,无需 Token,无需签名。**
App 内 WebView、同意弹窗里展示协议全文时调用。也可以在弹窗里只显示摘要,点"查看全文"时再加载。
#### Path 参数
| 参数 | 类型 | 说明 |
|---|---|---|
| `type` | string | `privacy` / `terms` / `info_collection` / `third_party` |
#### Query 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `platform` | string | — | `ios` / `android`。传入后服务端过滤掉不匹配的平台专属内容块,返回纯净 HTML;不传则返回完整内容 |
**推荐做法**:`info_collection` 和 `third_party` 务必传 `platform`,隐私政策和用户协议一般无平台差异可不传。
#### 请求示例
```
GET /v1/doc/third_party?platform=ios
GET /v1/doc/info_collection?platform=android
GET /v1/doc/privacy
```
#### 响应示例
```json
{
"code": 0,
"msg": "success",
"data": {
"type": "third_party",
"version_code": 2,
"version": "2025-03-28",
"title": "第三方信息共享清单",
"content": "<p>通用内容...</p><section data-platform=\"ios\">iOS 专属 SDK...</section>",
"published_at": 1743091200
},
"server_time": 1743100000
}
```
> 传了 `?platform=ios` 后,返回的 `content` 中不再含有 `<section data-platform="android">` 的内容,直接渲染即可。
#### 响应字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| `type` | string | 文档类型,同请求参数 |
| `version_code` | int | **数字版本码**,与本地缓存的已同意版本比较,判断是否需要重新弹窗 |
| `version` | string | 展示版本号(如 `2025-03-28`),显示在弹窗或页面顶部 |
| `title` | string | 文档标题,如「隐私政策」 |
| `content` | string | **HTML 正文片段**(body 内容,不含 `<html>/<head>` 标签),直接塞入 WebView 渲染 |
| `published_at` | int | 生效时间戳(Unix 秒) |
#### 客户端渲染建议
- **iOS(WKWebView)**:用 `loadHTMLString(_:baseURL:)` 加载,可在 `baseURL` 里传入域名让相对路径图片正常显示
- **Android(WebView)**:用 `loadDataWithBaseURL` 加载,`encoding` 传 `UTF-8`
- **Flutter(webview_flutter)**:用 `controller.loadHtmlString(content)` 加载,或用 `flutter_html` 包直接渲染
- 建议在 `<content>` 外层包一层基础 CSS(字体、行高、边距),由客户端统一注入,保持与 App 视觉风格一致
---
## 三、内容编写规范(管理后台)
> 这一节给**编辑文档内容的人**看,前端工程师可跳过。
### 平台专属内容的写法
文档正文是 HTML 片段。需要区分 iOS / Android 的段落,用 `<section data-platform="ios">` 或 `<section data-platform="android">` 包裹;**无 data-platform 属性的内容两端均显示**。
```html
<!-- ✅ 通用内容(iOS 和 Android 都显示)-->
<h2>一、基本信息</h2>
<p>我们使用以下第三方 SDK 提供服务……</p>
<!-- ✅ 仅 iOS 显示 -->
<section data-platform="ios">
<h3>iOS 平台 SDK 清单</h3>
<table>
<tr><th>SDK 名称</th><th>公司</th><th>用途</th><th>收集信息</th></tr>
<tr><td>微信 OpenSDK</td><td>腾讯</td><td>微信分享/登录</td><td>设备信息</td></tr>
<tr><td>支付宝 SDK</td><td>蚂蚁集团</td><td>支付</td><td>设备信息、交易信息</td></tr>
<tr><td>穿山甲 SDK</td><td>字节跳动</td><td>广告投放</td><td>设备信息、广告标识符</td></tr>
</table>
</section>
<!-- ✅ 仅 Android 显示 -->
<section data-platform="android">
<h3>Android 平台 SDK 清单</h3>
<table>
<tr><th>SDK 名称</th><th>公司</th><th>用途</th><th>收集信息</th></tr>
<tr><td>微信 OpenSDK</td><td>腾讯</td><td>微信分享/登录</td><td>设备信息</td></tr>
<tr><td>支付宝 SDK</td><td>蚂蚁集团</td><td>支付</td><td>设备信息、交易信息</td></tr>
<tr><td>华为推送 SDK</td><td>华为</td><td>消息推送</td><td>设备标识符</td></tr>
<tr><td>小米推送 SDK</td><td>小米</td><td>消息推送</td><td>设备标识符</td></tr>
</table>
</section>
```
### 规则说明
| 规则 | 说明 |
|---|---|
| 标签必须是 `<section>` | 过滤逻辑基于 `<section data-platform="...">...</section>`,其他标签不会被过滤 |
| `data-platform` 只允许 `ios` 或 `android` | 其他值不会被过滤(等同于通用内容) |
| `<section>` 不能嵌套 | 平台块内部不要再嵌套另一个 `<section data-platform>`,会导致过滤不完整 |
| 管理后台预览时两种内容均显示 | 平台块左上角会显示 `ios` / `android` 角标,方便区分 |
### 哪些文档需要区分平台
| 文档 | 是否区分 | 说明 |
|---|---|---|
| 隐私政策 `privacy` | 一般不需要 | 内容相同,偶有差异时可用平台块局部标注 |
| 用户协议 `terms` | 不需要 | |
| 个人信息收集清单 `info_collection` | **需要** | iOS/Android 收集的权限和字段不同 |
| 第三方共享清单 `third_party` | **需要** | 推送/广告 SDK 完全不同 |
---
## 四、用户同意隐私政策
### `POST /v1/user/agree-privacy`
**需要 Token(登录态)。**
用户在弹窗点击「同意」后调用,服务端将当前隐私政策版本码写入 `users.accepted_privacy_version`。
> **中间件强制检查**:所有需登录的接口(除白名单外)在每次请求时都会比对用户已同意版本与当前最新版本。
> 若用户未同意最新版,接口统一返回以下错误,客户端收到后需跳转弹窗:
```json
{
"code": 1010,
"msg": "隐私政策已更新,请重新阅读并同意"
}
```
#### 请求体
无需请求体(`{}`)。服务端自动获取当前最新版本码写入。
#### 响应示例
```json
{
"code": 0,
"msg": "success",
"data": true,
"server_time": 1743100000
}
```
---
## 五、弹窗设计规范
### 首次启动 / 版本更新弹窗
```
┌─────────────────────────────────┐
│ │
│ 同伴 App 隐私政策 │
│ │
│ 我们更新了《隐私政策》,请在使用 │
│ 前仔细阅读并同意。 │
│ │
│ · 我们如何收集和使用您的信息 │
│ · 我们如何共享您的信息 │
│ · 您的权利 │
│ │
│ 查看《隐私政策》全文 ↗ │
│ 查看《用户协议》全文 ↗ │
│ │
│ [ 不同意,退出 ] [ 同意并继续 ] │
└─────────────────────────────────┘
```
**行为规范**:
- 「查看全文」:跳转 WebView 页面(加载 `/v1/doc/{type}`),返回后弹窗仍存在
- 「同意并继续」:调用 `POST /user/agree-privacy` → 本地存 `version_code` → 关闭弹窗
- 「不同意,退出」:直接退出 App(合规强制要求,不可绕过)
- 弹窗**不可下拉关闭**、**点击遮罩不关闭**
### 版本号本地存储
```
Key: accepted_privacy_version
Type: Int
位置: UserDefaults(iOS)/ SharedPreferences(Android)/ flutter_secure_storage(Flutter)
默认值: 0
```
---
## 六、设置页面中的协议入口
设置页通常需要提供 4 类文档的查看入口(非弹窗,直接跳转 WebView):
```
设置
└─ 关于 / 法律条款
├─ 隐私政策 → WebView: GET /v1/doc/privacy
├─ 用户协议 → WebView: GET /v1/doc/terms
├─ 个人信息收集清单 → WebView: GET /v1/doc/info_collection
└─ 第三方信息共享清单 → WebView: GET /v1/doc/third_party
```
这里可直接用 HTML 链接代替 API 调用(二者内容完全一致):
- `https://your-domain.com/legal/privacy`(浏览器或 WebView 均可打开)
---
## 七、错误码
| 错误码 | 场景 | 客户端处理 |
|---|---|---|
| `1002` | `type` 参数非法 | 检查传参,不需要提示用户 |
| `1004` | 该类型文档尚未发布 | 提示「暂无内容,请稍后再试」 |
| `1010` | 用户未同意最新隐私政策 | 跳转隐私政策弹窗,用户同意后重试原请求 |
---
## 八、接口速查
| 接口 | 方法 | 鉴权 | 用途 |
|---|---|---|---|
| `/v1/doc/{type}` | GET | 无需 | App 内获取文档内容(JSON) |
| `/v1/user/agree-privacy` | POST | Token | 记录用户同意隐私政策 |
| `https://your-domain.com/legal/{type}` | — | 无需 | 浏览器直接访问(工信部/应用市场链接) |
| `https://your-domain.com/legal/{type}/{version}` | — | 无需 | 查看历史版本(如 `/legal/privacy/2025-01-01`) |