1. 构建金融系统的“数字护城河”
在金融科技领域,Java 依然是构建核心交易与风控系统的首选语言。无论是传统银行信贷、供应链金融平台,还是大型消费分期系统,开发者都需要在保证高并发稳定性的同时,引入精准的外部数据源来辅助决策。
天远风控决策接口 (JRZQ3P01) 1正是这样一款能为 Java 开发者提供强力支持的工具。它不仅能返回标准化的审核建议(如“建议拒绝”或“关注审核”),还能通过多维度的风险标签(如严重逾期、疑似欺诈、多头借贷机构数),帮助企业构建自动化的贷前审批流。本文将从 Java 工程师的视角,详细拆解如何处理该接口严格的 AES 加密签名机制,并实现强类型的数据解析。
2. API 调用示例:严谨的加解密与 HTTP 请求
对接天远API的核心挑战在于其安全机制:请求体需要进行 AES-128-CBC 加密,且需处理 IV(初始化向量)的拼接与 Base64 编码 。
2.1 技术选型与配置
- JDK 版本: 1.8+
- 加密算法:
AES/CBC/PKCS5Padding(注:Java 标准库中 PKCS5Padding 实际上兼容 PKCS7Padding) - 依赖库: 本示例使用原生
javax.crypto库处理加密,使用OkHttp处理网络请求,Jackson处理 JSON。
2.2 完整工具类实现
Java
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 天远风控接口 (JRZQ3P01) 对接工具类
* 包含 AES-128 加解密与 HTTP 请求封装
*/
public class TianyuanRiskClient {
private static final String API_URL = "<https://api.tianyuanapi.com/api/v1/JRZQ3P01>";
private final String accessId;
private final byte[] accessKey; // 128位密钥
private final OkHttpClient httpClient;
private final ObjectMapper objectMapper;
public TianyuanRiskClient(String accessId, String hexKey) {
this.accessId = accessId;
// 将16进制字符串转换为字节数组
this.accessKey = hexToBytes(hexKey);
this.httpClient = new OkHttpClient();
this.objectMapper = new ObjectMapper();
}
/**
* 核心加密逻辑:AES-CBC + IV拼接 + Base64
*/
public String encrypt(String plainText) throws Exception {
// 1. 生成随机 IV (16字节) byte[] iv = new byte[16];
new java.security.SecureRandom().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec keySpec = new SecretKeySpec(accessKey, "AES");
// 2. 初始化加密器 (PKCS5Padding 兼容 PKCS7)
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// 3. 拼接 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);
// 4. Base64 编码 return Base64.getEncoder().encodeToString(combined);
}
/**
* 核心解密逻辑
*/
public String decrypt(String base64Data) throws Exception {
byte[] decoded = Base64.getDecoder().decode(base64Data);
// 1. 提取前16字节 IV byte[] iv = new byte[16];
System.arraycopy(decoded, 0, iv, 0, 16);
// 2. 提取剩余密文 byte[] ciphertext = new byte[decoded.length - 16];
System.arraycopy(decoded, 16, ciphertext, 0, ciphertext.length);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec keySpec = new SecretKeySpec(accessKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
}
/**
* 发起风控查询
*/
public void queryRisk(String name, String idCard) {
try {
// 构造业务参数
Map<String, String> params = new HashMap<>();
params.put("name", name);
params.put("id_card", idCard); // [cite: 2]
// 加密数据
String encryptedData = encrypt(objectMapper.writeValueAsString(params));
// 构造请求体 {"data": "base64..."}
Map<String, String> bodyMap = new HashMap<>();
bodyMap.put("data", encryptedData);
RequestBody body = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"),
objectMapper.writeValueAsString(bodyMap)
);
// 构造请求,包含 Access-Id 和 时间戳
Request request = new Request.Builder()
.url(API_URL + "?t=" + System.currentTimeMillis())
.addHeader("Access-Id", this.accessId)
.post(body)
.build();
// 执行请求
try (Response response = httpClient.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
Map resMap = objectMapper.readValue(response.body().string(), Map.class);
// 检查业务Code
if ((int)resMap.get("code") == 0) {
// 成功,解密 Data 字段
String riskJson = decrypt((String) resMap.get("data"));
System.out.println("风控报告解密结果: " + riskJson);
} else {
System.err.println("业务异常: " + resMap.get("message"));
}
}
}
} catch (Exception e) {
e.printStackTrace(); // 实际项目中建议使用 Slf4j 记录日志
}
}
// Hex转Byte辅助方法
private byte[] hexToBytes(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
// 测试入口
public static void main(String[] args) {
// 请替换为您的真实 Key 和 ID
TianyuanRiskClient client = new TianyuanRiskClient("ACCESS_ID", "ACCESS_KEY_HEX");
client.queryRisk("张三", "110101199001011234");
}
}
3. 核心数据结构解析
对于 Java 开发者而言,将 JSON 数据映射为 POJO (Plain Old Java Object) 是最佳实践。以下是天远API 返回的核心数据模型详解。
3.1 响应字段映射表
| 字段名 | 类型 | 核心含义 | Java 处理建议 |
|---|---|---|---|
reviewSuggestions | String | 审核建议 (A-F) | 建议定义 Enum 枚举类:REJECT(A/B), REVIEW(C/D), PASS(E) |
blackOrgNum | String | 黑名单机构数 | 这是一个关键的风控指标。返回值如 >3,解析时需提取数字。 |
overdueAmt | String | 当前逾期金额 | 返回值如 >8000。金额越大,还款能力存疑风险越高。 |
lastCondition | Object | 最新逾期/欺诈状态 | 包含具体的风险标签,如严重逾期、疑似欺诈等。 |
loanTypes | Object | 贷款类型画像 | 用于区分用户是习惯用信用卡(isBank)还是网贷(isNetloan)。 |
3.2 深度解析:风险状态对象 (lastCondition)
此对象是判断用户具体风险点的依据。字段值为 "1" 代表命中,"0" 代表未命中 10。
| JSON 字段 | 对应风险 | 业务解读 |
|---|---|---|
seriousOverdue | 严重逾期(>90天) | 红线指标。通常意味着坏账,建议直接拒单。 |
generalOverdue | 一般逾期(1-90天) | 需结合逾期金额判断。若是小额短期,可能是遗忘;若大额,则风险较高。 |
suspectFraud | 疑似欺诈 | 高危预警。建议结合人脸识别或运营商数据进行二次核验。 |
fraud | 确认为欺诈 | 黑名单指标。直接拒绝,并加入内部黑名单。 |
4. 应用价值分析:如何在业务代码中落地?
在 Spring Boot 架构中,建议将天远风控 API 封装为一个独立的 RiskDecisionService。
4.1 场景一:全自动审批流(Auto-Decision)
利用 reviewSuggestions 字段实现“秒批秒贷”:
- Case E (正常用户) : 系统自动放行,进入额度计算环节。
- Case A/B (逾期/拒绝) : 系统自动触发拒信发送,无需人工干预,极大降低运营成本。
4.2 场景二:差异化定价策略
利用 loanTypes 和 overdueAmt 进行精细化运营:
- 如果用户
isBank=1(有银行借贷记录) 且slightlOverdue=1(仅有轻微逾期),说明用户资质尚可,但近期资金周转紧张。 - 策略: 可以通过审批,但适当上浮利率或降低授信额度,平衡风险与收益。
4.3 场景三:存量客户预警
针对存量贷款用户,定期(如每月)调用接口扫描:
- 若发现用户突然出现
blackOrgNum > 5(多头借贷激增) ,这通常是资金链断裂的前兆。 - 策略: 系统自动冻结其循环贷额度,防止风险扩大。
总结
通过 Java 语言的强类型约束和严谨的异常处理机制,我们可以将 天远风控决策API 稳定地集成到核心金融系统中。从 AES-128 加密的实现,到 JSON 数据的业务对象映射,每一步都体现了金融级开发的规范性。合理利用 reviewSuggestions 和 lastCondition 等核心字段,将帮助企业构建起一道坚实的数字化风控防线。
开发建议:
- 容错处理: 务必处理
code: 1007(余额不足) 状态码,建议接入监控报警,避免因欠费导致业务中断 13。 - 成本优化: 接口按次计费 (¥2.5/次),建议在 Redis 中对同一身份证的查询结果设置合理的缓存期(如 24 小时)。