一、打破“数据孤岛”,构建聚合型风控中台
在构建企业级信贷审批系统(Loan Origination System, LOS)时,后端工程师常面临对接繁琐的问题:为了获取借款人的完整画像,往往需要分别调用“多头查询”、“逾期黑名单”、“反欺诈名单”等多个上游接口。这不仅增加了网络开销,也让代码逻辑变得支离破碎。
天远API 的“综合多头”接口(JRZQ8F7C),通过单一端点聚合了 多头借贷、多头逾期、圈团欺诈 和 可疑准入 等全维度的风险数据。对于 Java 开发者而言,接入此接口意味着可以将原本复杂的“串行调用链”简化为一次高性能的聚合查询。
本文将提供一套生产级的 Java 接入方案,涵盖 AES-128-CBC 安全通信工具类的封装、authorized 授权参数的处理,以及如何将接口返回的扁平化 KV 列表转换为强类型的业务对象(POJO),助力企业快速落地聚合风控策略。
二、API接口调用示例(Java版)
本接口要求请求体进行 AES 加密,且必须包含 IV(初始化向量)。在 Java 中,我们推荐使用 javax.crypto 标准库来实现,无需引入额外的加密包。
1. 接口配置概览
-
服务地址:
https://api.tianyuanapi.com/api/v1/JRZQ8F7C -
请求方式:POST
-
核心鉴权:
- Header:
Access-Id - Body:
data(加密串)
- Header:
-
特殊参数:
authorized字段必填,需传 "1" 表示已获用户授权。
2. Curl 连通性测试
Bash
curl -X POST "<https://api.tianyuanapi.com/api/v1/JRZQ8F7C?t=1716345678000>" \
-H "Content-Type: application/json" \
-H "Access-Id: YOUR_ACCESS_ID" \
-d '{"data": "Encrypted_Base64_String..."}'
3. Java 完整接入代码
本示例包含通用的 AesUtil 和业务服务类 ComprehensiveRiskService。为了方便处理 riskCode,我们使用了 Jackson 库将响应转换为 Map。
Java
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
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.*;
import java.util.stream.Collectors;
public class ComprehensiveRiskService {
private static final String API_URL = "<https://api.tianyuanapi.com/api/v1/JRZQ8F7C>";
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. 发起聚合查询
Map<String, String> riskReport = queryComprehensiveRisk("王五", "310101199001011234", "13800000000");
if (riskReport != null) {
System.out.println("=== 综合风险画像 ===");
// 打印核心指标
System.out.println("多头通用分(41001): " + riskReport.getOrDefault("41001", "未命中"));
System.out.println("近1周逾期平台数(17001): " + riskReport.getOrDefault("17001", "0"));
System.out.println("圈团风险等级(22006): " + riskReport.getOrDefault("22006", "低风险"));
// 执行简单的阻断逻辑
checkRejectRules(riskReport);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 执行查询并返回清洗后的 Map
*/
public static Map<String, String> queryComprehensiveRisk(String name, String idCard, String mobile) throws Exception {
// 1. 组装参数 (注意 authorized 字段)
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();
Map<String, Object> respMap = mapper.readValue(responseJson, new TypeReference<Map<String, Object>>() {});
if ("200".equals(String.valueOf(respMap.get("code"))) || (int)respMap.get("code") == 200) {
// 根据返回类型处理(可能是加密字符串或直接是List)
Object dataObj = respMap.get("data");
String jsonArrayStr;
if (dataObj instanceof String) {
jsonArrayStr = AesUtil.decrypt((String) dataObj, ACCESS_KEY);
} else {
jsonArrayStr = mapper.writeValueAsString(dataObj);
}
// 5. 数据清洗:List<Map> -> Map<String, String>
List<Map<String, Object>> rawList = mapper.readValue(jsonArrayStr, new TypeReference<List<Map<String, Object>>>() {});
// 将 KV 结构转换为 Map 方便 O(1) 查找
Map<String, String> cleanMap = new HashMap<>();
for (Map<String, Object> item : rawList) {
cleanMap.put(String.valueOf(item.get("riskCode")), String.valueOf(item.get("riskCodeValue")));
}
return cleanMap;
} else {
System.err.println("API业务异常: " + respMap.get("message"));
return null;
}
}
private static void checkRejectRules(Map<String, String> report) {
// 规则1:当前存在信贷逾期 -> 拒单
int overdueCount = Integer.parseInt(report.getOrDefault("17001", "0"));
if (overdueCount > 0) {
System.err.println("[REJECT] 命中规则:当前存在逾期平台 " + overdueCount + " 个");
}
// 规则2:命中团伙欺诈高风险 -> 拒单
String fraudLevel = report.getOrDefault("22006", "0");
if ("3".equals(fraudLevel)) {
System.err.println("[REJECT] 命中规则:高风险团伙欺诈关联");
}
}
// --- 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))) {
return br.lines().collect(Collectors.joining());
}
}
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(), "AES"), new IvParameterSpec(iv));
byte[] encrypt = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
byte[] combined = new byte[iv.length + encrypt.length];
System.arraycopy(iv, 0, combined, 0, 16);
System.arraycopy(encrypt, 0, combined, 16, encrypt.length);
return Base64.getEncoder().encodeToString(combined);
}
public static String decrypt(String content, String key) throws Exception {
byte[] src = Base64.getDecoder().decode(content);
byte[] iv = Arrays.copyOfRange(src, 0, 16);
byte[] body = Arrays.copyOfRange(src, 16, src.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(), "AES"), new IvParameterSpec(iv));
return new String(cipher.doFinal(body), StandardCharsets.UTF_8);
}
}
}
三、核心数据结构解析
本接口的数据量极大,但结构统一。为了在 Java 中高效处理,理解其层级至关重要。
1. 响应结构
解密后的数据是一个 JSON Array,包含不同类型的风险指标对象。
JSON
[
{ "riskCode": 41001, "riskCodeValue": 85 }, // 评分类型
{ "riskCode": 17001, "riskCodeValue": 2 }, // 逾期类型
{ "riskCode": 22006, "riskCodeValue": 3 } // 反欺诈类型
]
2. Java 映射策略
建议在项目中定义一个 RiskCodeEnum 枚举类,将硬编码的数字(如 17001)映射为可读的常量,防止魔术数字(Magic Number)污染业务代码。
Java
public enum RiskCodeEnum {
OVERDUE_1WEEK("17001", "1周内逾期平台数"),
FRAUD_GROUP_LEVEL("22006", "圈团风险等级"),
SCORE_GENERAL("41001", "多头通用分");
// ... constructor & getters
}
四、字段详解(Java开发重点)
以下字段是本接口相对于普通多头查询接口的核心增量价值,也是构建“一票否决”规则的关键。
1. 多头逾期板块 (Overdue) - 独有
这些字段直接反映了用户的还款能力和意愿。
| 字段代码 (Key) | 字段名 | 业务含义 | 阻断策略建议 |
|---|---|---|---|
| 17001 | 1周内逾期平台数 | 当前正在违约 | 强阻断。若 > 0,说明用户当前资金链已断裂。 |
| 17003 | 3个月内逾期平台数 | 短期历史违约 | 结合 40013 (30天申请数) 判断,若既有逾期又有大量申请,拒绝。 |
| 17105 | 12个月内逾期次数 | 长期信用记录 | 用于计算用户的信用稳定性,作为评分卡变量。 |
2. 反欺诈板块 (Fraud) - 独有
无需单独对接反欺诈服务,直接使用以下指标识别团伙攻击 6。
| 字段代码 (Key) | 字段名 | 说明 | 阻断策略建议 |
|---|---|---|---|
| 22006 | 圈团风险等级 | 1=低, 2=中, 3=高 | 若为 3,建议直接进入人工复审或拒绝,防止团伙骗贷。 |
| 31006 | 疑似准入风险 | 1=低, 2=中, 3=高 | 基于资料虚假或行为异常的综合判定,高风险建议拦截。 |
3. 多头申请板块 (Application)
| 字段代码 (Key) | 字段名 | 说明 |
|---|---|---|
| 41005 | 银行多头共债分 | 衡量在银行体系内的借贷压力。 |
| 40161 | 7天新增平台数 | 突发性借贷行为的量化指标。 |
五、应用价值分析
将天远综合多头API集成到 Java 后端系统中,可实现以下核心价值:
-
降低 TCO (总拥有成本):
企业无需分别采购“多头”、“逾期”、“反欺诈”三套数据服务,也无需维护三套接口代码。通过一个 API 即可覆盖贷前审核 80% 的数据需求。
-
构建实时黑名单拦截:
利用 17xxx (逾期) 和 2xxxx (圈团) 数据。在 Java 代码中,可以编写一个 RiskFilter 拦截器,在用户提交申请的 200ms 内,如果发现当前存在逾期或命中团伙库,直接返回拒绝,大幅降低后续流程的计算成本。
-
全生命周期风险监测:
不仅用于贷前。对于存量客户,可以定期(如每月)调用此接口。若发现存量客户的 17001 (当前逾期) 指标突增,说明其在其他平台发生违约,本平台应立即触发“贷中预警”,停止授信或提前催收。
六、总结
天远综合多头 API 是“全栈式”风控数据接口。对于 Java 开发者,其核心挑战在于处理 AES 加密和 KV 数组的解析。
通过本文提供的 ComprehensiveRiskService 示例代码,您可以轻松将这数百个维度的风险数据转化为业务可用的 Map 对象。建议在后续开发中,结合 Java 的规则引擎(如 Drools 或 EasyRules),将 17001、22006 等核心指标配置为动态规则,从而构建出灵敏、高效的信贷风控大脑。