# 查询白名单公开添加接口 — 对接文档 > 面向**下游调用方**(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": "" } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `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": "" } ``` `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 也会自动加列。