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,而非在单个端点中通过参数区分单笔/批量模式,以保持接口语义清晰。
RESTful 实时评分接口设计架构 调用方客户端 APP / Web / 服务 API 网关 认证 / 限流 / 路由 欺诈评分服务 规则引擎 / 模型推理 幂等校验 / 结果缓存 数据库 评分记录 接口设计关键约束 1. 资源路径:/api/v1/fraud-scores(名词复数,版本前缀) 2. HTTP方法:POST 创建评分,GET 查询历史评分记录 3. 请求头:Content-Type: application/json;Authorization: Bearer <token> 4. 无状态约束:每次请求携带完整上下文,服务端不维护会话 5. 响应状态码:200 成功、400 参数错误、429 限流、500 服务异常

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,便于调用方进行请求-响应关联。
接口幂等性处理流程 接收评分请求 缓存中是否存在 请求ID? 是 返回缓存结果 否 执行评分逻辑 写入数据库 唯一键冲突? 否 写入缓存并返回 是 查询已有结果返回 双重保障机制 缓存层:快速返回重复请求结果 数据库唯一约束:防止并发重复写入

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次”),便于调用方调整调用频率。