GoOmni

GoOmni 是面向多个业务项目复用的 Go 后端框架:用 .api 定义接口、请求和返回类型,生成项目目录、HTTP 接入代码及 OpenAPI,业务代码负责 Service 和 Repository。也可以在已有项目中直接使用 omni.New(),或单独引入日志、存储等模块。

从这份 README 开始即可接入。 当前版本为 v0.9.0-preview,Go 1.22+;CLI 和项目依赖使用同一固定版本。框架不依赖 Gin,默认使用标准库网络层,提供可切换的 native HTTP/1.1 和实验性 HTTP/2 适配器。

响应字段、业务码、登录方式、权限规则和数据库表结构由接入项目决定。文中的 code/msg/data、200/9999 都是示例配置,可以替换。

选择接入方式

你的项目 从哪里开始
新建服务,先定义接口再开发 新项目:生成并运行
已有服务,逐步接入 HTTP 或基础模块 已有项目:按模块接入
已有标准 Handler、动态 JSON、上传或自定义 ID 类型 迁移扩展指南
想统一返回体、业务错误或三方回调 配置公共响应与独立协议
要接日志、数据库、鉴权和存储 业务开发与依赖装配
要切换网络层、配置 TLS 和退出清理 配置与运行
已接入,需要升级和检查生成代码 日常开发与升级

准备环境与安装

module 地址是 git.yuntuo.hk/k2safe/go-omni。先确保本机 Go 和 Git 可用,并具有仓库读取权限;使用自己的 Git 凭据管理器,不把账号或 Token 写进 module 地址。

以下命令在同一个终端执行。已有 GOPRIVATE 时,把 git.yuntuo.hk 追加到原来的逗号分隔列表。

export GOPRIVATE=git.yuntuo.hk
go install git.yuntuo.hk/k2safe/go-omni/cmd/go-omni@v0.9.0-preview
export PATH="$(go env GOPATH)/bin:$PATH"
go-omni --help

如果配置了自定义 GOBIN,将那个目录加入 PATH。只引用框架包、不生成项目时可以跳过 CLI 安装,直接使用下文的 go get。

新项目:生成并运行

在准备存放业务项目的目录执行,./hello 必须是新目录或空目录;把 example.com/hello 换成自己的 Go module 名称。

go-omni api new --framework-version v0.9.0-preview \
  --module example.com/hello ./hello
cd hello
go mod tidy
go run ./cmd/server

另开终端验证:

curl -i http://127.0.0.1:8080/api/greet/Alice
curl -i http://127.0.0.1:8080/health/ready
curl -sS http://127.0.0.1:8080/openapi.json

第一个接口返回 HTTP 200,业务数据为 {"message":"Hello Alice"}。初始模板未配置响应格式,使用默认公共体:

{"code":"OK","message":"success","data":{"message":"Hello Alice"},"requestId":"本次请求编号"}

按 Ctrl+C 停止服务。项目依赖已固定为远程版本,不需要克隆框架源码或添加本地 replace。

生成的项目放什么

hello/
├── api/service.api                  # 维护接口定义,也可 import 其他 .api
├── cmd/server/main.go               # 启动、路由注册、优雅退出
├── etc/config.yaml                  # 项目配置
├── internal/
│   ├── settings/                    # 配置读取
│   ├── servicecontext/              # 数据库、日志、外部客户端等依赖
│   ├── types/typesGen.go            # 生成的请求与业务返回类型
│   ├── handler/                     # 生成的绑定、响应策略、路由及文档
│   ├── service/greet/               # 在这里实现业务逻辑
│   ├── repository/greet/            # 在这里实现数据访问
│   └── middleware/                  # 声明鉴权等中间件后生成对应 hook
├── docs/openapi.json                # 生成的 OpenAPI 3.0.3
└── .goOmni/apiManifest.json         # 生成文件所有权与校验信息,一起提交 Git

修改 api/service.api 后重新生成,业务写在 internal/service,数据访问写在 internal/repository,依赖在 internal/servicecontext 装配。声明业务错误后还会生成 internal/apierrors/errorsGen.go。

