一、在 BFF 层重塑风控数据结构
在现代金融 SaaS 平台或信贷管理后台的开发中,前端往往需要展示一个可视化的“借款人风险仪表盘”。然而,上游风控接口为了追求传输效率和扩展性,通常返回扁平化的数据结构。
天远API 的“综合多头”接口(JRZQ8F7C)就是一个典型例子。它一次性聚合了 多头借贷、逾期黑名单、团伙欺诈 等五大类指标,返回一个包含数百个对象的 KV 数组。如果直接将这个数组透传给前端,前端逻辑会变得异常臃肿。
本文将演示如何在 Node.js 中间件层高效接入此接口。我们将利用 crypto 模块实现 AES-128-CBC 加密通信,并编写一个智能的 Data Transformer,将扁平的 riskCode 数组清洗为层级分明的业务对象,为前端提供“开箱即用”的 JSON 数据。
二、API接口调用示例(Node.js版)
1. 接口技术规范
- 接口地址:
https://api.tianyuanapi.com/api/v1/JRZQ8F7C - HTTP 方法:POST
- 鉴权:Header 需携带
Access-Id。 - Payload:Body 中的
data字段为 AES 加密并 Base64 编码后的字符串。 - 必填参:
authorized: "1"(必须获得用户授权)。
2. Node.js 完整实现代码
本示例包含 AES 加解密工具,以及一个核心的 transformData 函数,用于将原始数据分类重组。
JavaScript
const axios = require('axios');
const crypto = require('crypto');
// 配置信息
const CONFIG = {
apiUrl: '<https://api.tianyuanapi.com/api/v1/JRZQ8F7C>',
accessId: 'YOUR_ACCESS_ID',
accessKey: 'YOUR_ACCESS_KEY_HEX' // 16字节Hex字符串
};
class ComprehensiveRiskService {
constructor() {
this.key = Buffer.from(CONFIG.accessKey, 'utf-8').slice(0, 16);
this.algorithm = 'aes-128-cbc';
}
// --- AES 加密 (AES-128-CBC + PKCS7) ---
encrypt(dataObj) {
try {
const iv = crypto.randomBytes(16);
const plaintext = JSON.stringify(dataObj);
const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'base64');
encrypted += cipher.final('base64');
// 拼接 IV (16bytes) + 密文 -> Base64
const ivBuffer = iv;
const encryptedBuffer = Buffer.from(encrypted, 'base64');
const combinedBuffer = Buffer.concat([ivBuffer, encryptedBuffer]);
return combinedBuffer.toString('base64');
} catch (err) {
console.error('Encrypt Error:', err.message);
return null;
}
}
// --- AES 解密 ---
decrypt(base64Str) {
try {
const combinedBuffer = Buffer.from(base64Str, 'base64');
const iv = combinedBuffer.slice(0, 16);
const content = combinedBuffer.slice(16);
const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
let decrypted = decipher.update(content, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
} catch (err) {
console.error('Decrypt Error:', err.message);
return null;
}
}
// --- 核心:数据清洗 Transformer ---
// 将扁平的 List<KV> 转换为层级化的 JSON 对象
structureRiskData(flatList) {
const result = {
score: {}, // 41xxx 评分
overdue: {}, // 17xxx 逾期
fraud: {}, // 2xxxx/3xxxx 欺诈
application: {} // 40xxx 申请
};
flatList.forEach(item => {
const code = String(item.riskCode);
const value = item.riskCodeValue;
if (code.startsWith('41')) {
result.score[code] = value;
} else if (code.startsWith('17')) {
result.overdue[code] = value;
} else if (code.startsWith('2') || code.startsWith('3')) {
result.fraud[code] = value;
} else if (code.startsWith('40')) {
result.application[code] = value;
}
});
return result;
}
// --- 业务调用主流程 ---
async queryRiskProfile(userParams) {
// 1. 补充必填的授权字段
const payload = { ...userParams, authorized: "1" };
const encryptedData = this.encrypt(payload);
if (!encryptedData) return;
try {
const url = `${CONFIG.apiUrl}?t=${Date.now()}`;
const res = await axios.post(url, { data: encryptedData }, {
headers: {
'Content-Type': 'application/json',
'Access-Id': CONFIG.accessId
}
});
if (res.data.code === '200' || res.data.code === 200) {
console.log('API调用成功,开始清洗数据...');
// 解密数据(假设返回的是加密串,部分情况可能直接返回数组,视实际响应而定)
// 此处按标准加密流程处理
let rawList = [];
if (typeof res.data.data === 'string') {
rawList = this.decrypt(res.data.data);
} else {
rawList = res.data.data;
}
// 2. 执行数据重组
const structuredData = this.structureRiskData(rawList);
return structuredData;
} else {
console.error(`API业务错误: ${res.data.message} (Code: ${res.data.code})`);
return null;
}
} catch (error) {
console.error('网络请求失败:', error.message);
}
}
}
// === 使用示例 ===
(async () => {
const service = new ComprehensiveRiskService();
const profile = await service.queryRiskProfile({
name: "李四",
id_card: "110101199001011234",
mobile_no: "13800138000"
});
if (profile) {
console.log("=== 结构化风控报告 ===");
// 直接访问重组后的对象,逻辑更清晰
const overdueCount = profile.overdue['17001'] || 0; // 1周内逾期数
const fraudLevel = profile.fraud['22006'] || 1; // 圈团风险等级
if (Number(overdueCount) > 0) {
console.warn(`[拦截] 检测到当前存在 ${overdueCount} 个逾期平台!`);
} else if (Number(fraudLevel) === 3) {
console.warn(`[拦截] 检测到高风险欺诈团伙关联!`);
} else {
console.log(`[通过] 信用评分: ${profile.score['41001']}`);
}
}
})();
三、核心数据结构解析
本接口的数据量极大,包含五大类指标。Node.js 开发者需要理解原始数据与业务数据的映射关系。
1. 原始数据 (Flat List)
接口返回的是为了传输优化的数组:
JSON
[ { "riskCode": 17001, "riskCodeValue": 2 }, { "riskCode": 41001, "riskCodeValue": 85 }, { "riskCode": 22006, "riskCodeValue": 3 }]
2. 清洗后数据 (Nested Object)
经过 structureRiskData 函数处理后,数据变得语义化,适合前端组件绑定:
JavaScript
{
"overdue": {
"17001": 2 // 1周内逾期平台数
},
"score": {
"41001": 85 // 多头通用分
},
"fraud": {
"22006": 3 // 团伙风险等级
}
}
四、字段详解(Node.js 开发速查)
以下表格精选了综合版接口中新增且最具价值的字段,Node.js 开发者应重点关注这些字段的透传。
- 逾期风险 (Overdue) - 3
这是本接口的核心增量数据,直接反映用户的违约历史。
| Code | 字段含义 | 说明 | Node.js 逻辑建议 |
|---|---|---|---|
| 17001 | 1周内逾期平台数 | 当前正在逾期 | 若 > 0,BFF 层直接返回 status: REJECT。 |
| 17003 | 3个月内逾期平台数 | 近期违约迹象 | 结合申请次数展示趋势图。 |
| 17006 | 1年以前逾期平台数 | 历史黑名单 | 用于标记“有过违约记录的老户”。 |
- 欺诈风险 (Fraud) - 4
无需对接复杂的图数据库,直接获取关联网络分析结果。
| Code | 字段含义 | 值域 | Node.js 逻辑建议 |
|---|---|---|---|
| 22006 | 圈团风险等级 | 1(低), 2(中), 3(高) | 3 = 高危,建议标记为红色预警。 |
| 31006 | 疑似准入风险 | 1(低), 2(中), 3(高) | 基于资料虚假的判定,建议拦截高风险。 |
| 21007 | 圈团浓度分 | 0-100 | 分数越高,周围关联的黑产越多。 |
五、应用价值分析
在 Node.js 驱动的架构中,集成天远综合多头API主要服务于以下场景:
-
Dashboard 数据聚合层:
前端(React/Vue)通常需要展示“信用分”、“近7天申请趋势”、“是否存在逾期”等模块。Node.js BFF 层调用一次本接口,通过 structureRiskData 将数据拆解分发给前端的各个组件,避免了前端进行复杂的数组遍历。
-
API 网关的“守门员”:
在请求到达核心 Java/Go 服务之前,Node.js 网关可以先调用此接口进行“初筛”。
- 快速失败 (Fail-Fast) :如果
17001(当前逾期) > 0,网关直接拒绝请求,无需浪费后端昂贵的算力资源。 - 流量分发:根据
41005(银行分) 的高低,将用户引导至不同的贷款产品页面。
- 快速失败 (Fail-Fast) :如果
-
实时反欺诈 (Real-time Anti-Fraud):
利用 22006 (圈团风险) 指标。如果用户被识别为高风险团伙成员(CodeValue=3),Node.js 服务可以立即触发设备指纹记录或短信验证码拦截,防止大规模机器攻击。
六、总结
天远综合多头 API 提供了目前信贷风控中最全的数据维度。对于 Node.js 开发者,核心任务不仅仅是 AES 解密,更重要的是数据治理。
通过本文提供的 Transformer 模式,您可以将杂乱的 KV 列表转化为结构清晰的业务对象,不仅提升了代码的可维护性,也为前端提供了更加友好的数据接口。建议在 BFF 层对 17xxx (逾期) 和 2xxxx (欺诈) 类指标做强校验,守好风控的第一道防线。