3.7 服务注册与路由
概述
本项目没有传统的路由表文件逐条登记——路由由 Bind 反射绑定从 gmeta 注解自动生成。手工需要维护的只有两处:service 接口文件(手维护)和 router.go 的一行 Bind 注册(生成器自动写入)。
service 接口文件(手维护,真实代码摘自 internal/service/example.go)
go
// ============================== 案例演示服务接口 ==============================
// IExample 案例演示服务接口
// 实现位于 internal/logic/example.go
type IExample interface {
// Page 案例演示分页
// 按查询条件分页返回案例演示列表
Page(ctx context.Context, req *v1.ExamplePageReq) (res *v1.ExamplePageRes, err error)
// List 案例演示平铺列表
// data 直接为数组,树形结构由前端构建
List(ctx context.Context, req *v1.ExampleListReq) (res v1.ExampleListRes, err error)
// Detail 案例演示详情
// 按主键ID查询详情
Detail(ctx context.Context, req *v1.ExampleDetailReq) (res *v1.ExampleDetailRes, err error)
// Add 新增案例演示
// 新增成功后返回新记录ID
Add(ctx context.Context, req *v1.ExampleAddReq) (res *v1.ExampleIdRes, err error)
// Update 修改案例演示
// 按主键ID更新记录
Update(ctx context.Context, req *v1.ExampleUpdateReq) (res *v1.ExampleUpdateRes, err error)
// Status 案例演示状态
// 设置记录的状态字段
Status(ctx context.Context, req *v1.ExampleStatusReq) (res *v1.ExampleStatusRes, err error)
// Delete 删除案例演示
// 按主键ID删除单条记录
Delete(ctx context.Context, req *v1.ExampleDeleteReq) (res *v1.ExampleDeleteRes, err error)
// BatchDelete 批量删除案例演示
// 按主键ID列表批量删除记录
BatchDelete(ctx context.Context, req *v1.ExampleBatchDeleteReq) (res *v1.ExampleBatchDeleteRes, err error)
// Import 导入案例演示
// 接收上传的 xls/xlsx 文件并批量导入数据
Import(ctx context.Context, req *v1.ExampleImportReq) (res *v1.ExampleImportRes, err error)
// Export 导出案例演示
// 按查询条件导出数据并返回文件访问地址
Export(ctx context.Context, req *v1.ExampleExportReq) (res *v1.ExampleExportRes, err error)
}
// localExample 案例演示服务实现实例
var localExample IExample
// Example 获取案例演示服务实例
func Example() IExample {
if localExample == nil {
panic("implement not found for interface IExample, forgot register?")
}
return localExample
}
// RegisterExample 注册案例演示服务实现
func RegisterExample(i IExample) {
localExample = i
}三件套缺一不可:接口 IXxx(controller 依赖)+ 取用函数 Xxx()(controller 调用入口)+ RegisterXxx()(logic init 注册)。未注册时取用会 panic(启动即暴露配置错误,而非运行时空指针)。
禁止 gf gen service
service 接口文件是手工重组维护的(每模块一文件、中文注释齐全)。禁止整体运行 gf gen service——会按域重新生成并覆盖手工结构。logic 方法签名变更时手工同步接口文件。
路由注册(internal/router/router.go)
go
// 按域顺序注册(摘自 registerControllers)
Bind(s, new(controller.CAuth)) // 认证
Bind(s, new(controller.CIndex)) // 个人中心
Bind(s, new(controller.CUser)) // system 域
Bind(s, new(controller.CRole))
// …
Bind(s, new(controller.CExample)) // 代码生成器注册新增模块时在最后一个 Bind 行后追加一行(代码生成器 registerRouter 副作用自动写入,已存在则跳过,幂等)。
Bind 反射绑定做了什么(internal/router/bind.go)
text
Bind(s, new(controller.CXxx)) 对每个导出方法:
1. 校验签名:func (c *Xxx) Page(ctx, req *v1.XxxReq) (*v1.XxxRes, error)
2. 解析请求结构体内嵌 gmeta.Meta 扩展标签:
method / path / summary / permission / log / logType / public
3. 按 method:path 注册路由 + 写入路由元数据表 meta.Register
4. 补注册进 gf goai(闭包路由 gf 不会自动收集)→ /api.json 原生 OpenAPI
5. 生成统一包装 handler:
元数据入 ctx → 权限校验(403 短路)→ r.Parse 参数解析(含 gvalid)
→ 调用业务方法 → response.Ok/Fail 信封写出注解即事实
权限校验与操作日志共用同一份路由元数据(meta 表),gmeta 标签是唯一真相。不存在"代码改了忘改路由表/权限表"的可能。
URL 约定
- 全部路由统一
/api前缀,URL 中无/v1版本段(api/v1只是 Go 包名)。例:GET /api/user/page。 - 公开白名单(
middleware/public.go,精确匹配):/api/login、/api/register(白名单放行,路由未注册)、/api/captcha、/api/oauth2/token、/api/health;另放行/api.json与/swagger*。 - 三处必须一致:public.go 白名单 / gmeta
public:"true"标注 / router.go 手工注册。
注册验证
sh
go build ./... && ./bin/admin-server.exe
# 启动后检查:
curl http://127.0.0.1:8000/api.json | grep "/api/example" # OpenAPI 已含新路由
# 或配置 server.dumpRouterMap: true 启动看路由表