api new 的问候接口可以直接运行;自定义接口新生成的 Service 和鉴权 hook 返回 501,需要实现后才可用。生成器不会根据 HTTP 字段猜数据库表或业务规则。

从自己的接口定义生成

保存下面的完整示例为 catalog.api,再执行后面的生成命令。这里的响应格式、错误码和提示都由这个业务项目定义。

syntax = "v1"

response CommonResponse (
    body: {"code":"${code}", "msg":"${message}", "data":"${data}"}
    codeType: int32
    successCode: 200
    errorCode: 9999
    successMessage: "操作成功"
    dataMode: always
    codes: {"ITEM_NOT_FOUND":404001}
)

error ItemNotFound 404 "ITEM_NOT_FOUND" "记录不存在"

type GetItemReq {
    ItemID string `path:"itemId" binding:"required"`
}
type ItemResp {
    ItemID string `json:"itemId"`
    Name string `json:"name"`
}

@server (
    group: item
    prefix: /api/v1
    response: CommonResponse
)
service catalog-api {
    @doc "查询商品"
    @errors ItemNotFound
    @handler getItem
    get /items/:itemId (GetItemReq) returns (ItemResp)
}
go-omni api validate --api catalog.api
go-omni new --api catalog.api --framework-version v0.9.0-preview \
  --module example.com/catalog ./catalog
cd catalog
go mod tidy

将生成的 internal/service/item/getItem.go 替换为下面的演示实现。实际业务中换成 Repository 查询,Service 返回业务数据或错误,不再手工包装公共体:

package item

import (
    "context"

    "example.com/catalog/internal/apierrors"
    "example.com/catalog/internal/types"
)

func (s *Service) GetItem(ctx context.Context, req *types.GetItemReq) (*types.ItemResp, error) {
    if req.ItemID != "1001" {
        return nil, apierrors.ItemNotFound()
    }
    return &types.ItemResp{ItemID: req.ItemID, Name: "示例商品"}, nil
}

示例 import 对应上面的 example.com/catalog,使用其他 module 名称时同步替换。启动后,在另一个终端验证:

# catalog 项目目录中启动;先停掉前面的 hello 服务,释放 8080 端口。
go run ./cmd/server
# 另一个终端:
curl -i http://127.0.0.1:8080/api/v1/items/1001
curl -i http://127.0.0.1:8080/api/v1/items/missing

成功时得到 {"code":200,"msg":"操作成功","data":{"itemId":"1001","name":"示例商品"}};不存在时为 HTTP 404、{"code":404001,"msg":"记录不存在","data":null}。

需要 MySQL、文件存储或文件日志时,首次生成可以使用:

go-omni new --api catalog.api --framework-version v0.9.0-preview \
  --module example.com/catalogFull --database mysql --storage local \
  --log-output file --log-format json ./catalogFull

该命令在 catalog.api 所在目录执行,目标仍需是新目录。数据库迁移、表结构和 Repository 由项目维护,API 模板不自动建表;MySQL DSN 通过 GO_OMNI_DB_DSN 提供。存储还可选 s3、r2、oss,按生成项目的配置说明提供桶、端点和凭据。完整装配例子见 userService。

不采用 .api 时,也可以用 go-omni new --framework-version v0.9.0-preview --module example.com/billing ./billing 创建手写接口的 CRUD 示例项目;两种生成方式选择一种即可。

已有项目:按模块接入

在业务项目的 go.mod 所在目录执行:

go get git.yuntuo.hk/k2safe/go-omni@v0.9.0-preview

已有项目可直接复用现有目录、Service、Repository 和数据库连接,先选择一个接口接入。也可以只用 logx、storage 等包,暂时保留原来的 HTTP 框架。

下面是可独立运行的 main.go 示例;迁入现有项目时,把路由调用接到自己的业务服务即可。

package main

