Files
go-omni/PROJECT.md

19 KiB
Raw Blame History

生成项目与文件维护边界

v0.22.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 签名检查仍会执行。

生成选项、公共字段组合和可选 Go 拷贝工具,详见 生成能力指南。

哪些文件由用户维护

文件 首次生成后由项目维护
api/*.api 路由、DTO、具名中间件、响应模板、请求/响应模式
etc/config.yaml 运行参数;密钥通过环境变量提供
internal/config/config.go 自定义配置字段、默认值与校验
internal/config/loader.go 需要额外文件格式时注册 Decoder
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 数据访问;默认不补空骨架,可选 group 模式
internal/middleware/*Factory.go、dependencies.go 中间件工厂与依赖小接口,svc 实现访问器
omni.gen.json Repository 和中间件生成选项;不再声明结构映射
可选的 Go 转换函数 / copyx 调用 只有独立类型确实需要转换时维护,见 copyx
项目 Go 错误包 自行维护 Catalog 和常量,可跨服务共用
自定义标量文件、*Bind.go、*Manual.go 只有显式选择相应扩展时才生成/使用
项目测试、迁移、部署脚本、go.mod/go.sum 项目所有,生成器不重置其内容

以上骨架只补缺失文件,既有内容按字节保留。删除已废弃接口后,对应业务文件也不会被自动删除;由开发者整理编译器指出的失效类型或签名。

生成器持续管理:internal/config/loadGen.go、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 合并。普通 .api 编译不执行 Go;可在 workspace 显式配置 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 提前关闭,应持有独立生命周期,使用 WorkContext 并登记成对的 OnStop/OnDrain,详见 分阶段关闭。业务必须响应请求 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 监听已经启动。后台监控任务应通过 OnStop/OnDrain 停止并等待,再进入资源清理;关闭完成以后才报告的故障无法参与已经结束的进程结果。

Lifecycle *lifecycle.Controller 是 svc 持有的通用控制器,初始化时创建,生成入口使用它的 Context。该 Context 表示服务正在停止,不代表数据库已经关闭。不能把它当作在途请求的 Context,也不要在它取消时立刻关闭请求仍依赖的资源。发号器失去租约后拒绝发号等业务安全规则仍由项目实现。

配置加载入口 internal/config/loadGen.go 也由生成器维护。业务扩展 Config/Default/Validate;只有新格式才需要修改 NewLoader。JSON/YAML/XML、错误定位和旧 Load 迁移见 配置指南。

配置内置接口

生成项目在 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。

内置路由配置由项目文件明确设置,也可以使用 ${NAME:-fallback} 引用;不再按字段名自动读取环境变量。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.22.0-preview:

  1. 先保留旧服务,整理实际路由及现有响应、鉴权和超时要求。在新目录执行 go-omni new --api /path/to/service.api --module example.com/service ./service;不对旧入口强制覆盖。
  2. 在 .api 中选择 @app(defaultResponse: ...),显式保留原项目的字段、成功码、错误码和 dataMode。需要独立回执的路由单独声明 raw/produces。普通路由、404/405 和提前返回的中间件一起验收。
  3. 把配置放入 internal/config 和 etc/config.yaml;数据库、自定义 Connector、Redis、ID Client 等依赖放入 internal/svc,初始化成功后立即 OnClose。现有事务和迁移工具继续由项目维护;数据库扩展见 database。
  4. 业务错误目录只在项目 Go 包维护,通过 svc.AppOptions() 返回 omni.WithErrors(apierrors.Catalog()) 注册一次。需要继承 App 目录的路由不要重复声明独立错误目录。项目鉴权放入中间件,业务实现迁入 Logic/Repository;无需修改 main 或生成的 Handler。
  5. 上传、原始请求体验签和下载优先使用自动 Handler;只有特殊协议才选择手写扩展。保留实际验签算法、流式处理、大小限制和存储权限,按 迁移扩展 处理旧 Handler 与自定义标量。
  6. 每个服务运行下面的检查,再用实际配置验证依赖连通、启动失败清理和正常退出。业务侧补充原有接口兼容、鉴权、事务回滚与实际上传下载回归;框架测试不能代替这些业务验收。
go mod tidy
go-omni api validate
go-omni api generate --check
go test ./...
go build .
go run .

后续增加接口修改 .api 并重新生成;增加依赖修改配置和 svc。将契约、生成文件、manifest 与业务代码一起提交。完整流程不需要项目另外维护启动器,也不需要读取 handler 的私有变量。