Java微服务实战:聚合型“全能小微企业报告”接口的调用与数据清洗

一、用 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

jsx 复制代码
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

jsx 复制代码
{
  "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 示例,您可以快速打通从数据获取、清洗到规则判定的全流程,为企业构建一个高效、智能的小微风控大脑。

相关推荐
Rain的Java大神之路1 分钟前
Java 工程师做 AI,核心机会在「落地」而非「训练」
java·后端·ai编程
坊钰2 分钟前
【LangGraph框架入门级】4. 支持联网搜索的智能代理系统
python·ai编程·langgraph
流烟默3 分钟前
ClickHouse Part 机制与写入优化实践指南
大数据·运维·clickhouse
HZZD_HZZD7 分钟前
写字楼空调分项怎么算?分相计量与峰值控制
大数据·物联网·腾讯云
实验室管理云平台13 分钟前
广东生物检验机构 LIMS 平台哪家做得好呢?
大数据·科技
运维@小兵16 分钟前
安装Pycharm并破解
ide·python·pycharm
冬、不眠的夜22 分钟前
Nuitka 将 python 程序编译成 exe 指南
开发语言·python
yydbjx34 分钟前
嘉兴海宁口腔市场数据三类机构的定位对比
大数据
范中勤37 分钟前
Python Asyncio 协程与 LLM 异步并发原理技术文档(与 Java 对比|彻底澄清所有误区)
python·llm·协程·并发编程·asyncio
薛之谦_38 分钟前
JAVA 多线程编程:从基础原理到实战应用
java·开发语言