import (
    "log"
    "net/http"

    omni "git.yuntuo.hk/k2safe/go-omni"
    "git.yuntuo.hk/k2safe/go-omni/logx"
    "git.yuntuo.hk/k2safe/go-omni/response"
)

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    closeLog, err := logx.Setup(logx.Config{
        Service: "catalog", Level: "info", Format: "json", Output: "stdout",
    })
    if err != nil {
        return err
    }
    defer closeLog()

    app := omni.New(
        omni.WithLogger(true),
        omni.WithResponse(response.Config{
            Body: map[string]any{
                "code": "${code}", "msg": "${message}", "data": "${data}",
            },
            CodeType: "int32", SuccessCode: 200, ErrorCode: 9999,
            SuccessMessage: "操作成功", DataMode: "always",
            Codes: map[string]any{"ITEM_NOT_FOUND": 404001},
        }),
    )
    app.GET("/api/v1/items/:itemId", func(c *omni.Context) error {
        itemID := c.Param("itemId")
        logx.InfoContext(c.Context(), "item.get", "itemId", itemID)
        if itemID != "1001" {
            return omni.NotFound("ITEM_NOT_FOUND", "记录不存在")
        }
        return c.OK(map[string]string{"itemId": itemID, "name": "示例商品"})
    })
    // 第三方协议可明确输出文本,不经过公共 JSON 包装。
    app.GET("/ping", func(c *omni.Context) error {
        return c.Text(http.StatusOK, "pong")
    })
    return app.Run("127.0.0.1:8080")
}

运行 go run .,访问 /api/v1/items/1001、/api/v1/items/missing 和 /ping,可分别验证成功、业务错误及纯文本输出。App 实现标准 http.Handler,也可挂进现有 http.Server 或父路由;这时监听、TLS、信号和资源关闭由现有入口负责,结束时调用 app.Close()。

现有 http.HandlerFunc 可直接通过 app.POST("/legacy", httpx.WrapHandler(http.HandlerFunc(oldHandler))) 接入;状态码与访问日志一致,下载和流式输出保持原协议。标准中间件、动态 JSON、上传/原文 body、手写绑定和自定义类型的完整用法见 迁移扩展指南。

接入现有业务时,逐项确认:

  • 协议:保留既有 URL、HTTP 状态、字段、分页和空值语义。已有业务错误类型需要在项目内转换为 Omni 错误,或使用 WithResponseEncoder 适配原错误;设置字段模板不会自动识别其他库的错误。
  • 鉴权:把现有登录态和权限校验接入中间件,继续使用原来的授权范围和业务规则。原来依赖 gin.Context 等类型的 HTTP 代码需要适配;标准 context.Context 的业务服务可以继续复用。
  • 数据类型:ID、金额、日期等按项目协议转换。用 .api 的 scalar 显式关联项目自己的类型与 JSON/Text 转换方法,分别声明输入和输出协议;普通字段保持严格绑定,见 自定义标量。
  • 生成器:它不会自动接管任意已有 Go 项目。希望引入 .api 时,先在独立新目录生成对照,再规划路由与生成目录的接入,不要用脚手架覆盖现有业务目录。

先通过一个接口的成功、参数错误、未登录、无权限及业务错误回归,再按模块迁移。框架不要求所有项目使用同一套业务目录或身份模型。

配置公共响应与独立协议

接口来源或需求 配置位置
.api 生成的接口 response Name (...) 定义,@server (response: Name) 引用
多个 .api 共用返回体 将声明放到 common.api,使用 import "common.api"
某个生成接口用另一种 JSON 公共体 路由声明 @response OtherResponse
只要业务 JSON、无成功包装 路由声明 @response raw;失败仍沿用组级格式
手写路由和应用级 404/405 omni.WithResponse(response.Config{...}),或读取配置后传入 WithConfig
成功和失败都要遵循特殊回调协议 HandleWithResponse 绑定项目自己的 response.Encoder
单次直接输出 JSON、文本、XML、字节或空响应 c.JSON / c.Text / c.XML / c.Bytes / c.NoContent

