19 KiB
生成项目与文件维护边界
v0.14.0-preview 提供受管理的生成项目。安装同版远程 CLI,生成的项目直接引用该版本,无需本地 replace。根目录入口支持 go run . 和 go build .;框架本身仍然是可直接引用的库。安装和私有仓库访问见 README。
创建和日常开发
# 在存放业务项目的目录执行,目标目录必须为空。
go-omni new --module example.com/hello ./hello
cd hello
go mod tidy
go run .
go build .
在项目目录使用同版 CLI:编辑 api/service.api,运行 go-omni api generate,填写 internal/logic 的业务方法。增加接口、分组或具名中间件会自动更新接入代码,无需修改入口。--dry-run 预览;--check 只读检查,包括 main、HTTP 接入和 OpenAPI。API 文件通过相对 import 拆分模块。
契约变更提示
生成器在 manifest 中保存上次成功生成的接口快照,比较请求/返回类型、嵌套 DTO 字段与标签、自定义 scalar、路径、body 模式/限额和流式输出类型。只提示受影响的接口,并标出业务文件;删除或移动接口会提醒整理保留的旧实现。注释、文件排版、Logic 参数名与导入别名不会被当成接口变化。
例如把 GreetReq 改名为 HelloReq,旧业务方法尚未修改时:
ERROR [logicSignature] internal/logic/greet/greet.go:12 (GET /api/greet/:name): ...
before: Greet(context.Context, *types.GreetReq) (*types.GreetResp, error)
after: Greet(context.Context, *types.HelloReq) (*types.GreetResp, error)
| 命令 | 变更提示与检查行为 |
|---|---|
api generate --dry-run |
显示计划、契约变化和业务签名问题,不写文件;诊断本身不让预览失败 |
api generate |
更新生成文件和契约快照,保留业务代码,并显示需要处理的诊断;生成成功不代表业务已通过编译 |
api generate --check |
只读检查;生成产物过期或发现明确的 Logic 签名/语法/重复方法错误时返回非零退出码 |
api validate |
只验证 .api,不检查项目 Go 实现 |
REVIEW 提醒复查业务语义;ERROR 表示已发现源代码问题。生成后,旧签名仍会在后续 --check 中报错,直到你改好;DTO 字段变化的 REVIEW 与上次生成快照比较,生成后不持续要求确认,也不会替你修改业务。业务方法移到同组其他 Go 文件时仍能定位。
这是轻量源码分析,不运行项目 init、不执行 Go 命令、不下载依赖。项目自定义类型别名、嵌入方法或 build tags 等无法可靠判断时,会给 REVIEW 并交给 Go 编译器验证;不把这些情况强行判为签名错误。它不检查方法体是否引用已删除字段,也不证明业务逻辑正确。CI 中应同时执行 go-omni api generate --check 和 go test ./...,按项目实际构建参数测试。当前版本首次接手缺少快照的 manifest v2 时,会明确提示建立基线,不猜测旧契约;Logic 签名检查仍会执行。
哪些文件由用户维护
| 文件 | 首次生成后由项目维护 |
|---|---|
api/*.api |
路由、DTO、具名中间件、响应模板、请求/响应模式 |
etc/config.yaml |
运行参数;密钥通过环境变量提供 |
internal/config/config.go |
自定义配置字段、加载、默认值与校验 |
internal/svc/serviceContext.go |
依赖字段和初始化,持有加载后的 Config |
internal/svc/options.go |
AppOptions、GlobalMiddleware;注册 Go 错误目录和全局扩展 |
internal/svc/databaseOptions.go |
可选 MySQL 驱动与项目启动检查扩展 |
internal/svc/storage.go |
选用存储时生成,之后保留 |
internal/logic/<group>/*.go |
业务方法,使用标准 context.Context |
internal/repository/<group>/*.go |
数据访问,按需实现 |
internal/middleware/<name>.go |
项目鉴权/权限等具名中间件实现 |
| 项目 Go 错误包 | 自行维护 Catalog 和常量,可跨服务共用 |
自定义标量文件、*Bind.go、*Manual.go |
只有显式选择相应扩展时才生成/使用 |
| 项目测试、迁移、部署脚本、go.mod/go.sum | 项目所有,生成器不重置其内容 |
以上骨架只补缺失文件,既有内容按字节保留。删除已废弃接口后,对应业务文件也不会被自动删除;由开发者整理编译器指出的失效类型或签名。
生成器持续管理:internal/svc/runtimeGen.go、main.go、internal/handler/*Gen.go、分组的 handlersGen.go、internal/types/*Gen.go、中间件 registryGen.go、docs/openapi.json、.goOmni/apiManifest.json。如果在 .api 里选择声明错误,还会管理 internal/apierrors/errorsGen.go。文件标注 DO NOT EDIT;手改会被拒绝覆盖,需先移走自定义内容。
Logic 实例在注册时创建并由请求并发复用;请求数据用方法参数和局部变量传递,不存入共享 Logic/svc 字段。
普通 Handler 不需要维护。目录内可以同时有生成文件和单独的手写扩展文件,所有权按文件区分。没有额外 server 包或第二层 service。
依赖与启动扩展
svc.New(ctx, logger, cfg) 初始化依赖,可返回错误;Logger 由入口创建并借用。New 本身由生成器维护;新增客户端时扩展配置和 svc 的 Init(ctx) error:
client, err := newClient(c.Config.Client)
if err != nil { return err }
c.Client = client
c.OnClose(client.Close) // 创建成功后立即登记,后续失败也能释放
ServiceContext 嵌入 *lifecycle.Runtime;框架维护锁、清理栈和失败回滚,项目不再复制 Close/OnClose 实现。OnClose 按登记的相反顺序执行;Close 幂等。项目不要重复关闭同一资源,借用的 Logger 也不应由 svc 关闭。构造器在返回错误前若创建了尚未交付的资源,该构造器自己负责释放。
错误注册写在 internal/svc/options.go,错误表可以来自任意项目共用包:
func (c *ServiceContext) AppOptions() []omni.Option {
return []omni.Option{omni.WithErrors(apierrors.Catalog())}
}
业务调用 omnierrors.ByCode(apierrors.CodeUsernameInvalid),可接 WithParams / WithCause。无需在 .api 重复错误目录。未声明独立错误目录的路由继承 App 目录;独立目录不与 App 合并。静态 OpenAPI 不执行 Go;可从同一 Catalog 显式导出 api/omni.errors.json,详见 元数据导出。
GlobalMiddleware() 提供全局中间件;Middlewares 映射提供 .api 声明的具名中间件。全部显式调用,不依赖项目 init 注册,也不要求 svc 导入 handler。框架请求链按原有次序运行。
生成入口执行:配置加载/校验 → 日志 → svc.New → 生成的 handler.NewApp(应用 AppOptions、响应策略、校验最终 HTTP 配置、全局中间件、全部路由)→ App.RunContext → 依赖关闭 → 日志关闭。初始化失败、接入配置失败和监听失败也清理资源。
最终配置校验包含自定义响应编码器与错误目录的组合。码类型/映射冲突、编码器已绑定另一份目录等问题在创建 App 前返回错误,进入入口的清理流程,不等到构造 App 时才 panic。
日志在 AppOptions 前初始化,其配置应放在 Config 中。退出信号先触发 HTTP 排空;达到 shutdownTimeout 后按框架规则强制关闭连接。请求仍依赖的后台任务/客户端不能直接绑定信号 context 提前关闭,应持有独立生命周期,在 OnClose 中取消。业务必须响应请求 context 取消;忽略取消的 goroutine 无法保证在超时前结束。
依赖主动申请退出
从 v0.13.0-preview 起,生成的 svc 提供 RequestShutdown(err)。数据库客户端、节点租约、后台任务等发现不可恢复故障时,由项目决定是否退出:
// 在 svc 的依赖监控回调中调用;err 是实际故障原因。
c.RequestShutdown(err)
这只是通知,立即返回,不等待 HTTP 排空,也不直接关闭数据库。生成入口停止接收新请求,等待在途请求完成;超过 shutdownTimeout 后取消请求并关闭连接,再执行 svc.Close,最后关闭日志。非空故障原因保留在返回错误链中,进程退出码为 1;RequestShutdown(nil) 或正常 SIGINT/SIGTERM 在没有其他故障时退出码为 0。
多个依赖可并发通知,关闭流程仅执行一次,首个非空故障原因保留。正常信号已开始排空时收到的故障也不会被清理动作覆盖。初始化期间的通知同样有效,不依赖 HTTP 监听已经启动。业务应在 Close/OnClose 中停止并等待自己启动的监控任务,再结束资源清理;关闭完成以后才报告的故障无法参与已经结束的进程结果。
Lifecycle *lifecycle.Controller 是 svc 持有的通用控制器,初始化时创建,生成入口使用它的 Context。该 Context 表示服务正在停止,不代表数据库已经关闭。不能把它当作在途请求的 Context,也不要在它取消时立刻关闭请求仍依赖的资源。发号器失去租约后拒绝发号等业务安全规则仍由项目实现。
配置内置接口
生成项目在 etc/config.yaml 明确列出以下默认配置;可以独立关闭、修改路径,或让健康接口返回原始 JSON:
server:
endpoints:
liveness:
enabled: true
path: /health/live
responseMode: app
readiness:
enabled: true
path: /health/ready
responseMode: app
openapi:
enabled: true
path: /openapi.json
responseMode: app 使用项目 App 的响应模板/自定义 Encoder。健康成功使用项目成功码和成功提示;未就绪的 NOT_READY 可在项目 Go 错误目录配置业务码和公开提示,无需在 .api 再声明。
健康探针的 HTTP 状态固定遵循探针语义:存活/就绪为 200,未就绪为 503;项目错误目录或自定义编码器不能把未就绪改为 HTTP 200。响应模板 ${status} 同样使用探针状态。失败继续进入业务失败日志/指标,探针底层原因保留在错误链供日志使用,不直接返回给调用方。
responseMode: raw 返回 {"status":"ready"} 等健康数据,有依赖时包含检查名称、结果与耗时;不包装公共体。存活检查不执行依赖探针;服务开始退出后,就绪检查返回 503,存活仍可返回 200,直到监听关闭。OpenAPI 始终输出规范文档本身,不使用业务响应包装;关闭 HTTP 文档端点不影响生成 docs/openapi.json。
环境变量覆盖配置文件,例如 GO_OMNI_ENDPOINTS_READINESS_PATH=/readyz、GO_OMNI_ENDPOINTS_READINESS_RESPONSE_MODE=raw、GO_OMNI_ENDPOINTS_OPENAPI_ENABLED=false;liveness/readiness/openapi 都支持对应的 ENABLED 和 PATH。Config/AppOptions 的最终有效配置决定是否注册。
关闭或改名后,旧路径不再由框架保留,项目可以在 .api 自己声明。启用的内置 GET 路由与业务路由相同或被业务参数/通配路径覆盖时,在创建 App、开始监听之前报错;不会覆盖业务路由。内置接口仍经过全局中间件;项目在鉴权中间件中明确决定这些路径的访问规则,框架不自动绕过鉴权。
从 v0.13 或更早版本升级
请按 v0.14 定点迁移说明 使用 api upgrade。原始模板可以自动升级;含业务修改的基础文件必须参考局部预览合并。旧 New/Close/OnClose 移到框架 Runtime,项目依赖初始化保留在 Init。不要继续向项目里复制生命周期锁和清理栈。
App 默认响应格式
在 入口 .api 声明一次:
@app (
defaultResponse: CommonResponse
)
response CommonResponse (
codeType: int32
successCode: 0
errorCode: 9999
messageField: msg
dataMode: always
)
优先级:路由 @response → 分组 @server(response: ...) → App 默认。模板可以定义在 import 文件里,@app 只能在入口声明。重复声明、引用不存在的模板会报错;不会按文件顺序猜默认值。未声明 @app 时使用框架现有默认格式。
生成的 App 普通路由、404/405、未单独指定格式的生成路由共用默认模板。已匹配路由的中间件提前报错也使用路由策略。@response raw 的成功 JSON 不包装,错误沿用有效模板。
显式 .api 默认响应优先于 Config/AppOptions 中的响应配置,保持接口文档和实际响应一致。其他 AppOptions 继续生效;要改变格式就修改 .api。项目无需访问 handler 私有变量。
自动上传、回调与下载
以下声明生成完整 Handler,用户填写 Logic:
@handler upload
@body (mode: multipart; maxBytes: 33554432)
post /upload returns (UploadResp)
@handler callback
@body (mode: raw; maxBytes: 65536)
@produces "text/plain"
post /callback
@handler download
@produces "application/octet-stream"
get /download (DownloadReq)
- 以下为未声明 multipart 字段的流式用法;声明式绑定见 上传字段。raw/流式 multipart 的 Logic 增加
body *httpx.Body参数。body.Raw是受路由上限约束的原始字节流;body.Header是请求头副本。 - multipart 用
body.Parts.NextPart()顺序读取,关闭每个 Part;不调用 ParseMultipartForm,不产生上传临时文件,也不把整个文件放入内存。每个字段/文件的业务要求由 Logic 校验。请求 DTO 可定义 path/form/header;form仍是 URL 查询参数,不能用 json DTO 重复读取上传体。 - Parts 是
*httpx.MultipartReader,返回的*httpx.MultipartPart提供 Read、Close、Header、FormName、FileName;NextRawPart 可读取未做 quoted-printable 解码的部分。NextPart 自动关闭上一个未读完的 Part;Close 会报告读取错误。不要混用 Parts 与 Raw 读取同一个 multipart 请求。 - multipart 缺少结束边界、头部/内容损坏默认返回
400 / INVALID_MULTIPART;超过路由限额仍为 413,媒体类型/缺少 boundary 为 415。框架只提供通用标识和默认提示,项目可以在 Go 错误目录中配置自己的状态、业务码和文案。内部解析原因保留在错误链,不进入公开响应;Logic 自己返回的数据库等错误不会被统一改成 400。 - raw 数据不经过 JSON 解码。签名算法、密钥及回执内容由业务提供;同一个流只消费一次。若中间件先读取验签,项目必须显式使用受限缓冲/重放,生成器不会自动缓存大文件。
@produces使 Logic 返回(*httpx.Stream, error),其中 Content 是io.ReadCloser,可选 Name 生成附件下载头。ContentType 可携带 charset,但必须与契约媒体类型匹配。Handler 负责关闭内容流,无公共体包装;写出后发生错误不会追加 JSON。- Handler 在成功响应前检查剩余请求流,包含未读完的 multipart 部分、最终边界和请求体上限;已经遇到的 multipart 读取错误不会因业务忽略它而变成成功。读取失败和 413 不能撤回 Logic 已产生的外部副作用;重要业务应先调用
body.Complete()确认完整读取/验证,再提交事务或发布文件。Complete 可重复调用。 - 自动 Handler 采用先完成输入、再发送输出的顺序;不要把正在读取的 body.Raw 直接当作 Stream.Content 返回。需要同时读写请求/响应的协议时使用手写扩展。
- 大文件的耗时仍受 HTTP 读写超时约束。按接口需要配置,框架不自动无限延长。
默认请求体上限为 1 MiB,按路由设置 maxBytes,不能通过统一放大全局限制替代。@produces 仅声明媒体类型,不保证业务返回的内容符合该媒体的语义。Range、SSE 刷新、特殊协议等需要时仍可显式选 @handlerMode manual;它是可选扩展,不是普通上传下载的前置条件。
旧结构处理
新生成器使用 manifest v2,不兼容原 settings/servicecontext/service 与 cmd/server 布局。已有旧目录会报错,不能通过覆盖 main 自动迁移。试验代码需保留时,在新目录生成,移动 .api、Logic、配置和 svc 扩展并调整导入;独立的 cmd/migrate 等工具由项目维护。不会静默覆盖手写 main 或删除无关命令。
go-omni new 与 api new 统一使用此结构。受管理项目通过 .api 增加模块,旧的 add 手动模块装配方式不适用于该结构。直接引用 go-omni 框架包的项目不受这些目录约定约束。
已有服务接入步骤
每个独立服务生成一个项目,module 名称、上层目录和共享业务包由项目选择。接入时固定 CLI 与框架版本为 v0.14.0-preview:
- 先保留旧服务,整理实际路由及现有响应、鉴权和超时要求。在新目录执行
go-omni new --api /path/to/service.api --module example.com/service ./service;不对旧入口强制覆盖。 - 在
.api中选择@app(defaultResponse: ...),显式保留原项目的字段、成功码、错误码和dataMode。需要独立回执的路由单独声明 raw/produces。普通路由、404/405 和提前返回的中间件一起验收。 - 把配置放入
internal/config和etc/config.yaml;数据库、自定义 Connector、Redis、ID Client 等依赖放入internal/svc,初始化成功后立即OnClose。现有事务和迁移工具继续由项目维护;数据库扩展见 database。 - 业务错误目录只在项目 Go 包维护,通过
svc.AppOptions()返回omni.WithErrors(apierrors.Catalog())注册一次。需要继承 App 目录的路由不要重复声明独立错误目录。项目鉴权放入中间件,业务实现迁入 Logic/Repository;无需修改 main 或生成的 Handler。 - 上传、原始请求体验签和下载优先使用自动 Handler;只有特殊协议才选择手写扩展。保留实际验签算法、流式处理、大小限制和存储权限,按 迁移扩展 处理旧 Handler 与自定义标量。
- 每个服务运行下面的检查,再用实际配置验证依赖连通、启动失败清理和正常退出。业务侧补充原有接口兼容、鉴权、事务回滚与实际上传下载回归;框架测试不能代替这些业务验收。
go mod tidy
go-omni api validate
go-omni api generate --check
go test ./...
go build .
go run .
后续增加接口修改 .api 并重新生成;增加依赖修改配置和 svc。将契约、生成文件、manifest 与业务代码一起提交。完整流程不需要项目另外维护启动器,也不需要读取 handler 的私有变量。