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

11 KiB
Raw Blame History

查询白名单公开接口 — 对接文档

面向下游调用方API 用户 / 合作方),与上游数据源配置无关。
平台侧仅在 config.yaml 顶部保留一份 query_whitelist.public_api 配置。


1. 接口一览

接口 方法 路径 用途
创建规则 POST /api/v1/query-whitelist/entries 新建一条屏蔽规则;同用户+身份证+姓名已存在则 拒绝1013
追加接口 POST /api/v1/query-whitelist/entries/append 已有规则追加 api_codes(去重合并),不新建记录;规则不存在则 拒绝1014

两条接口鉴权、加密、响应格式相同,仅业务语义不同。

命中白名单后,对应 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 Key16 进制 Secret Key加密。Access Key 仅用于本地加密,不要写入明文 JSON

2.3 明文业务参数(加密前 JSON两个接口字段相同

无需在明文里传 keyaccess_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禁止非数组类型;禁止通配符 *
remark string 备注,最长 500 字符;追加接口传入非空时会覆盖原备注

api_codes 约束(公开接口专属)

传参方式 是否允许 示例
字符串数组 ["FLXG0V4B", "JRZQ8A2D"]
单个字符串 "FLXG0V4B"
通配全部 * ["*"]
空数组 []
非字符串元素 [123][{}]

2.4 IP 白名单

生产环境下,调用方 IP 须在该用户控制台配置的 IP 白名单内(与业务 API 一致)。开发环境(app.env=development)跳过。


3. 两个接口的业务区别

规则的唯一键为 user_id + 身份证 + name,每个组合在表里只有一条记录,api_codes 存在该行的 JSON 数组中。

3.1 创建接口 POST /entries

用于首次为某身份证建立屏蔽规则。

场景 行为
规则不存在 新建 1 条记录
同用户+身份证+姓名已存在 返回 1013 规则已存在不新建、不合并

示例:第一次传 api_codes: ["FLXG0V4B"] → 成功新建。

3.2 追加接口 POST /entries/append

用于在已有规则上补充更多生效接口,适合「先挡一个、后续再加」的场景。

场景 行为
规则已存在 更新原记录:将本次 api_codes 与已有列表去重合并(保留原顺序,新编码追加在后),不新建第二条
本次编码均已存在 仍返回成功(幂等),api_codes 不变
规则不存在 返回 1014 规则不存在,请先调用创建接口
原规则 api_codes*(全部接口) 返回 1003,公开接口不支持对「全部接口」规则追加

追加示例:

已有记录user_id=U1, id_card=350681..., name=*, api_codes=["FLXG0V4B"]

第二次调用 append
  api_codes: ["JRZQ8A2D"]

结果(同一条记录被更新):
  api_codes: ["FLXG0V4B", "JRZQ8A2D"]   ← 合并去重,非新建
第三次再 append
  api_codes: ["JRZQ8A2D", "FLXG2E8F"]

结果:
  api_codes: ["FLXG0V4B", "JRZQ8A2D", "FLXG2E8F"]   ← JRZQ8A2D 已存在则跳过

3.3 推荐调用顺序

  1. 首次屏蔽 → 调 创建接口,可一次传齐所有 api_codes
  2. 后续补接口 → 调 追加接口,只传新增的编码即可
  3. 若创建时返回 1013 → 改调 追加接口

4. 加密算法

与业务 API 完全一致AES-128-CBC + PKCS7 + 随机 IV

  1. Access Key 为 16 进制字符串,解码为 16 字节密钥
  2. 随机生成 16 字节 IV拼在密文前
  3. 整体 Base64 编码后放入 data

参考示例:tyapi-frontend/public/examples/nodejs/demo.js


5. 响应格式

与业务 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便于审计。


6. 错误码

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 规则已存在 创建接口:同用户下身份证+姓名重复
1014 规则不存在 追加接口:须先调用创建接口
1001 接口异常 系统错误

7. Node.js 调用示例

7.1 创建规则

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'; // 平台管理密钥,见文档 §8

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.2 追加生效接口(去重合并)

async function appendWhitelistApiCodes() {
  const payload = {
    name: '*',
    id_card: '350681198611130611',
    api_codes: ['JRZQ8A2D'],  // 只传本次要追加的编码
    remark: '',
  };

  const res = await fetch(`${BASE_URL}/api/v1/query-whitelist/entries/append`, {
    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)));
  }
}

// appendWhitelistApiCodes();

8. 平台配置(仅一份)

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


9. 字段说明补充

name = *

通配姓名:只要请求中的身份证号与本规则一致,无论传入什么姓名都会命中并返回「查询为空」。

name = 具体姓名

须身份证 + 姓名同时一致才命中。

用户隔离

使用谁的 Access-Id 调用(请求头),规则就只对该用户(user_id)下的 API 调用生效,不会影响其他用户,也不能创建 user_id=* 的全局规则。


10. 数据库变更

新增字段 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: trueGORM 也会自动加列。