第4章 RESTful审计接口设计原则:资源命名、HTTP方法、状态码

说到RESTful接口设计,很多新手容易掉进一个坑里——把接口设计成了RPC调用。我个人刚开始接触审计接口时也是这么干的,觉得审计嘛,就是记录操作日志,写几个/addAuditLog、/queryAuditByUserId就完事了。后来被一位架构师一顿批,才真正理解REST的精髓。

其实RESTful设计的核心就一句话:把业务操作映射到资源上,用HTTP的语义去表达动作。而不是你的接口名里包含动词。审计接口尤其如此,因为我们处理的审计记录本质上就是一组资源。

4.1 资源命名:用名词,别用动词

这是所有RESTful设计的共识,但也是最容易被违背的。

核心原则:资源用名词复数形式,路径层级反映资源之间的从属关系。

那审计接口的资源怎么命名?我建议围绕以下几个核心资源来设计:

  1. 审计日志 (audit-logs):最基础也是最主要的数据实体
  2. 审计策略 (audit-policies):定义审计规则、范围、级别
  3. 审计用户/主体 (subjects):谁在做操作
  4. 审计事件 (events):审计事件分类

看看错误和正确的对比:

错误命名 正确命名 说明
/api/addAuditLog POST /api/audit-logs 动词在HTTP方法里,不在URL里
/api/getAuditLogById GET /api/audit-logs/{id} 查询操作就用GET
/api/deleteAuditLog DELETE /api/audit-logs/{id} 删除操作对应DELETE方法
/api/searchAuditByUser GET /api/audit-logs?subjectId={userId} 筛选条件用查询参数

我曾经在一个项目中看到有人把接口路径写成/api/addSystemLogForUser,这实际上是个RPC风格的远程调用。如果按REST来设计,这就是POST /api/audit-logs,然后在请求体里带上subject信息就行。

资源命名还有其他几个要点:

  • 一致的命名分隔符:推荐连字符-,不要下划线或驼峰。例如audit-policies而不是auditPolicies或audit_policies
  • 版本号放在路径最前面:如/v1/audit-logs
  • 子资源用层级路径:如/v1/audit-logs/{id}/comments
  • 复数形式更常见:用audit-logs而不是audit-log

注意:不要为了“方便”把查询参数里该出现的内容放到路径里。比如/v1/audit-logs/user/123这种,应该写成/v1/audit-logs?subjectId=123。

路径层级只用来表示资源的从属关系,不是用来传参的。

4.2 HTTP方法:动作交给协议层

资源命名之后,HTTP方法决定了你对资源做什么。在审计接口中,5个标准方法各有用途:

HTTP方法 审计接口典型场景 幂等性 安全(非幂等)
GET 查询审计日志、审计策略列表或详情 是 是
POST 创建一条审计日志、批量写入审计流 否 否
PUT 全量更新审计策略、替换审计配置 是 否
PATCH 部分更新审计日志状态、修改审计等级 否 否
DELETE 删除过期的审计日志或审计策略 是 否

这里有个容易混淆的点——审计日志的“写入”。你会发现审计日志一旦生成就不太会改了,那么用PUT还是POST?我个人习惯是:如果日志ID由客户端生成,用PUT;如果ID由服务端生成,用POST。多数审计系统是由服务端生成ID,所以POST更常见。

经验之谈:审计接口中GET请求占了八成以上。你想想看,审计系统主要就是查查查。所以GET的查询设计直接影响接口可用性。

再说说GET查询的几种常见模式:

  • 分页查询:GET /v1/audit-logs?page=1&size=50
  • 时间范围:GET /v1/audit-logs?startTime=2025-01-01T00:00:00Z&endTime=2025-01-31T23:59:59Z
  • 条件筛选:GET /v1/audit-logs?subjectType=USER&action=LOGIN
  • 排序:GET /v1/audit-logs?sort=createdAt,desc

嗯,这里要注意一个设计陷阱——不要把操作用自定义的HTTP头来表示。我见过有人用X-Action: query头来实现不同的“动作”,这其实是把HTTP方法给扭曲了。

