7.1 KiB
用户 CRUD 参考项目
这个示例展示如何用 GoOmni 写一个完整业务模块:参数绑定、业务规则、存储接口、分页、认证授权、统一响应、错误转换和自定义中间件。
运行
在 go-omni 根目录执行(Go 1.22 或以上):
go run ./examples/reference
默认读取 examples/reference/config.yaml,监听 127.0.0.1:8080。文件已显式引用地址变量:
GO_OMNI_HTTP_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,示例代码不导入具体网络库,也不调用
Engine()/Raw()。 - Service、Repository、Model 不依赖 GoOmni 或网络库,通过标准
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执行查询,保留创建时间,修改时更新时间。
此手写示例显式读取 GO_OMNI_DB_DSN;驱动固定 mysql,连接池使用 database.DefaultConfig()。项目可将数据库配置放入自己的 Config 并通过 LoadProject 统一加载(见 配置指南),框架不再自动读取数据库环境变量:
export GO_OMNI_DB_DSN='root:password@tcp(127.0.0.1:3306)/go_omni?parseTime=true&loc=UTC'
go run ./examples/reference
不设置 GO_OMNI_DB_DSN 就使用内存实现。可通过 GO_OMNI_DEV_TOKEN 替换本地演示 Token。数据库连接会在收到 SIGINT / SIGTERM 后随 HTTP 服务优雅退出而关闭;连接失败会阻止服务启动,不会静默回退到内存存储。
验证
在框架根目录执行:
go test -race ./...
go vet ./...
go build ./...
TestUserCRUD 使用标准库适配器运行完整业务注册代码;同一套 Handler、Service、Repository 也可通过 omni.NewWithRouter 接入自研适配器。
测试覆盖完整 CRUD、认证和角色权限、校验失败、分页边界、邮箱冲突及失败更新、删除后重用邮箱、请求 ID、日志关联、内部错误隐藏、并发唯一性、取消请求不修改数据、数据库配置解析和 HTTP 优雅退出。MySQL 测试不自动连接外部数据库。