# 3.7 服务注册与路由 :::tip 概述 本项目没有传统的路由表文件逐条登记——路由由 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(启动即暴露配置错误,而非运行时空指针)。 ::: warning 禁止 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 信封写出 ``` ::: tip 注解即事实 权限校验与操作日志共用同一份路由元数据(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 启动看路由表 ```