前端全栈进阶:使用 Node.js 处理天远多数据源聚合接口与 Buffer 加密流

14 阅读6分钟

一、在 BFF 层重塑小微风控体验

在开发面向银行客户经理或信审员的“小微企业进件系统”时,前端页面通常需要展示一个综合看板:左边是企业经营状况,中间是法人借贷评分,右边是司法诉讼风险

如果前端直接调用底层数据接口,将面临两个问题:

  1. 数据过载天远API 的“全能小微企业报告”一次性返回数千个字段,直接透传会消耗大量带宽。
  2. 逻辑泄露:加密密钥和敏感的风控规则(如“命中黑名单即拒单”)不应暴露在浏览器端。

通过 Node.js 构建 BFF 层,我们可以充当“数据适配器”的角色:向上对接天远API(COMBQN13),向下为前端提供精简、脱敏后的 JSON 数据。本文将演示如何利用 Node.js 的 crypto 模块处理 AES 加密,并编写高效的 Transformer(转换器)将异构的子产品数据整合成统一的视图。

二、API接口调用示例(Node.js版)

1. 接口技术规范

  • 接口地址https://api.tianyuanapi.com/api/v1/COMBQN13
  • HTTP 方法:POST
  • 鉴权:Header (Access-Id) + Body (data 密文)
  • 聚合特性:响应体是一个 responses 数组,每个元素包含 api_code(子产品码)和 data

2. Node.js 完整实现代码

本示例包含 AES 加解密工具,以及一个核心的 DashboardPresenter 类,用于将复杂的聚合数据转换为前端看板所需的结构。

JavaScript

const axios = require('axios');
const crypto = require('crypto');

// 配置信息
const CONFIG = {
    apiUrl: '<https://api.tianyuanapi.com/api/v1/COMBQN13>',
    accessId: 'YOUR_ACCESS_ID',
    accessKey: 'YOUR_ACCESS_KEY_HEX' // 16字节 Hex 字符串
};

class SmeReportBFF {
    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 + 密文 -> 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;
        }
    }

    // --- 核心:数据清洗 Transformer ---
    // 将异构的 responses 数组转换为前端 Dashboard 需要的扁平对象
    transformToDashboard(apiResponse) {
        const dashboard = {
            company: { name: '未查得', status: '未知', risk: 'low' },
            person: { score: 0, overdueAmount: 0, loanCount: 0 },
            legal: { lawsuitCount: 0, totalAmount: 0 },
            blacklist: { isHit: false, details: [] }
        };

        const responses = apiResponse.responses || [];

        responses.forEach(item => {
            if (!item.success || !item.data) return;
            const data = item.data;

            switch (item.api_code) {
                case 'QYGL3F8E': // 人企关系 -> 提取企业基本面
                    if (data.items && data.items.length > 0) {
                        const info = data.items[0].basicInfo || {};
                        dashboard.company.name = info.name;
                        dashboard.company.status = info.regStatus;
                        // 简单规则:注销或吊销视为高风险
                        if (['注销', '吊销'].includes(info.regStatus)) {
                            dashboard.company.risk = 'high';
                        }
                    }
                    break;

                case 'JRZQ7F1A': // 全景雷达 -> 提取法人借贷画像
                    const behavior = data.behavior_report_detail || {};
                    dashboard.person.score = parseInt(behavior.B22170001 || 0); // 借贷行为分
                    dashboard.person.overdueAmount = behavior.B22170031 || "0"; // 近6月逾期金额
                    dashboard.person.loanCount = parseInt(behavior.B22170005 || 0); // 近12月贷款笔数
                    break;

                case 'JRZQ8A2D': // 特殊名单 -> 黑名单检测
                    if (data.id && data.id.court_bad === '0') { // 0表示命中
                        dashboard.blacklist.isHit = true;
                        dashboard.blacklist.details.push('法院失信人');
                    }
                    if (data.cell && data.cell.nbank_lost === '0') {
                        dashboard.blacklist.isHit = true;
                        dashboard.blacklist.details.push('网贷高风险');
                    }
                    break;

                case 'FLXG7E8F': // 司法涉诉 -> 涉诉统计
                    const stats = data.judicial_data?.lawsuitStat?.count || {};
                    dashboard.legal.lawsuitCount = stats.count_total || 0;
                    dashboard.legal.totalAmount = stats.money_total || 0;
                    break;
            }
        });

        return dashboard;
    }

    // --- 业务调用流程 ---
    async getSmeRiskProfile(params) {
        const payload = {
            ...params,
            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 
                }
            });

            // 假设外层未加密,直接处理 responses
            if (res.data && res.data.responses) {
                console.log('API 调用成功,正在生成 Dashboard 数据...');
                return this.transformToDashboard(res.data);
            } else {
                console.error('API 响应异常:', res.data);
                return null;
            }
        } catch (error) {
            console.error('网络请求失败:', error.message);
            return null;
        }
    }
}

