项目内的核心接口统一返回以下结构:
{
"code": 0,
"message": "success",
"data": {}
}code = 0表示成功message是提示信息data是业务数据,失败时通常为null- 当前实现里,常见错误码会直接沿用 HTTP 状态码语义,例如
400 / 401 / 404 / 409 / 500
说明:下面的 curl 示例以本地 go run 的 http://localhost:9000 为例;如果你是通过 Docker Compose 启动服务,则当前也使用宿主机 http://localhost:9000 访问同样的接口。
- 客户端可以传入
X-Request-ID - 如果客户端没有传,服务端会生成一个
- 服务端会把同一个
X-Request-ID写回响应头 - 请求日志会打印同一个
request_id
示例:
X-Request-ID: test-request-0611-001
{
"code": 0,
"message": "success",
"data": {
"service": "go-order-service",
"status": "ok"
}
}curl.exe http://localhost:9000/api/v1/health用于检查 Go 服务与 MySQL 的连接状态。
{
"code": 0,
"message": "success",
"data": {
"database": "mysql",
"status": "ok"
}
}{
"code": 500,
"message": "database unavailable",
"data": null
}curl.exe http://localhost:9000/api/v1/health/db用于检查 Go 服务与 Redis 的连接状态。
{
"code": 0,
"message": "success",
"data": {
"cache": "redis",
"status": "ok"
}
}{
"code": 500,
"message": "redis unavailable",
"data": null
}curl.exe http://localhost:9000/api/v1/health/redis{
"username": "testuser",
"password": "123456"
}{
"code": 0,
"message": "success",
"data": {
"id": 1,
"username": "testuser"
}
}{
"code": 400,
"message": "invalid request",
"data": null
}{
"code": 400,
"message": "username is required",
"data": null
}{
"code": 400,
"message": "password is required",
"data": null
}curl.exe -X POST http://localhost:9000/api/v1/users/register `
-H "Content-Type: application/json" `
-d '{"username":"testuser","password":"123456"}'{
"username": "testuser",
"password": "123456"
}{
"code": 0,
"message": "success",
"data": {
"token": "xxxxx.yyyyy.zzzzz"
}
}{
"code": 400,
"message": "invalid request",
"data": null
}{
"code": 401,
"message": "invalid username or password",
"data": null
}curl.exe -X POST http://localhost:9000/api/v1/users/login `
-H "Content-Type: application/json" `
-d '{"username":"testuser","password":"123456"}'登录成功后,cmd/apitest login 会把 token 保存到 .night-hawk-token。
该接口受 JWT 鉴权保护,请求头需要携带:
Authorization: Bearer xxxxx.yyyyy.zzzzz
{
"code": 0,
"message": "success",
"data": {
"user_id": 1,
"username": "testuser"
}
}{
"code": 401,
"message": "unauthorized",
"data": null
}curl.exe -H "Authorization: Bearer xxxxx.yyyyy.zzzzz" http://localhost:9000/api/v1/users/me直接从 MySQL 的 products 和 inventory 查询商品与库存,不需要 JWT。
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"name": "Go Backend Course",
"description": "A practical Go backend course",
"price": 19900,
"stock": 100
}
]
}curl.exe http://localhost:9000/api/v1/products创建订单接口需要 JWT 鉴权,并且必须携带 Idempotency-Key。
处理流程:
- 查询商品与库存
- 使用
SELECT ... FOR UPDATE锁定库存行 - 校验商品状态和库存数量
- 扣减
inventory.stock - 写入
orders - 写入
order_items - 提交事务
Authorization: Bearer xxxxx.yyyyy.zzzzz
Idempotency-Key: <unique-request-key>
{
"product_id": 1,
"quantity": 2
}{
"code": 0,
"message": "success",
"data": {
"id": 1,
"order_no": "ORD1780894800552424800",
"user_id": 3,
"username": "orderuser_0608",
"product_id": 1,
"product_name": "Go Backend Course",
"unit_price": 19900,
"quantity": 2,
"total_amount": 39800,
"status": "PENDING_PAYMENT",
"created_at": "2026-06-08T13:00:00.5524248+08:00"
}
}{
"code": 401,
"message": "unauthorized",
"data": null
}{
"code": 400,
"message": "idempotency key is required",
"data": null
}{
"code": 409,
"message": "duplicate request",
"data": null
}{
"code": 500,
"message": "idempotency service unavailable",
"data": null
}{
"code": 400,
"message": "quantity must be greater than 0",
"data": null
}{
"code": 400,
"message": "product_id must be greater than 0",
"data": null
}{
"code": 404,
"message": "product not found",
"data": null
}{
"code": 400,
"message": "insufficient stock",
"data": null
}curl.exe -X POST http://localhost:9000/api/v1/orders `
-H "Content-Type: application/json" `
-H "Authorization: Bearer xxxxx.yyyyy.zzzzz" `
-H "Idempotency-Key: order-test-0610-001" `
-d '{"product_id":1,"quantity":2}'go run ./cmd/apitest orders
go run ./cmd/apitest orders 1 2| 字段 | 说明 |
|---|---|
| id | 订单 ID |
| order_no | 订单号 |
| user_id | 下单用户 ID |
| username | 下单用户名,仅用于返回展示 |
| product_id | 商品 ID |
| product_name | 商品名称 |
| unit_price | 商品单价,单位为分 |
| quantity | 购买数量 |
| total_amount | 订单总金额,单位为分 |
| status | 订单状态,目前固定为 PENDING_PAYMENT |
| created_at | 订单创建时间 |
cmd/apitest db用于检查数据库连接cmd/apitest redis用于检查 Redis 连接cmd/apitest products用于检查商品列表cmd/apitest register用于检查用户注册cmd/apitest login用于检查用户登录并保存 JWT tokencmd/apitest me用于检查 JWT 鉴权后的当前用户接口cmd/apitest orders用于检查订单创建、Redis 幂等和库存扣减事务cmd/apitest orders会依次验证400 / 401 / 200 / 409cmd/apitest orders每次最多成功创建 1 个订单,重复请求会返回409
服务端已经接入请求日志中间件,会记录:
request_idmethodpathstatuslatencyclient_ip
如果请求头没有传 X-Request-ID,服务端会生成一个并写回响应头。
用于模拟订单支付状态变更。这个接口不接入第三方支付,也不做真实回调验签,主要方便本地联调和测试。
- 需要 JWT 鉴权
- 请求体必须包含
order_id和result result只允许SUCCESS或FAILED
Authorization: Bearer <token>
Content-Type: application/json
{
"order_id": 1,
"result": "SUCCESS"
}{
"code": 0,
"message": "success",
"data": {
"payment": {
"id": 1,
"payment_no": "PAY...",
"order_id": 1,
"amount": 39800,
"status": "SUCCESS"
},
"order_status": "PAID"
}
}{
"code": 400,
"message": "invalid payment result",
"data": null
}{
"code": 401,
"message": "unauthorized",
"data": null
}{
"code": 404,
"message": "order not found",
"data": null
}{
"code": 409,
"message": "order is not pending payment",
"data": null
}{
"code": 500,
"message": "internal server error",
"data": null
}curl.exe -X POST http://localhost:9000/api/v1/payments/mock `
-H "Content-Type: application/json" `
-H "Authorization: Bearer xxxxx.yyyyy.zzzzz" `
-d '{"order_id":1,"result":"SUCCESS"}'go run ./cmd/apitest payments
go run ./cmd/apitest payments 1 2Repository 层的 MySQL 集成测试默认不运行,避免 go test ./... 依赖真实数据库。
如果需要手动执行,请先启动本地 MySQL:
docker compose up -d mysql然后设置环境变量并运行:
$env:RUN_INTEGRATION_TESTS="1"
go test ./internal/repository -v
Remove-Item Env:RUN_INTEGRATION_TESTS当前仓储集成测试主要验证:
OrderRepository创建订单事务inventory.stock在创建订单时正确扣减- 库存不足时事务回滚,不写入
orders和order_items PaymentRepository支付成功状态流转PaymentRepository重复支付校验PaymentRepository支付失败后的库存回补
项目已接入 GitHub Actions。
每次 push 或 pull request 会自动执行:
- 单元测试:
go test ./... - 仓储集成测试:
RUN_INTEGRATION_TESTS=1 go test ./internal/repository -v
CI 中会启动 MySQL 8.0 service,并显式执行:
docs/db/schema.sqldocs/db/seed.sql
这意味着线上 CI 和本地测试命令保持一致,但不会依赖你本机的 MySQL 或 Redis。