一个轻量级订单库存管理系统,重点展示 Go 后端业务分层、事务一致性、库存扣减、订单状态机、Redis 缓存和测试能力。
本项目基于 Go + Gin + GORM + MySQL + Redis 实现,提供商品管理、库存管理、库存流水、幂等订单创建、订单状态流转和商品详情缓存等能力。
项目的目标不是堆功能,而是把常见后端工程能力做扎实:
- 清晰的 handler / service / dao / model 分层
- 统一请求参数校验和响应结构
- 使用事务保证订单创建和库存扣减一致
- 使用唯一幂等 Key 和请求摘要避免重复创建订单
- 使用库存流水追踪每一次库存变化
- 使用订单状态机限制非法状态流转
- 通过文档和测试清单支撑项目复盘
- HTTP Server 设置 ReadTimeout、WriteTimeout、IdleTimeout、ReadHeaderTimeout 和 MaxHeaderBytes
- MySQL 初始化时配置连接池:MaxOpenConns、MaxIdleConns、ConnMaxLifetime、ConnMaxIdleTime
- 启动时使用 PingContext 检查 MySQL 连通性
- 请求层使用 Request ID、Access Log 和 Recovery;HTTP Server 外层强制请求超时,向下游传递 deadline,超时返回
503 / 5002并隔离后续响应写入 - Redis 不可用时商品详情缓存自动降级,不影响主流程
- CI 覆盖 go test、go test -race、go vet、golangci-lint、goose validate 和 go build
- Dockerfile 使用多阶段构建和非 root 用户运行应用
- Docker Compose 编排应用、MySQL 和 Redis,并通过健康检查控制依赖启动顺序
- Goose 管理数据库版本,Makefile 统一封装开发、测试、Docker 和迁移命令
- Go 1.25.5
- Gin + GORM
- MySQL 8.4 + Redis 7.2
- Goose 数据库迁移
- YAML + godotenv 配置
- Docker + Docker Compose
- golangci-lint + GitHub Actions
已完成:
- 商品创建、查询、上下架
- 库存初始化、增加、查询
- 库存流水记录
- 创建订单时扣减库存
- 创建订单 idempotency_key 幂等控制
- 库存不足时事务回滚
- 订单支付、完成、取消
- 取消待支付订单时回滚库存
- 订单状态机限制非法流转
- 商品详情 Redis cache-aside 缓存
- 商品上架 / 下架时删除缓存
- Redis 不可用时不影响主流程
- 并发下单防超卖和多商品事务回滚测试
未实现,作为后续演进:
- 除健康检查外的 handler 业务接口测试、DAO 层测试与更完整的异常分支覆盖
- 自动迁移或独立 migration job,进一步简化首次启动流程
- 创建商品
- 查询商品列表
- 查询商品详情
- 商品上架
- 商品下架
- 初始化商品库存
- 增加商品库存
- 查询商品库存
- 记录库存变更流水
- 使用
idempotency_key幂等创建订单 - 查询订单列表
- 查询订单详情
- 支付订单
- 完成订单
- 取消订单
- 取消订单时回滚库存
- 商品详情缓存 cache-aside
- 商品上架 / 下架时删除商品详情缓存
.github/workflows/ci.yml 持续集成配置
cmd/ 项目启动入口
config/ YAML 加载、环境变量覆盖和配置校验
docs/ 设计文档、REST Client 请求和验证证据
internal/apperror/ 业务错误定义与错误码映射
internal/app/ 依赖装配、HTTP Server 和优雅退出
internal/bizcache/ Redis 业务缓存
internal/dao/ 数据库访问层
internal/handler/ HTTP 接口层
internal/middleware/ 请求 ID、日志、超时和恢复中间件
internal/model/ GORM 数据模型
internal/request/ 请求参数和校验规则
internal/response/ 统一响应结构
internal/service/ 业务规则、状态机和事务
migrations/ Goose SQL 迁移
pkg/database/ MySQL 初始化与连接池
pkg/redis/ Redis 客户端初始化
router/ 路由注册
compose.yml 应用、MySQL、Redis 编排
Dockerfile 应用镜像多阶段构建
Makefile 开发、测试、Docker 和迁移命令入口
项目采用简单的企业后端分层方式:
- handler:负责 HTTP 请求处理、参数绑定、错误映射和统一响应
- service:负责业务规则、状态流转、事务控制和跨表操作
- dao:负责数据库 CRUD、条件查询和条件更新
- model:负责数据库表结构映射
- request:负责接口入参结构和校验规则
- response:负责接口响应结构
- bizcache:负责业务缓存读写、缓存 key 规则和缓存失效
- apperror:负责业务错误定义、错误码和错误信息封装
核心原则:handler 不写业务规则,service 不直接拼 HTTP 响应,dao 不处理业务状态。
当前核心表:
- products:商品表
- product_inventories:商品库存表
- stock_logs:库存流水表
- orders:订单主表
- order_items:订单明细表
- order_idempotency_keys:订单创建幂等记录表
关键设计点:
- 商品价格使用 price_fen,单位为分,避免浮点精度问题
- 商品创建后默认下架,避免未准备库存的商品直接下单
- product_inventories 通过 product_id 唯一索引保证一个商品只有一条库存记录
- stock_logs 记录 before_quantity、change_quantity、after_quantity,便于追踪库存变化
- orders 使用状态机控制待支付、已支付、已完成、已取消
- order_items 保存下单时的商品名称和价格快照
- order_idempotency_keys 通过 idempotency_key 唯一索引仲裁并发请求,并通过 request_hash 检测 Key 复用冲突
详细表结构见:docs/table_design.md
- 商品名称不能为空
- 商品价格 price_fen 必须大于 0
- 商品创建后默认下架,status = 2
- 商品上架后 status = 1
- 商品下架后 status = 2
- 查询商品时,默认查询下架的商品, status = 2
- 初始化库存前商品必须存在
- 一个商品只能初始化一次库存
- 增加库存前库存记录必须存在
- 库存变更必须写入 stock_logs
- 库存流水 biz_type:1 初始化库存,2 手动入库,3 订单扣减,4 取消订单回滚
- 创建订单时 idempotency_key 必填且长度不能超过 128
- 相同 idempotency_key 和相同请求返回原订单,相同 Key 和不同请求返回冲突
- 创建订单时 items 不能为空
- 下单商品必须存在且已上架
- 商品库存必须存在且充足
- 幂等记录、创建订单、扣减库存、创建订单项、写库存流水必须在同一个事务内完成
- 取消待支付订单时需要回滚库存
- 查询商品详情时,设置商品缓存
- 上架/下架 商品时,删除商品缓存
详细规则见:docs/business_rules.md
当前项目在订单创建与取消场景中,使用数据库事务保证幂等记录、订单、订单项、库存和库存流水一致性,并通过库存行级锁控制并发扣减。
订单创建流程
- 计算规范化请求摘要,并通过唯一 idempotency_key 抢占创建权
- 通过 UUID 生成 orderNo,并创建订单
- 遍历订单商品项
- 对每个商品库存记录使用行级锁(
FOR UPDATE)读取并计算调整前后库存 - 减去需要扣减的商品库存
- 创建订单明细 order_items
- 创建并记录商品库存调整流水
- 创建完成后关联幂等记录与 order_id;相同请求重放时返回原订单
- 任一步骤失败时,幂等记录和订单事务整体回滚,避免出现部分写入
订单取消流程
- 查询需要取消的订单
- 判断订单状态,仅允许取消待支付订单;已取消订单按幂等直接返回
- 查询订单下已订购的商品
- 遍历订单商品项,并回滚商品库存
- 创建并记录商品库存调整流水
- 任一步骤失败时,事务整体回滚,避免库存回滚不完整
订单状态:
- 1:待支付
- 2:已支付
- 3:已完成
- 4:已取消
允许的状态流转:
- 待支付 -> 已支付
- 已支付 -> 已完成
- 待支付 -> 已取消
禁止的状态流转:
- 已支付订单不能取消
- 已完成订单不能取消
- 已取消订单不能支付或完成
- 未支付订单不能完成
当前项目对商品详情接口增加了 cache-aside 缓存。
product:detail:{product_id}
-
查询商品详情时,优先读取 Redis
-
如果 Redis 命中,直接返回缓存数据
-
如果 Redis 未命中,查询 MySQL
-
MySQL 查询成功后,将商品详情写入 Redis
-
Redis 不可用时,不影响 MySQL 主流程
-
商品状态变化时删除缓存:
-
商品上架:删除商品详情缓存
-
商品下架:删除商品详情缓存
- 当前仅对商品详情接口实现 cache-aside 缓存
- 当前通过“状态变更时主动删缓存”保证基础一致性,不包含延迟双删等增强策略
- 当前未引入缓存击穿保护(如互斥锁、逻辑过期),后续可按流量特征演进
- Redis 不可用时直接降级到 MySQL 主流程,优先保证业务可用性
创建订单要求客户端提交 idempotency_key,长度不超过 128。服务端对规范化后的订单项计算 SHA-256 请求摘要,并依赖 order_idempotency_keys.idempotency_key 唯一索引完成并发仲裁。
请求示例:
{
"idempotency_key": "order-create-20260627-001",
"items": [
{
"product_id": 1,
"quantity": 2
}
]
}- 首次请求获得创建权,在同一事务中创建幂等记录、订单、订单项、库存扣减和库存流水
- 相同 Key 且请求摘要相同:返回已创建的原订单,不重复扣减库存
- 相同 Key 但请求摘要不同:返回 HTTP 409 幂等冲突
- 创建失败:幂等记录随事务回滚,客户端可以使用原 Key 重试
- 多个并发请求使用相同 Key:数据库只允许一个请求创建订单,其余请求返回同一订单
幂等记录状态:1 表示创建中,2 表示已创建。创建完成后记录关联 order_id;事务失败时幂等记录同步回滚。
增加库存、支付订单和完成订单当前仍不使用请求级幂等 Key;重复调用由各自业务状态规则处理。
接口说明详见:docs/api_list.md
应用启动时先加载 .env,再读取 config.yml。环境变量会覆盖 YAML 中适合按环境变化的连接配置。
MYSQL_PASSWORD=your-password
REDIS_PASSWORD=MYSQL_PASSWORD:必填,应用、Docker Compose 和 Goose 共用。REDIS_PASSWORD:可选;当前 Compose 中的 Redis 未启用密码认证,保持为空即可。- 不要提交真实的
.env,可从 .env.example 复制后修改。
| 环境变量 | 覆盖的配置 |
|---|---|
APP_PORT |
server.port |
DB_HOST |
mysql.host |
DB_PORT |
mysql.port |
DB_USER |
mysql.user |
DB_NAME |
mysql.database |
REDIS_ADDR |
redis.addr |
REDIS_DB |
redis.db |
本地运行默认连接 127.0.0.1:3306 和 127.0.0.1:6379。Compose 会为应用容器设置 mysql:3306 和 redis:6379,无需修改 config.yml。
- Go 1.25.5
- GNU Make
- Docker 与 Docker Compose
- Goose v3.27.1
go mod download
go install github.com/pressly/goose/v3/cmd/goose@v3.27.1PowerShell 示例:
Copy-Item .env.example .env
$env:MYSQL_PASSWORD = "your-password"
make infra-up
make migrate-up
make run首次启动必须执行 make migrate-up 建表,其中 00006_add_order_idempotency_keys.sql 创建订单幂等表。之后可用 make dev 启动 MySQL、Redis 并运行应用。
$env:MYSQL_PASSWORD = "your-password"
make infra-up
make migrate-up
make docker-up迁移由宿主机上的 Goose 执行,默认连接 Compose 暴露的 127.0.0.1:3306。当前 docker-up 不会自动执行数据库迁移。
常用 Docker 命令:
| 命令 | 作用 |
|---|---|
make compose-config |
校验 Compose 配置 |
make infra-up |
仅启动 MySQL 和 Redis,并等待健康 |
make infra-down |
停止 Compose 项目 |
make docker-build |
构建应用镜像 |
make docker-up |
构建并启动应用、MySQL、Redis |
make docker-down |
停止并移除容器,保留数据卷 |
make docker-ps |
查看服务状态 |
make docker-logs |
持续查看全部服务日志 |
常用迁移命令:
| 命令 | 作用 |
|---|---|
make migrate-validate |
静态校验迁移文件 |
make migrate-status |
查看数据库迁移状态 |
make migrate-up |
执行全部待处理迁移 |
make migrate-up-one |
只执行下一条迁移 |
make migrate-up-to VERSION=5 |
迁移到指定版本 |
make migrate-down |
回滚最近一条迁移 |
make migrate-down-to VERSION=3 |
回滚到指定版本 |
make migrate-redo |
重做最近一条迁移 |
make migrate-create NAME=add_sku |
创建顺序编号的 SQL 迁移 |
默认访问地址为 http://localhost:8082,健康检查为:
curl http://localhost:8082/pingservice 测试会清理所连接数据库中的业务表。必须使用独立测试库,禁止将 DB_NAME 指向含有开发数据或生产数据的数据库。
| 测试项 | 当前状态 | 检查结论 |
|---|---|---|
| 核心业务测试 | 已有 | internal/service/*_test.go 覆盖商品、库存、订单创建、状态机和关键异常分支 |
| 并发防超卖测试 | 已有 | TestOrder_ConcurrentTesting_OrderOversold 和多数量并发测试校验成功数、失败数、最终库存和库存流水 |
| 多商品事务回滚测试 | 已有 | TestCreateOrder_MultipleItemsSecondInsufficient_Rollback 验证第二件商品失败时前序扣减和订单数据全部回滚 |
| 创建订单幂等测试 | 已有 | 覆盖同 Key 重放、不同请求冲突、并发同 Key 只创建一单、失败回滚后重试和空 Key |
| 订单状态并发测试 | 已有 | 覆盖并发支付、并发取消以及支付与取消竞争 |
假设已创建 go_order_inventory_test:
$env:MYSQL_TEST_PASSWORD = "your-password"
$env:MYSQL_TEST_DATABASE = "go_order_inventory_test"
make test-service常用测试和质量命令:
| 命令 | 作用 |
|---|---|
make test |
运行全部 Go 测试 |
make test-service |
设置 RUN_MYSQL_TEST=1 并运行 MySQL service 集成测试 |
make test-redis |
运行 Redis 集成测试 |
make test-all |
运行普通测试、MySQL service 测试和 Redis 集成测试 |
make test-race |
使用 race detector 运行普通测试;集成测试需要额外设置对应环境变量 |
make coverage |
生成 coverage.out |
make coverage-html |
生成 coverage.html |
make check |
执行格式化、模块校验、vet 和测试 |
Redis 集成测试前需保证 Redis 已启动,可先执行 make infra-up。
手动接口测试文件位于 docs/http,完整业务链路见 docs/http/demo_flow.http。测试计划见 docs/test_plan.md。
- docs/api_list.md:接口清单
- docs/business_rules.md:业务规则
- docs/table_design.md:数据表设计
- docs/test_plan.md:测试计划
- docs/test_result.md:测试结果记录
- docs/project_evolution.md:后续演进
- docs/evidence:项目运行、测试与关键业务截图证据
本目录用于保存项目运行、测试和关键业务链路截图,便于项目展示和面试讲解。
- 创建订单成功:
create_order_success_2026-05-23_17-26-48.png - 创建订单库存不足并回滚:
create_order_insufficient_inventory_rollback_2026-05-25_00-02-52.png - 取消订单后库存回滚:
order_cancel_inventory_rollback_2026-05-25_00-10-18.png - Redis 商品详情缓存命中:
redis_get_product_cache_success_2026-05-25_00-16-01.png - 商品上架/下架后缓存删除:
redis_on_or_off_sale_product_cache_delete_success_2026-05-25_00-16-01.png - Redis 集成测试执行成功:
redis_test_execute_success_2026-05-23_17-23-36.png - 自动化测试运行结果(分段截图):
test_run_success_part_1_2026-05-23_17-19-26.pngtest_run_success_part_2_2026-05-23_17-19-26.pngtest_run_success_part_3_2026-05-23_17-19-26.png
- 使用 handler / service / dao / model 分层组织代码,避免业务逻辑散落在接口层
- 创建订单使用事务保证 order_idempotency_keys、orders、order_items、product_inventories、stock_logs 多表一致性
- 库存扣减使用库存行锁 + 条件更新,并通过并发测试验证不会超卖
- order_items 保存商品名称和价格快照,避免商品后续修改影响历史订单
- stock_logs 记录库存变更前后数量、业务类型和业务 ID,便于排查库存异常
- 订单状态机限制待支付、已支付、已完成、已取消之间的非法流转
- 取消待支付订单时回滚库存,并记录 biz_type=4 的库存流水
- 创建订单通过唯一幂等 Key、请求摘要和事务回滚实现并发幂等
- 商品详情使用 Redis cache-aside 缓存,商品上下架时删除缓存
- Redis 异常时降级走 MySQL,不影响主业务流程
- 使用 AppError 统一业务错误、HTTP 状态码和业务 code,减少 handler 层重复错误判断
- 配置支持 YAML 默认值、环境变量覆盖和启动参数校验
- HTTP Server 配置超时、请求 ID、访问日志、panic 恢复和优雅退出
- Docker 使用多阶段构建、非 root 用户、健康检查和 Compose 依赖编排
- Goose 管理数据库版本,CI 自动执行 lint、test、race、vet、build 和迁移校验
- 增加 handler 业务接口测试和 DAO 测试
- 在 Compose 或部署流水线中加入独立 migration job
- 增加指标、链路追踪和结构化日志字段规范
- 优化错误码文档和接口返回示例
- 评估使用雪花 ID 替代当前 UUID orderNo
- 为幂等记录增加过期清理策略,并在引入用户体系后按用户隔离 Key