// === 使用示例 ===
(async () => {
    const bff = new SmeReportBFF();
    
    // 模拟前端发来的请求
    const dashboardData = await bff.getSmeRiskProfile({
        name: "张三",
        id_card: "110101199001011234",
        mobile_no: "13800138000"
    });

    if (dashboardData) {
        console.log("=== 前端看板数据 (JSON) ===");
        console.log(JSON.stringify(dashboardData, null, 2));
        
        // 简单的逻辑判断
        if (dashboardData.blacklist.isHit) {
            console.warn(">> 触发前端弹窗:该客户命中黑名单,禁止进件!");
        }
    }
})();

三、核心数据结构解析

Node.js 处理此接口的优势在于能够轻松操作 JSON 对象。我们需要关注如何将数组形式的响应映射为语义化的对象。

1. 原始聚合响应

JavaScript

// 这是一个数组,查找特定数据需要遍历
responses: [
  { api_code: "QYGL3F8E", data: { ... } }, 
  { api_code: "JRZQ7F1A", data: { ... } }
]

2. 清洗后的 Dashboard 数据

经过 transformToDashboard 函数处理后,数据结构变得极其精简,完全适配前端 UI 组件的 props:

JavaScript

{
  "company": {
    "name": "某某科技公司",
    "status": "存续",
    "risk": "low"
  },
  "person": {
    "score": 650,          // 借贷分
    "overdueAmount": "[5000,10000)" // 逾期金额
  },
  "blacklist": {
    "isHit": true,         // 是否命中黑名单
    "details": ["法院失信人"]
  }
}

四、字段详解(BFF层 透传策略)

以下字段是在 BFF 层进行数据清洗时,最常被提取并透传给前端的关键指标。

1. 身份与经营核验 (Identity & Biz)

原始字段 (Path)目标字段 (UI)处理逻辑
QYGL3F8E...basicInfo.namecompanyName展示用。
QYGL3F8E...basicInfo.regStatusbizStatus用于前端标签颜色(存续=绿,注销=红)。
QYGL3F8E...basicInfo.estiblishTimebizAge计算得出“经营年限”(如 3.5年)。

2. 借贷与偿债能力 (Credit & Ability)

原始字段 (Code)目标字段 (UI)说明
B22170001creditScore贷款行为分 (1-1000)。可以直接用于绘制仪表盘图表。
B22170031recentOverdue近6个月逾期金额。若非 "0",前端高亮显示。
A22160003multiHeadCount申请命中机构数。用于展示“多头借贷”程度。

3. 风险阻断 (Blocking Risk)

原始字段 (Key)目标字段 (UI)逻辑
id_court_badisDishonest(JRZQ8A2D) 法院失信。红线指标
count_wei_totallawsuitPending(FLXG7E8F) 未结案数量。数量过多提示风险。

五、应用价值分析

在 Node.js 中间件层集成天远全能小微企业报告,主要解决以下业务痛点:

  1. 数据脱敏与安全 (Security):

    原始接口返回了企业主详细的借贷记录(如具体金额区间)。BFF 层可以将其转换为模糊的等级(如“高/中/低”),避免敏感数据直接暴露在浏览器网络请求中。

  2. 前端性能优化 (Performance):

    原始响应体可能高达 100KB+。经过 Transformer 清洗后,传输给前端的 JSON 通常小于 2KB,显著提升了移动端 H5 进件页面的加载速度。

  3. 统一错误处理 (Error Handling):

    聚合接口中的某个子产品可能会失败(success: false)。Node.js 可以优雅地处理这种情况,例如:如果“司法涉诉”查询超时,BFF 层可以返回默认的“查询中”状态,而不影响“黑名单”和“企业信息”的正常展示,保证用户体验的降级而非崩溃。

六、总结

天远全能小微企业报告 API 是 SME(小微企业) 风控场景下的数据核武器。

对于 Node.js 开发者,核心任务是构建一个健壮的 Aggregation & Transformation 层。通过本文示例的 DashboardPresenter 模式,您可以轻松地将复杂的后台数据“翻译”为前端易读的业务语言,打造流畅、安全的小微企业信贷申请体验。