Files
go-omni/examples/reference/README.md

6.7 KiB
Raw Blame History

用户 CRUD 参考项目

这个示例展示如何用 GoOmni 写一个完整业务模块:参数绑定、业务规则、存储接口、分页、认证授权、统一响应、错误转换和自定义中间件。

运行

在 go-omni 根目录执行(Go 1.22 或以上):

go run ./examples/reference

默认监听 127.0.0.1:8080,可通过环境变量调整:

GO_OMNI_ADDR=127.0.0.1:8081 go run ./examples/reference

如果终端提示找不到 go,需要先把 Go 的 bin 目录加入 PATH。不要与 examples/basic 同时占用同一个端口。

默认使用内存存储,启动时为空,重启后数据清空。写接口需要 Authorization: Bearer dev-token,删除接口还需要 admin 角色;这是本地演示 Token,不可用于生产。设置 GO_OMNI_DB_DSN 后会改用 MySQL 存储,启动时会连接并 Ping 数据库;请先执行 schema.sql。

分层

examples/reference/
├── main.go                 # 组装存储、认证、业务服务、中间件和路由
├── handler/
│   ├── user.go             # JSON / URI / Query 参数校验、HTTP 错误转换
│   └── middleware.go       # 自定义结构化请求日志
├── service/
│   └── user.go             # 业务规则、输入规范化、UserRepository 接口
├── repository/
│   ├── memory.go            # 并发安全的内存实现,原子保证邮箱唯一
│   └── mysql.go             # database/sql + MySQL 持久化实现
├── model/
│   ├── user.go             # 用户、输入、分页结果
│   └── errors.go           # 业务错误
└── main_test.go            # 接口全流程与并发测试

调用路径:HTTP → Handler → Service → Repository。

  • Handler 依赖 GoOmni,示例代码不导入 Gin,也不调用 Engine() / Raw()。
  • Service、Repository、Model 不依赖 GoOmni 或 Gin,通过标准 context.Context 传递请求取消信号。
  • JSON 校验标签留在 Handler 的请求结构上;业务模型没有 HTTP 绑定标签。
  • Service 返回业务错误,由 Handler 映射为 HTTP 错误;未知错误交给框架转换成通用 500 响应。
  • 通过 application.Group("/api/v1", handler.RequestLog(logger)) 挂载自定义中间件,再注册 /users 子路由。日志记录请求 ID、耗时和返回错误;panic 由框架恢复中间件处理。

接口约定

方法 路径 成功结果
GET /health 200,健康检查
POST /api/v1/users 201,创建用户
GET /api/v1/users 200,分页列表
GET /api/v1/users/:id 200,用户详情
PUT /api/v1/users/:id 200,完整替换姓名和邮箱
DELETE /api/v1/users/:id 204,无响应体

姓名和邮箱必填。姓名最长 100 个字符,去掉首尾空白后不能为空;邮箱最长 254 字节。本示例约定整个邮箱地址不区分大小写,按小写存储并唯一校验。

PUT 必须同时提交姓名和邮箱;ID 和时间戳由服务生成。删除后允许重新使用邮箱,新用户获得新的 ID。删除不存在的用户返回 404。

列表默认 page=1&pageSize=20,每页最多 100 条,按 ID 升序;超出最后一页返回空数组并保留总数。

试一遍完整流程

以下命令假定刚启动一个空实例,第一条创建的用户 ID 为 1:

# 健康检查
curl -i http://127.0.0.1:8080/health

# 创建:201(开发 Token)
curl -i -X POST http://127.0.0.1:8080/api/v1/users \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dev-token' \
  -H 'X-Request-ID: demo-create' \
  -d '{"name":"张三","email":"zhangsan@example.com"}'

# 查询详情和分页列表:200
curl -i http://127.0.0.1:8080/api/v1/users/1
curl -i 'http://127.0.0.1:8080/api/v1/users?page=1&pageSize=10'

# 相同邮箱(包括大小写变体):409 EMAIL_TAKEN
curl -i -X POST http://127.0.0.1:8080/api/v1/users \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dev-token' \
  -d '{"name":"另一个人","email":"ZHANGSAN@example.com"}'

# 完整修改:200
curl -i -X PUT http://127.0.0.1:8080/api/v1/users/1 \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dev-token' \
  -d '{"name":"张三(已更新)","email":"zhangsan.new@example.com"}'

# 删除:204;随后查询:404 USER_NOT_FOUND
curl -i -X DELETE http://127.0.0.1:8080/api/v1/users/1 \
  -H 'Authorization: Bearer dev-token'
curl -i http://127.0.0.1:8080/api/v1/users/1

分页结果的 data 结构:

{
  "items": [],
  "total": 0,
  "page": 1,
  "pageSize": 20
}

成功与失败都使用框架的 code、message、requestId 约定;删除成功只有 HTTP 204 和请求 ID 响应头。

HTTP 状态 code 说明
400 INVALID_BODY JSON 或必填字段、格式校验失败
400 INVALID_PATH ID 不是正整数
400 INVALID_QUERY 分页参数不合法
400 INVALID_USER 业务校验失败,例如姓名全为空白
404 USER_NOT_FOUND 用户不存在
409 EMAIL_TAKEN 邮箱已被其他用户使用
500 INTERNAL_ERROR 未预期的内部错误,响应不包含内部细节

使用数据库

参考项目已经提供 MySQL 的 service.UserRepository 实现;Handler 和 Service 不需要修改。自定义数据库实现也应遵循相同契约:

  • 创建和修改时原子保证规范化后的邮箱唯一,例如使用数据库唯一索引;将唯一键冲突转换为 model.ErrEmailTaken。
  • 未找到用户返回 model.ErrUserNotFound;修改失败不保留部分更新。
  • 列表按 ID 排序、返回总数,空列表使用 []。
  • 使用传入的 context.Context 执行查询,保留创建时间,修改时更新时间。

MySQL 运行配置通过环境变量传入:

export GO_OMNI_DB_DRIVER=mysql
export GO_OMNI_DB_DSN='root:password@tcp(127.0.0.1:3306)/go_omni?parseTime=true&loc=UTC'
export GO_OMNI_DB_MAX_OPEN_CONNS=10
export GO_OMNI_DB_MAX_IDLE_CONNS=5
export GO_OMNI_DB_PING_TIMEOUT=5s
go run ./examples/reference

不设置 GO_OMNI_DB_DSN 就使用内存实现。可通过 GO_OMNI_DEV_TOKEN 替换本地演示 Token。数据库连接会在收到 SIGINT / SIGTERM 后随 HTTP 服务优雅退出而关闭;连接失败会阻止服务启动,不会静默回退到内存存储。

验证

在框架根目录执行:

go test -race ./...
go vet ./...
go build ./...

测试覆盖完整 CRUD、认证和角色权限、校验失败、分页边界、邮箱冲突及失败更新、删除后重用邮箱、请求 ID、日志关联、内部错误隐藏、并发唯一性、取消请求不修改数据、数据库配置解析和 HTTP 优雅退出。MySQL 测试不自动连接外部数据库。