# 公共字段、中间件工厂与生成选项 本页对应 `v0.19.0-preview`。CLI 与框架依赖固定到同一版本,安装见 [README](README.md#准备环境与安装)。 ## 一份项目生成配置 新项目根目录的 `omni.gen.json` 只描述生成选项: ```json { "repository": "none", "middleware": "factories" } ``` - `repository: none` 默认不创建空 Repository 文件;`group` 按 group 补齐可编辑骨架。已有文件始终保留;切回 none 不会删除业务文件。SQL、ORM 和仓储划分由项目决定。 - `middleware: factories` 是新项目默认值,自动调用带依赖的工厂。`handlers` 保留原有 `Defaults()` 及手动注册方式,供既有项目迁移。 - 该文件与 `.api` 一起提交。generate/check/dry-run 和 workspace 采用相同规则;validate/export/format 只处理接口契约。 - 旧 manifest 没有配置时补 `middleware: handlers`、`repository: none`。已有配置不能直接删除,避免意外切换中间件模式。 - v0.19 已删除 `mappings` 和 `mappingFiles`;遇到旧配置会明确报错,不读取子映射文件或执行映射生成。处理步骤见 [copyx 迁移](COPY.md#从旧-mapping-迁移)。 ## 公共字段组合 ```text type EmployeeFields { // 员工姓名。 Name string `json:"name" binding:"max=80"` Salary Money `json:"salary"` } type EnterpriseRequest { EmployeeFields } type AdminRequest { EmployeeFields CompanyID string `path:"companyId"` } ``` `Money` 由项目按既有 scalar 语法声明。组合行只有已声明的对象类型名,不加指针或字段 tag。公共对象可以通过已有相对 import 或声明过的共享根 `@common/...` 引入。 生成器展开为普通 Go 字段。JSON 保持平铺;字段的绑定来源、校验、注释、可空性和 OpenAPI 使用同一份展开结果。不会在 JSON 中增加 `EmployeeFields` 一层。重复 Go 字段、重复来源字段名、循环组合、未知对象和过深展开均失败;即使重复字段类型相同,也不能隐式覆盖。要改变某个字段的规则,应拆出更小的公共组合。 允许多层组合,上限 32 层、单类型 4096 字段、全契约展开 65536 字段。Go 匿名指针嵌入并非本功能;生成 DTO 中不会使用匿名嵌入。 ## DTO 与业务类型 普通 Logic 直接接收生成的 Request、返回生成的 Response,不额外创建相同结构。只有独立核心或明确不同的输入/输出职责才保留业务类型。 必要转换直接写 Go,或用可选 `copyx.Copy(&target, source)`。同名同类型字段自动赋值,字段改名和类型变化通过 Go 参数、转换函数明确设置,见 [完整示例](examples/copying/main.go)。无需 JSON 映射配置、包路径字符串或额外生成文件。 `copyx` 在运行时检查类型和字段,**不提供旧映射生成阶段的静态字段覆盖保证**;CI 应给实际类型对添加测试。普通业务字段新增通常只改 `.api` 和 Logic;独立 DTO/领域类型都需要此字段时,两边各加一次,同名同类型无需追加拷贝配置。 ## 中间件工厂自动注册 `.api` 沿用 `middleware: RequireAccess` 或 auth 对应的中间件声明。新项目会生成可编辑的 `internal/middleware/requireAccessFactory.go`,只需实现一次: ```go // internal/middleware/dependencies.go,由项目维护。 type AccessChecker interface { CheckAccess(context.Context, string) error } type Dependencies interface { AccessChecker() AccessChecker } // internal/middleware/requireAccessFactory.go,由项目维护。 func NewRequireAccess(deps Dependencies) (httpx.Middleware, error) { checker := deps.AccessChecker() if checker == nil { return nil, errors.New("access checker is required") } return func(next httpx.Handler) httpx.Handler { return func(c *httpx.Context) error { if err := checker.CheckAccess(c.Context(), c.Header("Authorization")); err != nil { return err } return next(c) } }, nil } // internal/svc/access.go,由项目维护;ServiceContext.Init 初始化 c.Auth。 func (c *ServiceContext) AccessChecker() middleware.AccessChecker { return c.Auth } ``` 编译器检查 ServiceContext 是否实现依赖接口,middleware 不导入 svc。多个工厂可以共用这些小接口。所需 imports 和具体鉴权服务仍属于项目。 启动顺序为:框架基础依赖 → `svc.Init` → 生成的 `middleware.New(c)` 调用各具名工厂 → 创建 App/注册路由 → 监听。中间件实例创建一次,由请求并发复用;不要把单次请求信息存在实例字段里。工厂之间没有初始化顺序依赖;路由执行顺序仍以 `.api` 声明为准。 工厂返回错误或 nil 中间件均阻止监听;错误链保留,已登记资源按现有生命周期清理。工厂 panic 也先清理依赖再传播。资源初始化和清理登记放在 svc.Init,工厂借用资源;HTTP 请求排空之前不会关闭这些依赖。 factory 模式不再在 Init 写 `Middlewares["..."]`;残留非空手写注册表会明确报错,防止生成注册静默覆盖它。未实现的工厂让启动失败;未实现的 Logic 仍返回 501。 ### 既有项目迁移 1. 首次更新保持 `middleware: handlers`,现有 hook 和手写 Configure 不变。 2. 改为 factories 并重新生成,会补 `dependencies.go` 和 `*Factory.go`,保留原来的手写文件。 3. 将旧实现迁入工厂,提供 Dependencies 的访问器;移除 Init 中按名字覆盖 map 的代码。 4. 旧函数不再需要时由项目删除。若新工厂符号与旧文件中的同名函数冲突,整理旧文件;生成器不覆盖业务代码。 5. `generate --check` 与 `go test ./...` 检查后再运行。工厂没有全部实现前,服务不会启动。 ## 文件维护边界 | 项目维护 | 生成器持续维护 | | --- | --- | | `.api` 公共字段与接口 | 展开后的 DTO、Handler、OpenAPI | | `omni.gen.json` 的 Repository/中间件选项 | 对应骨架和注册代码;已有手写骨架不覆盖 | | 独立 Go 业务类型、scalar、可选 copyx 调用和测试 | 不生成结构转换文件 | | middleware Dependencies、具名工厂、svc 访问器 | 中间件 registry、启动调用 | | Logic、Repository、配置、svc 初始化/清理登记 | main、runtime、路由接入 | 独立三端工资契约测试位于 `cmd/go-omni/apigen/testdata/composition`,不导入任何接入项目。它验证公共字段、Money scalar、三端 Handler、copyx、重新生成保留业务内容,以及新增字段不需要更新转换规则。 ```sh GO_OMNI_SCAFFOLD_INTEGRATION=1 go test ./cmd/go-omni/apigen -run TestCopyxThreeEndpointConsumer -count=1 ```