4.3 状态码:精准表达请求结果

状态码是RESTful接口的“呼吸灯”——它告诉调用方这次请求到底发生了什么。好多接口设计得不错,但状态码乱用,导致调用方要么处理不了错误,要么被迫去解析响应体里的错误码。

在审计接口里,我用得最多的是这几个:

状态码 含义 审计接口场景
200 OK 请求成功,响应体包含数据 GET查询成功、PUT/PATCH更新成功
201 Created 资源创建成功 POST创建审计日志成功
204 No Content 请求成功,响应体为空 DELETE删除成功、某些批量操作
400 Bad Request 客户端请求参数有误 必填字段缺失、时间格式错误、参数类型不对
401 Unauthorized 身份验证失败 过期或无效
403 Forbidden 权限不足 用户无审计日志查看权限
404 Not Found 资源不存在 查询不存在的审计日志ID
409 Conflict 资源状态冲突 审计策略已存在,重复创建
422 Unprocessable Entity 语义错误,业务规则不满足 审计日志内容不符合规则、数据完整性校验失败
500 Internal Server Error 服务端意外错误 数据库连接失败、未捕获的异常
503 Service Unavailable 服务暂时不可用 审计服务熔断、限流命中

个人建议:不要在状态码上“耍花招”。比如用200来表示业务异常,然后在响应体里放个code字段说明“实际上失败”。这种做法是最糟糕的——调用方必须解析响应体才能知道请求是否成功。

记住:状态码反映HTTP层面的请求结果,响应体里的错误码反映业务层面的错误原因。这是两层不同的事情。

我之前在一个审计平台上,看到接口设计把“日志写入成功”返回200,“日志写入失败”也返回200,然后响应体里放个{"success": false}。调用方得先解析json再判断。这个设计对审计这种高可靠场景来说是个大坑。

正确的做法是:比如日志内容不合法,返回422 Unprocessable Entity;如果日志写入超时,返回500或者503。调用方根据状态码就能快速判断是否要重试或报错。

4.4 RESTful审计接口设计清单

总结一下,设计一个标准的RESTful审计接口,你可以对照这个清单检查:

  1. 资源命名:用名词复数,路径层级合理,URL里没有动词
  2. HTTP方法:GET用于查询,POST用于创建,PUT/PATCH用于更新,DELETE用于删除
  3. 状态码:成功用2xx,客户端错误用4xx,服务端错误用5xx
  4. 查询参数:分页、排序、筛选都用标准查询参数
  5. 响应格式:统一的JSON结构,包含数据、错误信息(如果有)
  6. 版本管理:路径包含版本号,保证向后兼容

避坑指南:我曾经在一个项目里没有严格控制接口的幂等性——用户重复提交审计日志导致重复记录。后来我们在POST接口上加了一个X-Request-Id,用幂等键来保证同样的请求只处理一次。

审计场景对数据准确性要求极高,幂等性设计是必选项,不是可选项。

4.5 知识体系图

下面这张图概括了RESTful审计接口设计的核心逻辑:资源命名→HTTP方法映射→状态码响应。你可以把它当作设计时的“快速脑图”。

RESTful审计接口设计核心逻辑 第一步:资源命名 GET — 查询 POST — 创建 PUT/PATCH — 更新 DELETE — 删除 200/201/204 — 成功 4xx — 客户端错误 5xx — 服务端错误 设计口诀 资源名词放路径,方法语义定动作,状态码表知成败

这张图从左到右展示了设计的三个关键步骤。你先明确要操作的资源(审计日志、策略等),然后选择对应的HTTP方法来表达操作语义,最后用合适的状态码反馈结果。

实际项目里,大部分设计问题都出在第一步——资源划分不合理,导致后面怎么改都像打补丁。你设计阶段花时间把资源模型理清了,后面的工作会顺畅很多。

小提示:如果你刚开始做审计接口设计,不妨先把所有操作罗列出来,然后问自己一个问题——“这个操作在表达对资源的什么动作?” 找到答案,你的设计就完成了一半。