7.8 KiB
查询白名单公开添加接口 — 对接文档
面向下游调用方(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 字段:
{
"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,解密成功即证明调用方持有正确密钥。
{
"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]、[{}]remarkstring 否
2.4 IP 白名单
生产环境下,调用方 IP 须在该用户控制台配置的 IP 白名单内(与业务 API 一致)。开发环境(app.env=development)跳过。
3. 加密算法
与业务 API 完全一致(AES-128-CBC + PKCS7 + 随机 IV):
- Access Key 为 16 进制字符串,解码为 16 字节密钥
- 随机生成 16 字节 IV,拼在密文前
- 整体 Base64 编码后放入
data
参考示例:tyapi-frontend/public/examples/nodejs/demo.js
4. 响应格式
与业务 API 一致:
{
"code": 0,
"message": "业务成功",
"transaction_id": "uuid",
"data": "<AES 加密后的规则详情>"
}
data 解密后为:
{
"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 调用示例
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 段之后),不要写在上游数据源配置块内:
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):
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 也会自动加列。