主题
OpenAPI 开放接口
OpenAPI 模块为外部系统提供基于 AppKey + AppSecret 的开放接口能力。模块代码位于 modules/Openapi,后台页面位于 web/src/views/openapi,当前覆盖客户应用管理、签名认证、限流、余额、余额流水和请求日志。
模块定位
OpenAPI 模块可以分成两条链路:
| 链路 | 作用 | 入口 |
|---|---|---|
| 管理链路 | 管理客户应用、密钥、余额和日志 | modules/Openapi/routes/route.php |
| 调用链路 | 校验外部请求、执行业务接口、返回统一响应 | routes/api.php |
当前项目已经在 routes/api.php 中提供 /api/v1/user 示例路由,并挂载签名校验和限流中间件。二次开发时,新的开放接口通常继续放在同一组路由下。
模块组成
| 组件 | 主要类 | 责任 |
|---|---|---|
| 签名中间件 | CheckSignatureMiddleware | 生成请求 ID,校验 app-key、signature 和签名结果 |
| 限流中间件 | RateLimiterMiddleware | 按客户应用配置限制请求频率 |
| 签名服务 | OpenapiAuth | 校验应用、时间戳、签名,记录请求日志,更新最近调用时间 |
| 响应服务 | OpenapiResponse | 输出统一 JSON,并回填日志响应码、错误信息和耗时 |
| 客户应用 | Users | 保存应用信息、密钥、状态和最近使用时间 |
| 余额 | UserBalance | 保存客户当前余额 |
| 余额流水 | UserBalanceLog | 保存充值、扣费、调账、退款流水 |
| 请求日志 | OpenapiRequestLog | 保存请求参数、调用方、响应结果和耗时 |
后台菜单当前包含两页:
| 菜单 | 页面 | 说明 |
|---|---|---|
| 用户管理 | web/src/views/openapi/users | 管理客户应用、查看详情、充值、查看流水、重置密钥 |
| 请求日志 | web/src/views/openapi/openapiRequestLog | 查看调用参数、业务码、错误信息和耗时 |
数据模型
| 表 | 作用 | 关键字段 |
|---|---|---|
openapi_users | 客户应用 | app_key、app_secret、qps、status、last_used_at、secret_reset_at |
openapi_user_balance | 当前余额 | user_id、balance |
openapi_user_balance_log | 余额流水 | type、amount、before_balance、after_balance、request_id |
openapi_request_log | 请求日志 | request_id、user_id、app_key、path、response_code、duration_ms |
余额流水类型
UserBalanceLogType 当前定义了 5 类流水:
| 值 | 类型 |
|---|---|
1 | 充值 |
2 | 扣费 |
3 | 调账增加 |
4 | 调账减少 |
5 | 退款 |
当前已经落地的后台动作是“充值”,扣费、调账和退款仍保留在枚举层。
请求认证链路
链路里有两个容易忽略的事实:
- 请求日志在
OpenapiAuth完成应用、状态和时间戳校验后才写入;缺少请求头、无效应用、应用禁用、时间戳失效这几类请求当前不会生成日志记录。 - 无效签名会先写入日志,再由
OpenapiResponse回填失败业务码,便于按request_id排查。
签名规则
外部请求需要同时满足以下约定:
| 项 | 约定 |
|---|---|
| 请求头 | app-key、signature |
| 请求参数 | 必须包含 timestamp |
| 时间窗口 | config('api.timestamp_period', 60),当前默认回退值为 60 秒 |
| 算法 | HMAC-SHA256 |
| 密钥 | 客户应用的 app_secret |
签名过程:
- 读取所有请求参数。
- 递归扁平化嵌套对象和数组。
- 按参数名排序。
- 拼成
key=value&key=value。 - 使用
app_secret生成 HMAC-SHA256 摘要。
扁平化示例:
json
{
"user": {
"name": "张三"
},
"items": [
{ "id": 1 }
],
"timestamp": 1760000000
}会生成以下键值:
text
items[0].id=1
timestamp=1760000000
user.name=张三最终签名串:
text
items[0].id=1×tamp=1760000000&user.name=张三统一响应
开放接口应统一使用 OpenapiResponse:
php
use Modules\Openapi\Facade\OpenapiResponse;
return OpenapiResponse::success(['ok' => true]);
return OpenapiResponse::error('failed');成功响应示例:
json
{
"code": 10000,
"message": "success",
"data": {
"ok": true
},
"trace": {
"request_id": "018f0000-0000-7000-9000-000000000000",
"timestamp": 1760000000,
"take_time": 12
}
}分页响应还会额外返回 total、limit 和 page。
状态码
| 业务码 | 常量 | 说明 |
|---|---|---|
10000 | SUCCESS | 成功 |
10001 | FAILED | 通用失败 |
10002 | APP_KEY_LOST | 缺少 app-key |
10003 | SIGNATURE_LOST | 缺少 signature |
10004 | INVALID_APP_KEY | 应用不存在 |
10005 | INVALID_SIGNATURE | 签名失败 |
10006 | INVALID_TIMESTAMP | 时间戳失效 |
10007 | BALANCE_NOT_ENOUGH | 余额不足 |
10008 | RATE_LIMIT | 触发限流 |
10009 | APP_DISABLED | 应用已禁用 |
后台管理接口
后台接口定义在 modules/Openapi/routes/route.php。
下表沿用后台前端使用的相对路径;实际 HTTP 路径会带上 /api/ 前缀。
| Method | 接口 | 用途 |
|---|---|---|
GET | openapi/users | 客户应用列表 |
POST | openapi/users | 创建客户应用 |
GET | openapi/users/{id} | 客户应用详情 |
PUT | openapi/users/{id} | 更新客户应用 |
DELETE | openapi/users/{id} | 删除客户应用 |
PUT | openapi/users/enable/{id} | 启用或禁用客户应用 |
PUT | openapi/user/{id}/regenerate | 重置密钥 |
POST | openapi/user/charge | 充值 |
GET | openapi/user/balance/log | 余额流水 |
GET | openapi/request/log | 请求日志 |
DELETE | openapi/request/log/{id} | 删除请求日志 |
客户应用
创建客户应用时,Users::booted() 会自动生成 app_key、app_secret,并把缺省状态写成启用。更新时,空密码会被控制器剔除,原密码保持不变。
常用字段:
| 字段 | 说明 |
|---|---|
username | 客户应用名称 |
mobile | 联系手机号,当前按手机号唯一校验 |
qps | 调用频率配置 |
status | 1 启用,2 禁用 |
balance_warning | 余额预警阈值 |
last_used_at | 最近一次签名成功时间 |
secret_reset_at | 最近一次密钥重置时间 |
充值
UsersController::charge() 使用事务和 lockForUpdate() 更新余额,再写入 openapi_user_balance_log。当前请求字段:
| 字段 | 规则 |
|---|---|
user_id | 必填,必须存在于 openapi_users |
balance | 必填,整数,最小值 1 |
remark | 可选,最多 500 字符 |
成功响应:
json
{
"user_id": 1,
"balance": 500
}请求日志
请求日志页支持按 user_id、app_key、request_id、path、response_code 查询。详情页会展示请求参数、业务码、HTTP 状态、错误信息和耗时。路由层当前使用 apiResource 注册请求日志资源,控制器已经实现的是列表和删除能力,后台页面也只消费这两条链路。
服务端接入
路由配置
当前项目已经在 routes/api.php 中提供示例:
php
use Illuminate\Support\Facades\Route;
use Modules\Openapi\Facade\OpenapiResponse;
use Modules\Openapi\Middlewares\CheckSignatureMiddleware;
use Modules\Openapi\Middlewares\RateLimiterMiddleware;
Route::prefix('v1')->middleware([
CheckSignatureMiddleware::class,
RateLimiterMiddleware::class,
])->group(function () {
Route::any('user', function () {
return OpenapiResponse::success([]);
});
});Laravel 会给 routes/api.php 自动添加 /api 前缀,所以示例地址是 /api/v1/user。
最小调用示例
bash
curl -X POST 'https://your-domain.com/api/v1/user' \
-H 'app-key: your_app_key' \
-H 'signature: signature_by_hmac_sha256' \
-H 'Content-Type: application/json' \
-d '{"timestamp":1760000000}'异常渲染
bootstrap/app.php 当前会把 OpenapiException 统一渲染为 OpenapiResponse::error()。对 URI 以 api/v1 开头的普通异常,也会走开放接口响应分支;其中 QueryException 当前仍返回 ApiResponse::error(),这是现有实现边界。
排障
每个通过 OpenapiResponse 返回的响应都会携带 trace.request_id。排障时优先按该字段定位日志:
sql
select *
from openapi_request_log
where request_id = 'response_trace_request_id'
limit 1;| 字段 | 用途 |
|---|---|
response_code | 判断失败类型 |
error_message | 查看服务端错误消息 |
app_key / user_id | 确认调用方 |
path / method | 确认目标接口 |
data | 复算签名 |
duration_ms | 判断耗时 |
常见定位方向:
| 业务码 | 检查点 |
|---|---|
10004 | 客户是否仍在使用旧密钥 |
10005 | 扁平化、排序、app_secret 是否一致 |
10006 | 客户端时间和服务端时间差 |
10008 | 当前应用 qps 配置和调用频率 |
10009 | 客户应用状态 |
模块已有自动化测试覆盖:
- 新建应用默认启用
- 禁用应用拒绝调用
- 充值写入余额和流水
- 重置密钥
- 签名成功后更新最近调用时间并完成请求日志
bash
php artisan test --compact tests/Feature/Openapi/OpenapiAdminMvpTest.php可扩展方向
当前实现已经留出了几条清晰的延展线:
- 在
routes/api.php的v1分组中继续挂载新的开放接口。 - 基于
UserBalanceLogType增加扣费、调账和退款业务流。 - 给
OpenapiAuth的应用查询增加缓存层。 - 把请求日志扩展为异步写入或接入外部监控系统。
- 为新的业务错误补充
Code枚举和对应异常类。