Java后端进阶:处理多数据源聚合API —— 以天远小微企业报告为例

一、用 Java 构建"公私联动"的信贷审批中台

在商业银行与供应链金融的信贷系统中,小微企业贷(SME Loan)的风控逻辑最为复杂。它要求系统既要像查企业一样核验工商司法信息,又要像查个人一样评估企业主(法人)的偿债能力。

天远API 的"全能小微企业报告"(COMBQN13),通过单一接口聚合了 QYGL3F8E(人企关系)JRZQ7F1A(全景雷达)JRZQ8A2D(特殊名单)FLXG7E8F(司法涉诉) 四大核心产品 111。这种"一包四查"的设计极大降低了网络交互频次,但也给 Java 后端带来了挑战:如何将接口返回的异构 JSON Array 解析为标准的 Java 对象,以便输入到 Drools 或 EasyRules 等规则引擎中?

本文将提供一套完整的 Java 解决方案,涵盖 AES 加密工具类组合响应的 POJO 映射策略 以及 核心风控指标的提取逻辑,助力开发者构建高健壮性的小微风控服务。

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

本接口采用标准的 AES-128-CBC 加密。由于请求参数涉及 authorized(授权书状态),请确保在业务流程中已留存用户的电子签名或授权日志。

1. 接口配置概览

  • 服务地址https://api.tianyuanapi.com/api/v1/COMBQN13 2
  • 请求方式:POST
  • 鉴权 :Header (Access-Id) + Body (data 密文)
  • 数据特点 :响应体包含一个 responses 数组,每个元素对应一个子产品 3。

2. Java 完整接入代码

为了处理聚合响应,本示例定义了一个 SmeRiskService 类。我们使用 Jackson 的 JsonNode 来灵活处理不同子产品的 data 结构,避免定义过于庞大的实体类。

Java

java 复制代码
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;

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;

/**
 * 天远小微企业全能报告 API 服务
 */
public class SmeRiskService {

    private static final String API_URL = "<https://api.tianyuanapi.com/api/v1/COMBQN13>";
    private static final String ACCESS_ID = "YOUR_ACCESS_ID";
    private static final String ACCESS_KEY = "YOUR_ACCESS_KEY_HEX"; // 16字节 Hex

