一个使用 Go + Gin 实现的订单后端服务,模拟电商订单核心链路,覆盖用户认证、商品查询、订单创建、库存扣减、Redis 幂等、支付状态流转、统一响应、请求日志、Docker Compose 和自动化测试。
本项目不是普通 CRUD Demo,而是围绕订单系统里的真实后端问题做设计:
- 如何保证下单时库存扣减、订单写入和订单明细写入的一致性
- 如何防止用户重复点击或网络重试导致重复下单
- 如何设计订单支付状态流转
- 如何通过日志、测试、Docker 和 CI 提升项目可维护性
| 能力 | 项目体现 |
|---|---|
| 用户认证 | 注册、登录、JWT 鉴权中间件、受保护接口 |
| 数据持久化 | MySQL 存储 users/products/inventory/orders/order_items/payments |
| 事务一致性 | 创建订单时使用事务扣减库存、写入订单和订单明细 |
| 防重复提交 | Redis Idempotency-Key 防止重复下单 |
| 状态流转 | 支持 PENDING_PAYMENT -> PAID / PAYMENT_FAILED |
| 可观测性 | RequestLogger + X-Request-ID 记录请求链路 |
| 工程化交付 | Docker Compose 一键启动 MySQL、Redis 和 Go 服务 |
| 自动化测试 | 单元测试 + MySQL 集成测试 |
| CI 质量门禁 | GitHub Actions 自动执行单元测试和集成测试 |
- JWT 用户认证:注册、登录、JWT 鉴权中间件、受保护接口访问
- MySQL 持久化:用户、商品、库存、订单、支付流水落库
- 订单库存事务:创建订单时使用 MySQL 事务和
SELECT ... FOR UPDATE扣减库存 - Redis 幂等 key:使用
Idempotency-Key防止重复下单 - 支付状态流转:支持
PENDING_PAYMENT -> PAID / PAYMENT_FAILED - 统一响应结构:所有接口统一返回
code/message/data - 请求日志:支持
X-Request-ID,记录method/path/status/latency/client_ip - Docker Compose:一键启动 MySQL、Redis 和 Go 服务
- 单元测试:覆盖 response、JWT、AuthMiddleware、RequestLogger、Redis 幂等逻辑
- MySQL 仓储集成测试:覆盖订单创建事务和支付状态流转事务
- Go
- Gin
- MySQL 8.0
- Redis 7
- JWT
- Docker Compose
database/sqlgo-redis/v9miniredis
详见:系统架构说明
其中包括:
- 系统架构图
- 订单创建流程图
- 支付状态流转图
- 初始化 Go 项目
- 接入 Gin
- 实现健康检查接口
/api/v1/health - 用户注册接口
- 注册接口参数校验
- 注册接口错误返回
- 用户登录接口
- 登录成功返回 JWT token
- JWT 鉴权中间件
- 受保护接口
/api/v1/users/me - 商品列表接口
/api/v1/products - 订单创建接口
- JWT 保护订单创建接口
- 数据库表结构设计
- Docker Compose 启动 MySQL
-
schema.sql初始化 - 用户注册/登录迁移到 MySQL
- bcrypt 密码 hash 存储
- 用户数据服务重启后仍可登录
- 商品列表迁移到 MySQL
-
seed.sql初始化数据 - Go 服务接入 MySQL
-
/api/v1/health/db - 订单创建迁移到 MySQL
- 库存扣减事务
- Redis 接入 Docker Compose
- Go 服务接入 Redis
- Redis 健康检查
/api/v1/health/redis - Redis 幂等 key
- 订单创建防重复提交
- 统一响应结构
- 统一错误响应
- 请求日志中间件
-
X-Request-ID - 支付状态流转
go run ./cmd/server启动 MySQL、Redis 和 Go 服务:
docker compose up -d --build查看容器:
docker ps期望看到:
go-order-service-mysql
go-order-service-redis
go-order-service-app验证服务:
curl.exe http://localhost:9000/api/v1/health
curl.exe http://localhost:9000/api/v1/health/db
curl.exe http://localhost:9000/api/v1/health/redis
curl.exe http://localhost:9000/api/v1/productsDocker Compose 模式访问地址:
http://localhost:9000查看 app 日志:
docker compose logs app停止服务:
docker compose down注意:不要随便执行 docker compose down -v,它会删除 MySQL 数据卷。
Go app 容器中连接 MySQL 使用:
MYSQL_HOST=mysql连接 Redis 使用:
REDIS_ADDR=redis:6379不要在 app 容器中使用 127.0.0.1 连接 MySQL 或 Redis,因为容器内的 127.0.0.1 指向 app 容器自身。
Copy-Item .env.example .envdocker compose up -d mysqlGet-Content docs/db/schema.sql | docker exec -i go-order-service-mysql mysql -uroot -prootpassdocker exec -it go-order-service-mysql mysql -uroot -prootpass -e "USE go_order_service; SHOW TABLES;"go run ./cmd/server
curl.exe http://localhost:9000/api/v1/health/dbdocker compose up -d redis
docker exec -it go-order-service-redis redis-cli ping期望返回:
PONG启动服务后,可以直接运行:
go run ./cmd/apitest health
go run ./cmd/apitest db
go run ./cmd/apitest redis
go run ./cmd/apitest products
go run ./cmd/apitest register JulieJaps 112233
go run ./cmd/apitest login JulieJaps 112233
go run ./cmd/apitest me
go run ./cmd/apitest orders
go run ./cmd/apitest orders 1 2
go run ./cmd/apitest payments
go run ./cmd/apitest payments 1 2cmd/apitest 会自动尝试 http://localhost:8080 和 http://localhost:9000。如果你想手动指定地址,可以加 -base,例如:
go run ./cmd/apitest -base http://localhost:9000 health说明:
login成功后会把 JWT token 保存到.night-hawk-token.night-hawk-token已加入.gitignore,不要提交me会自动读取.night-hawk-token并访问受保护接口/api/v1/users/meorders会自动读取.night-hawk-token并访问受保护接口/api/v1/orderspayments会自动读取.night-hawk-token并访问受保护接口/api/v1/payments/mockdb用于验证数据库连接,不需要 JWT tokenredis用于验证 Redis 连接,不需要 JWT token
GET /api/v1/healthGET /api/v1/health/dbGET /api/v1/health/redisPOST /api/v1/users/registerPOST /api/v1/users/loginGET /api/v1/users/meGET /api/v1/productsPOST /api/v1/ordersPOST /api/v1/payments/mock
默认测试不依赖 MySQL / Redis 外部服务:
go test ./...当前测试覆盖:
- 统一响应结构
response.Success / response.SuccessWithStatus / response.Error - JWT token 生成与解析
- JWT
AuthMiddleware鉴权行为 - 请求日志中间件和
X-Request-ID - Redis
Idempotency-Key首次请求、重复请求、成功标记、失败释放、用户隔离 - MySQL 仓储集成测试,默认跳过
Repository 集成测试会连接真实 MySQL,默认不运行。
需要先启动 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 单元测试:
go test ./... - MySQL repository 集成测试:
RUN_INTEGRATION_TESTS=1 go test ./internal/repository -v
CI 中会启动 MySQL 8.0 service,并执行:
docs/db/schema.sqldocs/db/seed.sql
集成测试覆盖:
- OrderRepository 创建订单事务
- 库存扣减
- 库存不足回滚
- PaymentRepository 支付成功状态流转
- 重复支付拦截
- 支付失败库存回补
go run ./cmd/apitest health
go run ./cmd/apitest db
go run ./cmd/apitest redis
go run ./cmd/apitest products
go run ./cmd/apitest register JulieJaps 112233
go run ./cmd/apitest login JulieJaps 112233
go run ./cmd/apitest me
go run ./cmd/apitest orders
go run ./cmd/apitest payments订单创建接口已经迁移到 MySQL,并接入 Redis 幂等 key。
- 接口会在事务中完成商品校验、库存校验、库存扣减、
orders写入和order_items写入 - 成功后会返回
order_no - 当前订单状态固定为
PENDING_PAYMENT POST /api/v1/orders必须携带Idempotency-Keyheader- 服务端使用
userID + Idempotency-Key组成 Redis key - 第一次请求会创建订单并扣减库存
- 重复请求会返回
409 duplicate request - 重复请求不会再次扣减库存,也不会重复写入
orders和order_items
更多接口细节请看 docs/api.md。
当前项目已支持模拟支付状态流转:
- 订单创建后状态为
PENDING_PAYMENT POST /api/v1/payments/mock携带result=SUCCESS时,订单状态变为PAID,并写入paymentsPOST /api/v1/payments/mock携带result=FAILED时,订单状态变为PAYMENT_FAILED,并恢复本次订单扣减的库存- 同一订单重复支付会返回
409 - 支付接口需要 JWT 鉴权
- 当前支付为模拟支付,不接入真实第三方支付
- 当前订单创建只支持单商品下单
- 当前重复 Idempotency-Key 返回
409,不返回第一次请求的完整响应 - 当前尚未接入消息队列
- 当前尚未做压测和 pprof 性能分析