Java后端集成天远API:多头借贷风险查询接口的AES加解密实战

27 阅读6分钟

一、构建基于天远API的金融风控中台

在构建企业级信贷审批系统或消费金融风控中台时,数据源的稳定性与集成效率至关重要。天远数据提供的“全国自然人多头借贷风险信息查询”API,通过整合银行、消金、小贷及互金平台的多维度数据,为后端决策引擎提供了关键的风险特征变量。

对于Java开发者而言,对接此类金融API的核心挑战在于处理高安全级别的加密传输协议以及解析复杂的区间化指标。本文将提供一套完整的Java原生接入方案,涵盖AES-128-CBC加解密工具类封装、HTTP请求构建以及核心风险字段的POJO映射解析,帮助开发者将天远API的风控能力无缝集成至Spring Boot或其他Java后端框架中。

二、API接口调用示例(Java版)

本接口要求严格的安全机制:请求体需进行AES加密并Base64编码,且必须携带随机生成的IV(初始化向量)。以下是基于Java标准库(javax.crypto)实现的完整对接流程。

1. 接口基础信息

  • 服务地址https://api.tianyuanapi.com/api/v1/JRZQ9E2A
  • 请求方式:POST
  • 鉴权方式:Header中传入 Access-Id,Body中传入加密后的 data 4。

2. Curl 调用示意

在调试Java代码前,可先确认环境连通性:

Bash

curl -X POST "<https://api.tianyuanapi.com/api/v1/JRZQ9E2A?t=1716345678000>" \
  -H "Content-Type: application/json" \
  -H "Access-Id: YOUR_ACCESS_ID" \
  -d '{"data": "base64_encoded_encrypted_string..."}'

3. Java 完整接入代码

本示例包含一个通用的加密工具类 AesUtils 和调用主类 TianYuanClient。为了保持通用性,HTTP请求部分使用了标准 HttpURLConnection,实际生产中建议替换为 RestTemplateOkHttp

Java

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper; // 需引入Jackson或Fastjson

/**
 * 天远API 加解密及调用工具类
 */
public class TianYuanClient {

    private static final String API_URL = "<https://api.tianyuanapi.com/api/v1/JRZQ9E2A>";
    private static final String ACCESS_ID = "YOUR_ACCESS_ID"; // 替换您的AccessId
    private static final String ACCESS_KEY = "YOUR_ACCESS_KEY"; // 替换您的Key(16进制)