    public static void main(String[] args) {
        try {
            // 1. 发起聚合查询
            SmeRiskSummary summary = querySmeReport("张三", "110101199001011234", "13800138000");

            if (summary != null) {
                System.out.println("=== 小微企业主风险摘要 ===");
                System.out.println("关联企业: " + summary.getCompanyName());
                System.out.println("经营状态: " + summary.getRegStatus());
                System.out.println("个人借贷行为分: " + summary.getLoanBehaviorScore());
                System.out.println("近半年逾期金额: " + summary.getOverdueAmount6M());
                System.out.println("涉诉未结案数: " + summary.getOpenLawsuitCount());
                
                // 简单的拒单规则演示
                if ("注销".equals(summary.getRegStatus()) || summary.getOpenLawsuitCount() > 0) {
                    System.err.println("[REJECT] 命中拒单规则:企业注销或存在未结案涉诉");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查询并清洗数据,返回领域模型对象
     */
    public static SmeRiskSummary querySmeReport(String name, String idCard, String mobile) throws Exception {
        // 1. 准备参数
        Map<String, String> params = new HashMap<>();
        params.put("name", name);
        params.put("id_card", idCard);
        params.put("mobile_no", mobile);
        params.put("authorized", "1"); // 必须获得授权

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

        // 3. 发送请求
        String responseJson = sendPost(encryptedData);

        // 4. 解析响应结构
        ObjectMapper mapper = new ObjectMapper();
        JsonNode rootNode = mapper.readTree(responseJson);
        
        // 假设外层未加密,直接解析 responses 数组
        // 若 data 字段加密,需先解密 data 再解析,逻辑同上
        JsonNode responses = rootNode.get("responses");
        if (responses == null || !responses.isArray()) {
            System.err.println("无效的响应格式");
            return null;
        }

        // 5. 核心:从聚合数据中提取关键指标
        return parseRiskSummary(responses);
    }

    /**
     * 数据清洗器:遍历子产品数组,提取核心字段
     */
    private static SmeRiskSummary parseRiskSummary(JsonNode responses) {
        SmeRiskSummary summary = new SmeRiskSummary();

        for (JsonNode item : responses) {
            String apiCode = item.get("api_code").asText();
            boolean success = item.get("success").asBoolean();
            JsonNode data = item.get("data");

            if (!success || data == null || data.isNull()) continue;

            // 分发处理逻辑
            switch (apiCode) {
                case "QYGL3F8E": // 人企关系
                    if (data.has("items") && data.get("items").isArray() && data.get("items").size() > 0) {
                        JsonNode company = data.get("items").get(0).get("basicInfo");
                        summary.setCompanyName(company.path("name").asText());
                        summary.setRegStatus(company.path("regStatus").asText());
                    }
                    break;
                case "JRZQ7F1A": // 全景雷达
                    summary.setLoanBehaviorScore(data.path("behavior_report_detail").path("B22170001").asText("0"));
                    summary.setOverdueAmount6M(data.path("behavior_report_detail").path("B22170031").asText("0"));
                    break;
                case "JRZQ8A2D": // 特殊名单
                    summary.setCourtBad(data.path("id").path("court_bad").asText("未命中"));
                    break;
                case "FLXG7E8F": // 司法涉诉
                    summary.setOpenLawsuitCount(data.path("judicial_data").path("lawsuitStat")
                            .path("count").path("count_wei_total").asInt(0));
                    break;
            }
        }
        return summary;
    }

    // --- 领域模型 DTO ---
    static class SmeRiskSummary {
        private String companyName = "未查得";
        private String regStatus = "未知";
        private String loanBehaviorScore = "0";
        private String overdueAmount6M = "0";
        private String courtBad = "未命中";
        private int openLawsuitCount = 0;
        
        // Getters & Setters 省略...
        public void setCompanyName(String name) { this.companyName = name; }
        public String getCompanyName() { return companyName; }
        public void setRegStatus(String status) { this.regStatus = status; }
        public String getRegStatus() { return regStatus; }
        public void setLoanBehaviorScore(String score) { this.loanBehaviorScore = score; }
        public String getLoanBehaviorScore() { return loanBehaviorScore; }
        public void setOverdueAmount6M(String amt) { this.overdueAmount6M = amt; }
        public String getOverdueAmount6M() { return overdueAmount6M; }
        public void setCourtBad(String status) { this.courtBad = status; }
        public void setOpenLawsuitCount(int count) { this.openLawsuitCount = count; }
        public int getOpenLawsuitCount() { return openLawsuitCount; }
    }

    // --- HTTP & AES Utils (简化版) ---
    private static String sendPost(String data) throws Exception {
        URL url = new URL(API_URL + "?t=" + System.currentTimeMillis());
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setRequestProperty("Access-Id", ACCESS_ID);
        conn.setDoOutput(true);
        try (OutputStream os = conn.getOutputStream()) {
            os.write(("{\"data\":\"" + data + "\"}").getBytes(StandardCharsets.UTF_8));
        }
        try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) sb.append(line);
            return sb.toString();
        }
    }

    static class AesUtil {
        public static String encrypt(String content, String key) throws Exception {
            byte[] iv = new byte[16];
            new SecureRandom().nextBytes(iv);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"), new IvParameterSpec(iv));
            byte[] enc = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
            byte[] combined = new byte[16 + enc.length];
            System.arraycopy(iv, 0, combined, 0, 16);
            System.arraycopy(enc, 0, combined, 16, enc.length);
            return Base64.getEncoder().encodeToString(combined);
        }
    }
}

三、核心数据结构解析

1. 聚合响应模式

接口返回的 data 并非直接的业务对象,而是一个由子产品响应组成的列表。Java 开发者应使用 策略模式Switch-Case 结构根据 api_code 字段进行分发处理。

JSON

json 复制代码
{
  "responses": [
    { "api_code": "QYGL3F8E", "success": true, "data": { ... } }, // 人企关系
    { "api_code": "JRZQ7F1A", "success": true, "data": { ... } }, // 全景雷达
    { "api_code": "JRZQ8A2D", "success": true, "data": { ... } }  // 特殊名单
  ]
}

2. 字段映射挑战

每个子产品的字段命名风格不同:

  • QYGL3F8E :使用驼峰命名(如 regStatus)4。
  • JRZQ7F1A :使用大写代码(如 B22170001)55。
  • JRZQ8A2D :使用下划线命名(如 id_court_bad)6。

建议在 Java 实体类(如上文的 SmeRiskSummary)中统一重命名为符合业务语义的字段(如 loanScore, companyStatus),屏蔽底层的异构性。

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

以下表格梳理了在 Java 风控系统中,用于**自动准入(Auto-Approve)自动拒单(Auto-Reject)**的关键字段。

1. 企业经营维度 (QYGL3F8E)

字段路径 (JSON Path) 业务含义 逻辑建议
items[0].basicInfo.regStatus 经营状态 若包含"注销"、"吊销",Java 逻辑应直接抛出 RejectException 7。
items[0].basicInfo.estiblishTime 成立时间 计算经营年限,如 < 1年 则归为高风险 8。

2. 企业主还款能力 (JRZQ7F1A)

字段代码 字段含义 说明
B22170001 贷款行为分 1-1000。分数越低,个人信用越差 9999。
B22170031 近6个月累计逾期金额 区间值(如 [5000,10000))。用于计算负债压力 10。
B22170026 近12个月M0+逾期笔数 衡量还款意愿。若 > 3,建议转人工 11。

3. 司法与黑名单 (JRZQ8A2D / FLXG7E8F)

字段路径 字段含义 逻辑建议
id.court_bad 法院失信人 0 表示命中。一票否决指标 12。
lawsuitStat.count.money_wei_total 涉诉未结案金额 企业主的潜在负债,需计入 DTI(债务收入比)计算 13。

五、应用价值分析

集成天远全能小微企业报告后,Java 后端系统可实现以下核心能力:

  1. 公私联动画像构建:

    在一次 API 事务中,同时获取"企业的壳"和"法人的核"。例如,如果企业经营正常(QYGL3F8E),但法人近期有大量网贷逾期(JRZQ7F1A),系统可自动判定为"经营性资金挪用风险",拒绝放款。

  2. 供应链金融自动准入:

    对于经销商融资场景,Java 服务可以配置规则链:

    • Step 1: 校验企业是否存续(QYGL3F8E)。

    • Step 2: 校验法人是否为老赖(JRZQ8A2D)。

    • Step 3: 校验涉诉金额是否超过注册资本的 50%(FLXG7E8F)。

      全部通过后,自动触发授信流程。

  3. 贷后风险预警:

    利用 Java 的 Quartz 或 Spring Scheduler 定时任务,定期调用此接口。若发现存量客户的 openLawsuitCount(未结案数)突增,系统自动生成风控工单,提示客户经理进行贷后回访。

六、总结

对于 Java 开发者而言,天远全能小微企业报告 是一个典型的"胖接口"。对接的关键在于编写健壮的 解析器(Parser) ,能够兼容子产品的成功与失败状态,并将异构数据清洗为统一的领域模型。

通过本文提供的 SmeRiskService 示例,您可以快速打通从数据获取、清洗到规则判定的全流程,为企业构建一个高效、智能的小微风控大脑。

相关推荐
希艾席帝恩4 小时前
数字孪生如何重塑现代制造体系?
大数据·人工智能·数字孪生·数据可视化·数字化转型
武汉海翎光电4 小时前
从数据采集到智能决策:船舶传感器的技术跃迁之路
大数据·人工智能
下海fallsea5 小时前
美团没打赢的仗
大数据
无代码专家5 小时前
无代码:打破技术桎梏,重构企业数字化落地新范式
大数据·人工智能·重构
usrcnusrcn5 小时前
告别PoE管理盲区:有人物联网工业交换机如何以智能供电驱动工业未来
大数据·网络·人工智能·物联网·自动化
一缕猫毛6 小时前
Flink demo代码
java·大数据·flink
Hello.Reader6 小时前
Flink ML 基本概念Table API、Stage、Pipeline 与 Graph
大数据·python·flink
pale_moonlight7 小时前
十一、Flink基础环境实战
大数据·flink
beijingliushao7 小时前
103-Spark之Standalone环境测试
大数据·ajax·spark