一、用 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/COMBQN132 - 请求方式: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 后端系统可实现以下核心能力:
-
公私联动画像构建:
在一次 API 事务中,同时获取"企业的壳"和"法人的核"。例如,如果企业经营正常(QYGL3F8E),但法人近期有大量网贷逾期(JRZQ7F1A),系统可自动判定为"经营性资金挪用风险",拒绝放款。
-
供应链金融自动准入:
对于经销商融资场景,Java 服务可以配置规则链:
-
Step 1: 校验企业是否存续(QYGL3F8E)。
-
Step 2: 校验法人是否为老赖(JRZQ8A2D)。
-
Step 3: 校验涉诉金额是否超过注册资本的 50%(FLXG7E8F)。
全部通过后,自动触发授信流程。
-
-
贷后风险预警:
利用 Java 的 Quartz 或 Spring Scheduler 定时任务,定期调用此接口。若发现存量客户的 openLawsuitCount(未结案数)突增,系统自动生成风控工单,提示客户经理进行贷后回访。
六、总结
对于 Java 开发者而言,天远全能小微企业报告 是一个典型的"胖接口"。对接的关键在于编写健壮的 解析器(Parser) ,能够兼容子产品的成功与失败状态,并将异构数据清洗为统一的领域模型。
通过本文提供的 SmeRiskService 示例,您可以快速打通从数据获取、清洗到规则判定的全流程,为企业构建一个高效、智能的小微风控大脑。