${data} 是业务返回值,${message} 是公开提示,${code} 是业务码;占位符保留原值类型,不是字符串拼接。还支持 ${requestId}、${ok}、${status},字段名和嵌套结构由项目决定。

  • codeType 可以是 string、int32、int64;整数类型需要配置 successCode 和 errorCode。
  • codes 把逻辑错误码映射成对外业务码;未单独映射的失败使用 errorCode。JSON 业务码不会改变 HTTP 状态,例如 404001 仍对应 HTTP 404。
  • successMessage 设置成功提示;未设置 errorMessage 时沿用错误的公开提示。messages 可按逻辑错误码单独覆盖,优先级最高。全局 errorMessage 会替换未单独映射的所有失败提示,只有需要统一失败文案时才设置。
  • dataMode: always 在无数据时输出 null;默认 omitNil 省略模板里相应对象字段。没有 returns 的 .api 路由默认 HTTP 204、无响应体,这与 200 + data:null 是不同的接口契约。

生成接口的格式以 .api 为准,修改后重新生成,运行时与 OpenAPI 一起更新;应用 YAML 不会覆盖它。404/405 没有匹配到某个 .api 接口,使用应用默认配置,需要统一时也配置应用默认值。

文本/XML 回调用 Go 编码器或直接输出,.api 可用 @handlerMode manual 与 @produces 声明成功媒体类型;特殊错误协议仍需项目维护。@response raw 仅表示原始 JSON。前面示例的 c.Text 控制这次正常返回;需要连鉴权失败、业务错误和 panic 都按回调协议响应时使用 HandleWithResponse。完整配置和回调示例见 响应参考。

业务开发与依赖装配

日志

业务包直接使用 logx,不需要 HTTP 框架对象:

// ctx 是 Service 接收的标准 context.Context。
logx.InfoContext(ctx, "order.created", "orderId", orderID)
logx.ErrorContext(ctx, "order.failed", "error", err)

入口用 logx.Setup 初始化,退出时关闭;生成的 API 项目已经完成日志初始化和退出清理。Service、Repository 和外部请求持续传递请求的 context.Context,业务日志可关联 requestId,同时保留取消信号。框架不规定或自动注入 userId、tenantId。

WithLogger(true) 开启 HTTP 访问日志。格式、级别、文件轮转、脱敏键和慢请求阈值均可配置,见 日志。

鉴权与业务错误

.api 可声明认证方式和关联中间件,例如把下面的声明放入契约,并在相应 @server 块中加上 auth: UserAuth:

auth UserAuth (
    type: bearer
    middleware: Auth
)

生成器会创建 internal/middleware/auth.go,由项目实现凭据校验和身份注入。声明会生成路由绑定与 OpenAPI 的认证描述,不会自动实现登录或授权规则;未实现的 hook 返回 501。手写路由可使用 auth.RequireAuth(authenticator) 或自己的中间件。JWT、Session 和 Policy 按需选择。

业务错误在 .api 中用 error 声明,通过 @errors 关联路由后,生成可调用的错误函数和 OpenAPI 中对应的 401、403、409 等响应。手写路由可以直接返回 omni.NotFound、omni.Forbidden 等错误。已有项目在自己的适配层转换原错误,内部原因和公开提示分开处理。

数据库、存储及其他模块

依赖在启动入口或项目自己的 internal/servicecontext 中创建,通过构造函数传给 Service/Repository,避免每个请求都新建连接池。只注册自己拥有的资源,例如 app.OnShutdown(db.Close);关闭按注册的逆序执行。

能力 包与接入说明
数据库连接池、事务、显式迁移 database,见 数据库;表结构与驱动由项目选择
本地文件、S3、R2、OSS、直传与临时下载 storage,见 文件存储;业务依赖接口,装配时选择适配器
缓存、出站 HTTP、限流、幂等、后台任务 cache、httpc、middleware、task,见 基础模块
JWT/Session、策略、Redis 队列、审计、分页、观测 见 扩展模块;按需注入实现
存活与就绪检查 health,见 健康检查;数据库等依赖作为 readiness 探针

