Files
tyapi-server/docs/查询白名单公开添加接口对接文档.md
2026-06-19 10:49:13 +08:00

262 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 查询白名单公开添加接口 — 对接文档
> 面向**下游调用方**API 用户 / 合作方),与上游数据源配置无关。
> 平台侧仅在 `config.yaml` 顶部保留**一份** `query_whitelist.public_api` 配置。
---
## 1. 接口概述
| 项 | 说明 |
|----|------|
| 地址 | `POST /api/v1/query-whitelist/entries` |
| 用途 | 为**当前 access_id 对应用户**添加查询白名单规则 |
| 生效范围 | **仅对该用户生效**`user_id` 自动绑定,不可创建全局规则) |
| 计费 | 否 |
命中白名单后,对应 API 调用将返回 `1000 查询为空`,不调用上游、不扣费。
---
## 2. 鉴权(双重 + IP
### 2.1 请求头
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `Access-Id` | string | 是 | 目标 API 用户的 Access-Id |
| `Whitelist-Mgmt-Key` | string | 是 | 平台下发的**独立管理密钥**(与 Access Key 不同) |
| `Content-Type` | string | 是 | 固定为 `application/json` |
### 2.2 请求体(加密)
与业务 API 相同,外层仅传 `data` 字段:
```json
{
"data": "<AES-128-CBC Base64 密文>"
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `data` | string | 是 | 业务参数的 AES-128-CBC 密文Base64 编码) |
使用目标用户的 **Access Key**16 进制 Secret Key加密。Access Key **仅用于本地加密,不要写入明文 JSON**
### 2.3 明文业务参数(加密前 JSON
**无需在明文里传 `key` 或 `access_id`**。身份校验方式与业务 API 相同:请求头携带 `Access-Id`,服务端用该用户 Access Key 解密 `data`,解密成功即证明调用方持有正确密钥。
```json
{
"name": "*",
"id_card": "350681198611130611",
"api_codes": ["FLXG0V4B", "JRZQ8A2D"],
"remark": "可选备注"
}
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `name` | string | 是 | 姓名;填 `*` 表示只匹配身份证,不校验姓名 |
| `id_card` | string | 是 | 18 位身份证号 |
| `api_codes` | string[] | 是 | 生效的产品编码列表;**必须为 JSON 数组**,至少 1 个元素;元素类型为 string**禁止**传字符串/对象等非数组类型;**禁止**包含通配符 `*`(不可写 `["*"]` 或与其他编码混用) |
> **`api_codes` 约束(公开接口专属)**
>
> | 传参方式 | 是否允许 | 示例 |
> |----------|----------|------|
> | 字符串数组 | ✅ | `["FLXG0V4B", "JRZQ8A2D"]` |
> | 单个字符串 | ❌ | `"FLXG0V4B"` |
> | 通配全部 `*` | ❌ | `["*"]` |
> | 空数组 | ❌ | `[]` |
> | 非字符串元素 | ❌ | `[123]`、`[{}]` |
| `remark` | string | 否 | 备注,最长 500 字符 |
### 2.4 IP 白名单
生产环境下,调用方 IP 须在该用户控制台配置的 IP 白名单内(与业务 API 一致)。开发环境(`app.env=development`)跳过。
---
## 3. 加密算法
与业务 API 完全一致AES-128-CBC + PKCS7 + 随机 IV
1. Access Key 为 16 进制字符串,解码为 16 字节密钥
2. 随机生成 16 字节 IV拼在密文前
3. 整体 Base64 编码后放入 `data`
参考示例:`tyapi-frontend/public/examples/nodejs/demo.js`
---
## 4. 响应格式
与业务 API 一致:
```json
{
"code": 0,
"message": "业务成功",
"transaction_id": "uuid",
"data": "<AES 加密后的规则详情>"
}
```
`data` 解密后为:
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"is_global": false,
"name": "*",
"id_card_masked": "350681********0611",
"api_codes": ["FLXG0V4B", "JRZQ8A2D"],
"status": "enabled",
"remark": "",
"operation_ip": "203.0.113.10",
"created_at": "2026-06-18T10:00:00+08:00",
"updated_at": "2026-06-18T10:00:00+08:00"
}
```
`operation_ip` 为本次添加时的客户端 IP便于审计。
---
## 5. 错误码
| code | message | 说明 |
|------|---------|------|
| 0 | 业务成功 | 创建成功 |
| 1002 | 解密失败 | `data` 解密失败 |
| 1003 | 请求参数结构不正确 | 明文参数缺失或格式错误 |
| 1004 | 未经授权的IP | IP 不在白名单 |
| 1005 | 缺少Access-Id | 请求头缺失 |
| 1006 | 未经授权的AccessId | Access-Id 无效、解密失败或账户冻结 |
| 1010 | 缺少管理密钥 | 未传 `Whitelist-Mgmt-Key` |
| 1011 | 管理密钥无效 | 管理密钥错误 |
| 1012 | 公开接口未启用 | 服务端 `public_api.enabled=false` |
| 1013 | 规则已存在 | 同用户下身份证+姓名重复 |
| 1001 | 接口异常 | 系统错误 |
---
## 6. Node.js 调用示例
```javascript
const crypto = require('crypto');
const BASE_URL = 'https://api.tianyuanapi.com';
const ACCESS_ID = process.env.ACCESS_ID;
const ACCESS_KEY = process.env.ACCESS_KEY; // 用户 Access Key
const MGMT_KEY = '2R12TmEc1e8P3p69RdIoN5Ykjk%H@4orPy7DZv7MXpGByoEL'; // 平台管理密钥,见文档 §7
function encrypt(data, keyHex) {
const key = Buffer.from(keyHex, 'hex');
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
cipher.setAutoPadding(true);
let enc = cipher.update(data, 'utf8');
enc = Buffer.concat([iv, enc, cipher.final()]);
return enc.toString('base64');
}
function decrypt(data, keyHex) {
const key = Buffer.from(keyHex, 'hex');
const buf = Buffer.from(data, 'base64');
const iv = buf.slice(0, 16);
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
decipher.setAutoPadding(true);
let dec = decipher.update(buf.slice(16));
dec = Buffer.concat([dec, decipher.final()]);
return dec.toString('utf8');
}
async function addWhitelistEntry() {
const payload = {
name: '*',
id_card: '350681198611130611',
api_codes: ['FLXG0V4B', 'FLXG2E8F', 'JRZQ8A2D'],
remark: '外部系统添加',
};
const res = await fetch(`${BASE_URL}/api/v1/query-whitelist/entries`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Id': ACCESS_ID,
'Whitelist-Mgmt-Key': MGMT_KEY,
},
body: JSON.stringify({ data: encrypt(JSON.stringify(payload), ACCESS_KEY) }),
});
const json = await res.json();
console.log('响应:', json);
if (json.code === 0 && json.data) {
console.log('规则详情:', JSON.parse(decrypt(json.data, ACCESS_KEY)));
}
}
addWhitelistEntry();
```
---
## 7. 平台配置(仅一份)
`config.yaml` 顶部(`app` 段之后),**不要**写在上游数据源配置块内:
```yaml
query_whitelist:
public_api:
enabled: true
management_key: "2R12TmEc1e8P3p69RdIoN5Ykjk%H@4orPy7DZv7MXpGByoEL"
```
| 配置项 | 类型 | 说明 |
|--------|------|------|
| `enabled` | bool | 是否启用公开接口 |
| `management_key` | string | 平台独立管理密钥(当前 **48 位**),调用时放入请求头 `Whitelist-Mgmt-Key` |
**当前环境管理密钥(`Whitelist-Mgmt-Key`**
```
2R12TmEc1e8P3p69RdIoN5Ykjk%H@4orPy7DZv7MXpGByoEL
```
> 该密钥与下游用户的 Access Key 无关,仅用于授权调用本公开接口。生产环境轮换密钥后请同步更新本文档与 `config.yaml`。
---
## 8. 字段说明补充
### `name = *`
通配姓名:只要请求中的**身份证号**与本规则一致,无论传入什么姓名都会命中并返回「查询为空」。
### `name = 具体姓名`
须身份证 + 姓名**同时一致**才命中。
### 用户隔离
使用谁的 `Access-Id` 调用(请求头),规则就只对该用户(`user_id`)下的 API 调用生效,不会影响其他用户,也不能创建 `user_id=*` 的全局规则。
---
## 9. 数据库变更
新增字段 `operation_ip`(记录公开接口添加时的客户端 IP
```sql
ALTER TABLE query_whitelist_entries
ADD COLUMN IF NOT EXISTS operation_ip VARCHAR(64) DEFAULT '';
```
脚本:`scripts/migrate_query_whitelist_operation_ip.sql`
`auto_migrate: true`GORM 也会自动加列。