3. 接口设计原则:RESTful API设计规范、接口幂等性、请求与响应格式约定
实时欺诈评分接口作为风控系统的核心入口,其设计质量直接影响系统的可维护性、调用方集成效率以及数据一致性。本章围绕RESTful API设计规范、接口幂等性保障、请求与响应格式约定三个核心维度展开,帮助开发者构建标准化、高可用的评分接口。
3.1 RESTful API设计规范
REST(Representational State Transfer)是一种基于HTTP协议的架构风格,强调资源导向、无状态通信和统一接口。在实时欺诈评分场景中,我们将“评分请求”视为一种资源操作,遵循以下设计原则:
| 设计维度 | 规范要求 | 示例 |
|---|---|---|
| 资源命名 | 使用名词复数形式,全小写,单词间用连字符分隔 | /api/v1/fraud-scores |
| HTTP方法 | 仅使用POST提交评分请求(非幂等场景),GET用于查询评分记录 | POST /api/v1/fraud-scores |
| 版本控制 | 在URL路径中嵌入版本号,如 v1、v2 |
/api/v2/fraud-scores |
| 无状态 | 每次请求包含所有必要信息,服务端不保存客户端会话状态 | 请求头携带认证令牌 |
| 响应状态码 | 使用标准HTTP状态码表达语义 | 200成功、400参数错误、429限流 |
实践要点: 实时评分接口通常设计为
POST /api/v1/fraud-scores,因为每次评分请求都是独立的“创建评分结果”操作,且请求体包含完整的交易上下文。避免在URL路径中嵌入交易ID等动态参数,保持资源路径的简洁性。
设计建议: 对于批量评分场景,建议单独设计批量端点
POST /api/v1/fraud-scores/batch,而非在单个端点中通过参数区分单笔/批量模式,以保持接口语义清晰。
3.2 接口幂等性设计
幂等性(Idempotency)是指同一操作多次执行产生的结果与执行一次相同。在实时欺诈评分场景中,网络重试、消息重复投递等异常情况可能导致同一笔交易被重复评分,进而产生重复的扣分、冻结或风控记录。幂等性设计是保障数据一致性的关键防线。
3.2.1 幂等性实现策略
| 策略 | 实现方式 | 适用场景 | 注意事项 |
|---|---|---|---|
| 请求ID去重 | 调用方传入唯一请求ID(Idempotency-Key),服务端缓存处理结果 | 所有评分接口 | 缓存过期时间需合理设置(建议24小时) |
| 业务主键去重 | 使用交易订单号、设备指纹等业务字段作为唯一标识 | 订单评分场景 | 需确保业务主键全局唯一 |
| 数据库唯一约束 | 在评分记录表中建立唯一索引(请求ID或业务主键) | 高并发写入场景 | 需处理唯一键冲突异常 |
| 分布式锁 | 基于Redis等中间件实现请求粒度的分布式锁 | 跨服务调用场景 | 注意锁超时和死锁防范 |
常见陷阱: 仅依赖数据库唯一约束而不做缓存层去重,会导致每次重复请求都穿透到数据库层,增加数据库压力。建议采用“缓存层去重 + 数据库唯一约束”的双重保障策略:先查缓存,命中则直接返回缓存结果;未命中则写入数据库,利用唯一约束兜底。
3.2.2 幂等性处理流程
以下是一个基于请求ID的幂等性处理伪代码示例:
// 幂等性校验核心逻辑
public FraudScoreResponse handleScoreRequest(ScoreRequest request) {
String idempotencyKey = request.getIdempotencyKey();
// 1. 检查缓存中是否已存在该请求的处理结果
FraudScoreResponse cachedResult = cacheService.get(idempotencyKey);
if (cachedResult != null) {
return cachedResult; // 直接返回历史结果,避免重复处理
}
// 2. 尝试写入数据库(利用唯一约束防止并发重复写入)
try {
// 执行评分逻辑
FraudScore score = fraudEngine.evaluate(request);
FraudScoreResponse response = buildResponse(score);
// 写入评分记录表(idempotency_key 字段有唯一索引)
scoreRepository.insertWithUniqueKey(idempotencyKey, response);
// 3. 写入缓存,设置过期时间(如24小时)
cacheService.set(idempotencyKey, response, Duration.ofHours(24));
return response;
} catch (DuplicateKeyException e) {
// 唯一键冲突:说明其他请求已写入成功,查询并返回已有结果
return scoreRepository.queryByIdempotencyKey(idempotencyKey);
}
}
幂等性Key生成建议: 推荐调用方使用
UUID 或 时间戳 + 随机数 + 业务标识 的组合生成请求ID。服务端应要求调用方在请求头 Idempotency-Key 中传入该值,并在响应头中回传相同的Key,便于调用方进行请求-响应关联。
3.3 请求与响应格式约定
统一的请求与响应格式能够降低调用方的集成成本,提升接口的可读性和可维护性。以下是在实时欺诈评分接口中推荐的格式规范。
3.3.1 请求格式
{
"idempotencyKey": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"transaction": {
"orderId": "ORD20250321001",
"amount": 2999.00,
"currency": "CNY",
"timestamp": "2025-03-21T14:30:00+08:00"
},
"payer": {
"userId": "U12345678",
"phone": "138****5678",
"email": "user@example.com",
"deviceFingerprint": "FP_ABC123XYZ"
},
"payee": {
"merchantId": "M98765432",
"merchantName": "示例商户"
},
"additionalInfo": {
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0 ...",
"billingAddress": "北京市朝阳区..."
}
}
| 字段路径 | 类型 | 必填 | 说明 |
|---|---|---|---|
idempotencyKey |
String | 是 | 全局唯一请求ID,用于幂等性校验 |
transaction.orderId |
String | 是 | 业务订单号,建议与幂等性Key不同 |
transaction.amount |
Number | 是 | 交易金额,单位:元,保留两位小数 |
payer.userId |
String | 是 | 付款方用户ID |
payer.deviceFingerprint |
String | 否 | 设备指纹,用于设备维度风控 |
additionalInfo |
Object | 否 | 扩展信息字段,支持灵活接入 |
3.3.2 响应格式
{
"code": 200,
"message": "success",
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data": {
"score": 85,
"riskLevel": "HIGH",
"decision": "REJECT",
"hitRules": [
{
"ruleId": "R001",
"ruleName": "高频交易检测",
"riskScore": 40
},
{
"ruleId": "R007",
"ruleName": "异地登录检测",
"riskScore": 30
}
],
"processingTimeMs": 156
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
code |
Integer | 业务状态码:200成功,400参数错误,429限流,500服务异常 |
message |
String | 状态描述信息,便于调用方定位问题 |
requestId |
String | 回传调用方传入的幂等性Key,便于请求-响应关联 |
data.score |
Integer | 欺诈评分(0-100),分数越高风险越大 |
data.riskLevel |
String | 风险等级:LOW / MEDIUM / HIGH / CRITICAL |
data.decision |
String | 风控决策:APPROVE / REVIEW / REJECT |
data.hitRules |
Array | 命中的规则列表,包含规则ID、名称和贡献分数 |
data.processingTimeMs |
Integer | 服务端处理耗时(毫秒),用于性能监控 |
响应格式设计要点:
- 统一外层结构: 所有响应均包含
code、message、requestId、data四个顶层字段,便于调用方统一解析。 - 业务状态码与HTTP状态码分离: HTTP状态码用于表达传输层状态(200成功、429限流),业务状态码用于表达业务层结果(如评分成功但决策为拒绝)。
- 回传幂等性Key: 响应中的
requestId必须与请求中的idempotencyKey一致,便于调用方进行请求-响应关联和日志排查。 - 包含处理耗时:
processingTimeMs字段帮助调用方和运维人员监控接口性能,及时发现异常延迟。
3.4 错误处理与异常响应
当接口发生错误时,应返回统一的错误响应格式,帮助调用方快速定位问题。以下为推荐的错误响应结构:
{
"code": 400,
"message": "参数校验失败",
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"errors": [
{
"field": "transaction.amount",
"message": "交易金额必须大于0"
},
{
"field": "payer.userId",
"message": "用户ID不能为空"
}
]
}
| HTTP状态码 | 业务code | 典型场景 | 处理建议 |
|---|---|---|---|
| 400 | 400 | 请求参数缺失或格式错误 | 调用方根据 errors 列表修正参数后重试 |
| 401 | 401 | 认证失败或Token过期 | 重新获取认证Token后重试 |
| 429 | 429 | 请求频率超过限流阈值 | 等待 Retry-After 响应头指定的时间后重试 |
| 500 | 500 | 服务端内部异常 | 联系服务端运维人员排查,调用方可按指数退避策略重试 |
限流响应最佳实践: 当返回429状态码时,建议在响应头中添加
Retry-After: 30(单位:秒),明确告知调用方需要等待的时间。同时,在响应体中通过 message 字段说明限流原因(如“每秒请求数超过100次”),便于调用方调整调用频率。