API 项目的数据库迁移需要单独维护并显式执行;启动不会自动改表。完整示例 userService 展示 MySQL、事务、鉴权、日志和文件导出,可参考依赖装配方式后接入自己的业务规则。

配置与运行

生成项目从 etc/config.yaml 读取配置,优先级为 默认值 → 配置文件 → 环境变量。密钥通过环境变量提供。手写入口需显式调用配置加载函数并传给 omni.New(omni.WithConfig(settings));单独调用 omni.New() 不会自动读取环境变量。

环境变量 用途
GO_OMNI_HTTP_ADDR 监听地址;生成项目默认 127.0.0.1:8080
GO_OMNI_TRANSPORT nethttp(默认)、native、http2
GO_OMNI_TLS_CERT_FILE / GO_OMNI_TLS_KEY_FILE 成对设置 TLS 证书和私钥路径
GO_OMNI_SHUTDOWN_TIMEOUT 优雅退出等待时间,默认 10s
GO_OMNI_ENABLE_LOGGER / GO_OMNI_LOG_LEVEL 访问日志开关、日志级别
GO_OMNI_TRUSTED_PROXIES 可解析转发地址头的可信代理 IP/CIDR,逗号分隔
GO_OMNI_DB_DSN 选择 MySQL 初始化时的数据库连接配置

例如在生成项目中切换 native:

GO_OMNI_TRANSPORT=native go run ./cmd/server

手写入口可用 omni.WithTransport(omni.TransportNative);TLS 配置为 omni.WithTLS(certFile, keyFile)。native 提供 HTTP/1.1、Keep-Alive、TLS、超时、连接/请求并发上限和对象复用;HTTP/2 是单独的实验适配器,必须配置 TLS。默认 nethttp 的 TLS 支持 HTTP/1.1 与 HTTP/2。

自研网络层通过 RunWith(ctx, runner) 接入监听与连接生命周期,通过 NewWithRouter(router) 替换路由;业务 Handler/Service 不需要导入具体适配器。细节与验收契约见 网络适配层。

Run 处理 SIGINT/SIGTERM;RunContext 接收调用方控制的退出信号。服务先停止接收新请求,等待活跃请求完成,再清理资源;超时会取消请求并关闭连接,业务代码仍需响应 context 取消。

日常开发与升级

在已生成的 API 项目目录执行:

go-omni api validate
go-omni api inspect
go-omni api generate --dry-run
go-omni api generate
go-omni api generate --check
go test ./...

Gen.go、docs/openapi.json 和 manifest 由生成器管理,手动修改会报告冲突。Service、Repository、中间件实现、main、配置和依赖装配属于项目,重复生成保留手写内容;契约改变后仍需自行调整业务签名并通过编译。将 .api、生成文件和 .goOmni/apiManifest.json 一起提交,CI 可运行 generate --check 检查是否同步。

升级时先安装目标版本的 CLI,再在业务 module 中 go get 同一版本;API 项目随后执行上述生成与测试步骤,手写项目只需依赖升级及业务回归。v0.9.0-preview 的变化、迁移边界和验收记录见 发布说明。

按需深入

需要了解 文档
.api 类型、参数绑定、错误、鉴权和生成规则 API 开发指南
公共响应体、提示优先级、独立回调协议 响应参考
CLI 完整参数、本地框架开发和模块脚手架 CLI
完整 API 业务项目 userService
手写接口分层示例 reference
框架成熟度、历史变更 路线图、更新记录
网络性能、测试条件与适用范围 性能说明、HTTP/2 验收

当前版本可用于固定版本接入与业务验证,仍标记为 preview。.api 借鉴 go-zero 的接口开发方式,支持范围以本仓库文档为准,不是完整 goctl 语法兼容层;HTTP/2 保持实验状态,OSS/R2 云账号实网验收不属于本次发布范围。

Description
No description provided
Readme 6.6 MiB
Languages
Go 88.2%
Python 10.6%
JavaScript 1.1%