    public static void main(String[] args) {
        try {
            // 1. 准备请求业务参数
            Map<String, String> params = new HashMap<>();
            params.put("name", "李四");
            params.put("id_card", "110101199001011234");
            params.put("mobile_no", "13900000000");
            params.put("auth_authorize_file_code", "20251210-AUTH-002");

            // 2. 数据加密
            String encryptedData = AesUtils.encrypt(new ObjectMapper().writeValueAsString(params), ACCESS_KEY);

            // 3. 发送HTTP POST请求
            String responseJson = sendPostRequest(encryptedData);
            System.out.println("原始响应: " + responseJson);

            // 4. 解析响应并解密
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> respMap = mapper.readValue(responseJson, Map.class);
            
            if ((int)respMap.get("code") == 0) {
                String encryptedRespData = (String) respMap.get("data");
                // 解密业务数据
                String riskInfoJson = AesUtils.decrypt(encryptedRespData, ACCESS_KEY);
                System.out.println("=== 解密后的多头借贷风险数据 ===");
                System.out.println(riskInfoJson);
            } else {
                System.err.println("API调用失败: " + respMap.get("message"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送POST请求 (原生JDK实现)
     */
    private static String sendPostRequest(String encryptedData) throws Exception {
        long timestamp = System.currentTimeMillis();
        URL url = new URL(API_URL + "?t=" + timestamp);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setRequestProperty("Access-Id", ACCESS_ID);
        conn.setDoOutput(true);

        // 构造JSON Body
        String jsonBody = "{\"data\": \"" + encryptedData + "\"}";

        try (OutputStream os = conn.getOutputStream()) {
            byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }

        try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder response = new StringBuilder();
            String responseLine;
            while ((String responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
            return response.toString();
        }
    }

    /**
     * AES-128-CBC 加解密工具内部类
     */
    static class AesUtils {
        private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; // Java中PKCS5Padding等同于PKCS7

        public static String encrypt(String content, String key) throws Exception {
            byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); // 实际应截取前16位或按文档要求处理
            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
            
            // 生成随机IV
            byte[] iv = new byte[16];
            new SecureRandom().nextBytes(iv);
            IvParameterSpec ivSpec = new IvParameterSpec(iv);

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
            byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));

            // 拼接 IV + 密文
            byte[] combined = new byte[iv.length + encrypted.length];
            System.arraycopy(iv, 0, combined, 0, iv.length);
            System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length);

            return Base64.getEncoder().encodeToString(combined);
        }

        public static String decrypt(String content, String key) throws Exception {
            byte[] decoded = Base64.getDecoder().decode(content);
            
            // 提取IV (前16字节)
            byte[] iv = new byte[16];
            System.arraycopy(decoded, 0, iv, 0, 16);
            
            // 提取密文
            byte[] ciphertext = new byte[decoded.length - 16];
            System.arraycopy(decoded, 16, ciphertext, 0, ciphertext.length);

            byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(iv);

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
            
            return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
        }
    }
}

三、核心数据结构解析

Java是强类型语言,理解数据结构对于定义DTO(数据传输对象)至关重要。天远API 的响应解密后是一个扁平化的JSON对象,包含数百个Key-Value对。

数据层级说明

  1. Transport Layer (传输层) :包含 code (int), message (String), data (String - 加密串) 。

  2. Business Layer (业务层) :解密后的JSON,主要包含以下数据类型:

    • Flag Indicators: 如 xyp_cpl0044 (是否逾期),通常为 "0" 或 "1" 的字符串。
    • Interval Mappings: 如 xyp_cpl0001 (贷款机构数),返回值为 "1", "2" 等代码,需参照文档映射表转换为具体区间 。
    • Scores/Ratios: 如 xyp_cpl0081 (信用评分),为浮点数字符串。

四、字段详解(Java开发重点关注)

下表列出了在Java业务逻辑中常用于生成“拒绝规则”或“人工审核策略”的核心字段。请注意,API大部分整型统计数据(如金额、次数)采用了区间映射机制。

字段代码 (Key)业务含义数据类型/说明适用场景
xyp_cpl0081信用风险评分Double (0-1)分数越高信用越低,可用于自动拒绝线设定
xyp_cpl0044当前逾期状态Integer (0/1)1表示存在未结清逾期,属于风控红线指标
xyp_cpl0009近7天贷款机构数Map Code (区间)反映短期多头借贷强度,需映射解析
xyp_cpl0032近1天交易失败金额Map Code (区间)映射如 "1"->(0,1500),监测异常交易行为
xyp_model_score_high小额网贷分V1Integer (350-950)越高越好,未命中返回-1,用于信用分层
xyp_cpl0072当前逾期金额Map Code (区间)评估违约严重程度,如 "2"代表[1000,2000)元

注:开发者建议编写一个 EnumsMap 工具类,专门用于将上述 Map Code 翻译为业务可读的区间字符串。

五、应用价值分析

天远API集成到Java微服务架构中,主要服务于以下核心业务流:

  1. 信贷审批自动化:

    在Java编写的工作流引擎(如Activiti)中,将API调用作为“风险数据获取”节点。通过解析 xyp_cpl0081 和 xyp_model_score_high,系统可以自动计算综合评分,实现秒级批核。

  2. 反欺诈规则引擎:

    利用 xyp_cpl0070 (近1天贷款机构数) 和 xyp_cpl0016 (近1天交易失败笔数) 设定阈值。如果用户短时间内在多个平台申请失败,Java应用可直接触发“高危拦截”策略。

  3. 用户画像补全:

    将API返回的 xyp_cpl0045 (信用贷款时长)和历史还款表现存入数据仓库,丰富用户的金融画像,为后续的存量客户营销或提额提供数据支撑。

六、总结

对于追求高并发与稳定性的后端系统,天远API 提供了极具价值的标准化风控数据。通过本文提供的Java AES解密工具与调用示例,开发者可以快速打通数据链路。

建议在实际开发中:

  • 异常处理:做好 1002 (解密失败) 和 1007 (余额不足) 的全局异常捕获。
  • 数据脱敏:由于涉及 id_cardmobile_no 等敏感信息,务必在日志打印时进行掩码处理。
  • 缓存策略:对于同一用户的查询结果,建议在有效期内进行缓存,以节省调用成本。