1. 由于速度,所以选择 Go
在互金领域,风控系统的响应速度就是生命线。用户在 APP 上点击"借款"的那一秒,后台可能需要并发调用十几个数据源。这正是 Go 语言大显身手的地方------利用 Goroutine,我们可以轻松实现对天远风控决策接口 (JRZQ3P01) 的高并发调用,在毫秒级内完成对用户信用状况的"全身体检"。
不过,快归快,安全不能丢。这个接口使用了严格的 AES-128-CBC 加密模式 ,而且 Go 的标准库 crypto/aes 比较"原始",不提供自动填充(Padding),这就需要我们自己动手实现 PKCS7 填充逻辑。别担心,接下来我会带你一步步搞定它。
2. API 调用实战:手动实现 PKCS7 与 AES
Go 的设计哲学是"少即是多",标准库只提供最基础的积木。对于天远 API 要求的 PKCS7 填充 和 IV 拼接 ,我们需要自己封装工具函数。
2.1 核心代码实现
这段代码不仅实现了加解密,还演示了如何发起 HTTP 请求。你可以直接把它复制到你的 utils 包里。
Go
go
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// 配置常量
const (
ApiUrl = "<https://api.tianyuanapi.com/api/v1/JRZQ3P01>" //
AccessId = "YOUR_ACCESS_ID"
AccessKeyHex = "YOUR_ACCESS_KEY_HEX" // 16进制字符串
)
// --------------------------
// 1. 加密核心工具 (Go标准库需要手动处理Padding)
// --------------------------
// PKCS7Padding 补码
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// PKCS7UnPadding 去码
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
// Encrypt AES-CBC 加密 -> 拼接IV -> Base64
func Encrypt(plainText string, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// 生成随机IV (16字节)
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", err
}
// 执行 PKCS7 填充
mode := cipher.NewCBCEncrypter(block, iv)
content := PKCS7Padding([]byte(plainText), block.BlockSize())
crypted := make([]byte, len(content))
mode.CryptBlocks(crypted, content)
// 拼接 IV + 密文
combined := append(iv, crypted...)
// Base64 编码
return base64.StdEncoding.EncodeToString(combined), nil
}
// Decrypt Base64解码 -> 分离IV -> AES-CBC 解密
func Decrypt(encryptedBase64 string, key []byte) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return "", err
}
// 提取 IV (前16字节) if len(decoded) < aes.BlockSize {
return "", fmt.Errorf("ciphertext too short")
}
iv := decoded[:aes.BlockSize]
ciphertext := decoded[aes.BlockSize:]
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
mode := cipher.NewCBCDecrypter(block, iv)
// 注意:解密直接在原切片上操作
mode.CryptBlocks(ciphertext, ciphertext)
// 去除 PKCS7 填充
return string(PKCS7UnPadding(ciphertext)), nil
}
// --------------------------
// 2. 业务调用逻辑
// --------------------------
func main() {
// 将Hex密钥转为[]byte
key, _ := hex.DecodeString(AccessKeyHex)
// 构造业务参数
reqData := map[string]string{
"name": "测试用户",
"id_card": "11010119900101xxxx", // [cite: 2]
}
jsonBytes, _ := json.Marshal(reqData)
// 加密
encryptedData, err := Encrypt(string(jsonBytes), key)
if err != nil {
panic(err)
}
// 构造最终请求体 {"data": "..."}
payload := map[string]string{"data": encryptedData}
payloadBytes, _ := json.Marshal(payload)
// 发起 POST 请求
// 注意:URL中必须携带时间戳 t
req, _ := http.NewRequest("POST", fmt.Sprintf("%s?t=%d", ApiUrl, time.Now().UnixMilli()), bytes.NewBuffer(payloadBytes))
req.Header.Set("Access-Id", AccessId) //
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// 读取响应
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
// 处理结果
if code, ok := result["code"].(float64); ok && code == 0 {
// 成功:解密响应中的 data 字段
secretData := result["data"].(string)
realData, _ := Decrypt(secretData, key)
fmt.Printf("风控决策结果: %s\n", realData)
} else {
fmt.Printf("调用失败: %v - %v\n", result["code"], result["message"])
}
}
3. 核心数据结构解析
在 Go 中,我们不喜欢用 map[string]interface{} 这种"万能胶",定义清晰的 struct 才是王道。这能帮我们在编译阶段就发现字段错误。
3.1 响应体 Struct 定义
根据 API 文档,我们可以定义如下结构体,利用 json tag 实现自动映射:
Go
go
type RiskResponse struct {
ReviewSuggestions string `json:"reviewSuggestions"` // 核心建议: A-F
BlackOrgNum string `json:"blackOrgNum"` // 黑名单机构数,如 ">3"
OverdueAmt string `json:"overdueAmt"` // 逾期金额,如 ">8000"
LastCondition LastCondition `json:"lastCondition"` // 欺诈/逾期状态详情
LoanTypes LoanTypes `json:"loanTypes"` // 贷款类型画像
}
type LastCondition struct {
SeriousOverdue string `json:"seriousOverdue"` // 严重逾期 >90天 (1:命中 0:未命中) SuspectFraud string `json:"suspectFraud"` // 疑似欺诈 (1:命中 0:未命中) Fraud string `json:"fraud"` // 欺诈 (1:命中 0:未命中) // ... 其他字段
}
type LoanTypes struct {
IsBank string `json:"isBank"` // 银行/信用卡 IsNetloan string `json:"isNetloan"` // 网贷 // ... 其他字段
}
注意:虽然这些字段逻辑上是布尔值,但文档显示它们返回的是字符串 "1" 或 "0" ,所以 Struct 成员类型定义为 string 是最安全的做法。
4. 应用价值:Go 还能怎么玩?
有了上面的基础,我们在实际业务中可以玩出更多花样。
4.1 高并发批量清洗(Goroutines)
假设你刚拿到一批 Excel 名单(10万条),需要快速筛选出优质客户。 Go 的优势就在这儿:你可以开启一个 Worker Pool(比如 50 个 goroutine),并发调用接口。
- 注意点 :虽然接口本身"不设调用频率限制" ,但你的余额是有限的(¥2.5/次)。记得加上
RateLimiter并在本地记录日志,防止程序跑飞了把钱烧光。
4.2 实时网关转换 (Middleware)
如果你在使用 Gin 或 Echo 框架开发后端,可以将这个 API 封装成一个中间件。 当用户请求借款接口时,中间件先异步请求天远 API:
- 如果
ReviewSuggestions == "A"(严重逾期) ,直接在中间件层拦截请求,返回"资质不符",连业务逻辑层都不用进,极大节省服务器资源。
4.3 字符串转布尔值的"坑"
由于 API 返回的是 "1"/"0" 字符串,在 Go 的业务逻辑中,建议写一个 Helper 方法:
Go
go
func (l LastCondition) IsHighRisk() bool {
// 命中严重逾期 或 命中欺诈 即为高危
return l.SeriousOverdue == "1" || l.Fraud == "1"
}
这样代码的可读性会高很多。
总结
用 Go 对接 天远风控决策接口,难点主要在于处理 AES-CBC 的 Padding 问题。一旦跨过这个坎,Go 的高并发能力能让你的风控系统如虎添翼。
最后提醒两点:
- 容错 :接口可能会返回
1002(解密失败) 或1007(余额不足) ,生产环境一定要做好 Error Handling。 - 保密 :你的
Access-Id和Access-Key千万别硬编码提交到 GitHub 上,那是给黑客送钱!