GoOmni

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

从这份 README 开始接入。 当前版本 v0.24.0-preview,新生成项目采用根目录入口和 config / svc / logic / handler。可以固定版本开展业务接入;文件维护边界和已有服务迁移步骤见 项目指南,版本变化与验收见 发布说明。

v0.24 改动:新增可选 CORS,生成项目通过 server.cors 自动接入,支持预检、错误响应和明确的来源白名单;默认关闭。配置与规则、可运行示例。

v0.23 改动:配置校验定位具体字段和安全原因;httpc.JSON 可选限制成功状态;copyx.Check 无需样本预检类型;logx 支持实例独立时区。四项完整示例。

v0.19 改动:新增可选 copyx 工具,用普通 Go 调用完成浅拷贝、深拷贝和显式类型转换;深拷贝会拒绝自定义转换中残留的源对象可变引用。移除了 mappings / mappingFiles 和映射代码生成,已有使用者须按 迁移步骤 调整,不能直接删除仍在调用的旧文件。

普通接口的流程是:修改 .api → 生成 Request/Response → 写 Logic → 测试。有独立职责的业务类型才需要转换;可直接写 Go,也可以按需使用 copyx。公共字段组合、中间件工厂、Repository 开关继续支持,见 生成能力指南。

Go 1.22+。框架不依赖 Gin,默认使用标准库网络层,提供可切换的 native HTTP/1.1 和实验性 HTTP/2 适配器。

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

契约支持 returns([]string/[]int64/[]bool/[]any)、OpenAPI 公共引用、workspace 一次导出共享 Go 错误目录,以及文件显式环境引用与配置检查。详细配置与升级步骤见 业务开发指南。

已有通用开发能力见 业务开发指南:数组/null、严格请求策略、声明式上传、文件显式配置引用、资源生命周期、共享契约和升级工具。MySQL 直接使用 官方适配器。

后台组件可在 svc 登记 分阶段关闭,先停止接收工作并等待收尾,再关闭共享资源;无需绑定队列或 ORM。

使用 GORM 的项目可以选择 gormx 适配器,复用数据库初始化和连接池,并将 SQL 日志接入 logx。

当前功能与使用入口

下表是当前公开能力的索引。每项给出调用入口和完整说明;模块按需使用,omni.New() 不会自动初始化数据库、Redis、ORM、队列或云存储。示例中的 import 统一以 git.yuntuo.hk/k2safe/go-omni/ 开头。

功能与用途 怎么用 解释与示例
项目、接口与文档生成 go-omni new;改 .api 后 api generate;--check 检查漂移 下方快速开始、CLI、完整生成项目
公共字段与跨服务共享契约 .api 组合对象字段;omni.imports.json 声明共享根;workspace 批量生成 公共字段示例、共享契约与 workspace
中间件依赖工厂、可选 Repository omni.gen.json 选择 factories / none;在 New<Name>(deps) 写一次实现 配置与完整工厂示例
路由、分组、请求上下文 omni.New()、app.GET / Group / Use;业务使用 context.Context 基本服务、已有项目接入
HTTP 网络与扩展适配 配置 transport 为 nethttp / native / http2;自定义 Router/Runner 网络层配置与契约、性能与限制
浏览器跨域 CORS server.cors / omni.WithCORS;独立 HTTP 用 cors.New(...).Wrap(handler) 配置、默认值与协议边界、鉴权与预检示例
HTTP 老接口桥接 通过官方 Handler/Middleware 适配器接入标准 net/http,跟踪实际响应状态 接入说明、桥接/上传/自定义 ID 示例
请求校验、动态 JSON、自定义标量 .api 声明来源/校验/scalar;手写契约用 contract.New[T];简单绑定可用 validate.JSON/Query/URI 语法与例子、请求策略
数组、null、原始请求、上传下载 .api 声明数组/可空返回、body/multipart 与大小限制;必要时使用 Bind/Manual 扩展 契约示例、含上传/回调/下载的项目
公共响应与特殊协议 .api 定义 response 并 @app(defaultResponse: ...);Go 用 WithResponse / Encoder 响应配置、响应模板契约
错误目录与动态提示 WithErrors(catalog) 注册一次;ByCode(...).WithParams(...).WithCause(err) 错误目录、模板参数示例
OpenAPI 与错误公共引用 .api 自动生成;手写接口可使用 openapi 注册器;Go 错误目录显式导出 契约工具、注册示例
结构化业务日志 logx.Info/Error、logx.FromContext(ctx)、logx.New;支持独立时区、轮转、脱敏、慢请求和级别更新 下方日志示例、完整配置
文件配置与环境引用 Default()→JSON/YAML/XML 字段引用→类型绑定→校验;无隐式覆盖,支持 -check-config 统一配置指南、完整可运行示例、动态级别
生命周期与健康检查 svc 登记 OnClose/OnStop/OnDrain,RequestShutdown 通知故障;健康路径/开关可配 生命周期用法、健康检查、生成项目扩展
数据库、连接池与事务 database.Open / OpenConnector;事务与检查由项目显式调用 通用数据库、MySQL TLS/Logger
可选 GORM 与迁移 gormx 复用已有连接池;go-omni migrate 预览,显式 apply GORM 示例、迁移用法
认证与权限接口 注入 Authenticator / Policy;JWT 或 Session 只是可选实现 认证说明、鉴权完整例子
缓存 cache.NewMemory 或 Redis 实现;业务面向 Store 的 Get/Set/Delete 缓存配置/代码、可运行模块服务
出站 HTTP、熔断 httpc.New 配置代理、连接池和阶段超时;JSON 支持单次成功状态限制/请求头/限额;resilience.New 隔离故障 HTTP 完整用法、可运行示例、熔断/观测
请求追踪、访问日志、恢复、限流、幂等、超时 app.Use(...) 按需组合 middleware;跨副本选择 Redis 实现 模块说明与限制、分布式实现
本地后台任务与调度 task.New、Submit、Every;有界队列、排空关闭 task 示例与生命周期
持久队列 业务使用 Publisher / Consumer;Redis Streams 提供至少一次交付 队列初始化/消费示例
文件存储、直传、云端分片 Store 的 Put/Get/Delete;Local、S3/R2、OSS 按配置装配;扩展接口提供分片续传 存储接口与完整例子、本地存储服务
日志关联、指标与链路 metrics.New().Middleware();telemetry 注入 OTel exporter/reader 基础指标、OTel 用法、完整接入
审计与分页 audit.New 注入 Sink/白名单;pagination.Parse / NewResult 解释与调用例子
可选结构转换与深拷贝 copyx.Copy / Clone / New;Check[S,T] 预检类型,Go converter 处理差异 下方示例、完整 copyx 指南、可运行代码
自定义适配器验收 transporttest.Run、storagetest.Run / RunUploads 验证扩展契约 网络契约测试入口、存储契约测试入口

浏览器跨域 CORS

同源部署保持默认关闭;需要跨域时,在生成项目的配置文件中加入:

server:
  cors:
    enabled: true
    allowedOrigins: [https://admin.example.com, https://merchant.example.com]
    allowedMethods: [GET, HEAD, POST, PUT, PATCH, DELETE]
    allowedHeaders: [Authorization, Content-Type]
    exposedHeaders: [Content-Disposition, X-Request-ID]
    allowCredentials: false
    maxAge: 10m

不需要修改 main、Logic 或 .api。框架在鉴权前处理合法预检;实际请求仍鉴权,401/404/405/500 保持项目响应格式并附带允许来源的跨域头。没有 Origin 的服务端回调正常处理。配置只决定浏览器读取权限,不替代登录、验签或 CSRF 防护。

手写项目用 omni.WithCORS(omni.CORSConfig{...}),独立 HTTP 服务用 cors.New(...).Wrap(handler);代理已经处理 CORS 时保持关闭。完整 默认值与边界、可运行示例。

带认证的出站 HTTP

HTTP Client 在 svc 初始化一次,业务重复使用。普通内部请求可直接选择直连和连接参数,无需手写 Transport:

client, err := httpc.New(httpc.Config{
    Proxy: httpc.ProxyConfig{Mode: httpc.ProxyDirect},
    Timeout: 3*time.Second, DialTimeout: time.Second,
    TLSHandshakeTimeout: time.Second, ResponseHeaderTimeout: time.Second,
    MaxConnsPerHost: 32, MaxIdleConnsPerHost: 16, MaxIdleConns: 64,
})
if err != nil { return err }
// 在 svc 生命周期登记 client.Close;关闭前先排空使用它的工作。
err = client.JSON(ctx, "GET", endpoint, nil, &result,
    httpc.WithHeader("Authorization", "Bearer "+token),
    httpc.WithMaxResponseBytes(64<<10),
)

请求头只作用于本次调用,不把用户 Token 留在共享 Client。默认代理/参数保持原行为;也可选择环境代理或固定代理。JSON 自动关闭并限制响应,Do 保留下载/流式读取扩展。完整配置、重试边界和已有自定义 Transport 的迁移见 httpc。

go run ./examples/httpClient

统一配置加载

业务只维护 Config、Default()、Validate() 和配置文件。生成器维护 Load/main;文件缺失字段保留默认值,显式 0/false/空字符串正常覆盖,固定值不会被同名环境变量再次覆盖。

BatchSize int `json:"batchSize"` // 加入 Config;在 Default() 中设置 BatchSize: 100。
server:
  address: "${HTTP_ADDR:-127.0.0.1:8080}"
batchSize: "${BATCH_SIZE:-100}"
password: "${DB_PASSWORD}"
sessionToken: "${SESSION_TOKEN:-}"

${NAME} 未设置或为空就报错;${NAME:-value} 明确指定备用值。$$ 转义 $;密码中的冒号、换行、引号和 ${...} 原样插入,不二次解释。JSON/YAML/XML 共用解析后的字段处理和 Go 类型绑定,错误同时定位文件、字段、变量及可用行列,不打印原值。

go run ./examples/configuration -format xml
DEMO_CLIENT_TIMEOUT=500ms go run ./examples/configuration
# 在生成项目中,仅检查选中的文件,不初始化依赖:
go run . -config etc/config.test.yaml -check-config

完整 语义与迁移、可运行示例。v0.22 移除旧自动环境覆盖、前缀/别名及 env/envDefault 标签,生成项目先迁移开发者配置再重新生成;文件中的响应模板 ${code} 写成 $${code},.api 保持原样。

选择接入方式

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

准备环境与安装

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

安装固定版本的 CLI,后面的命令继续使用同一终端。私有 module 加入 GOPRIVATE,保留已有配置:

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

如果设置过 GOBIN,将该目录加入 PATH。远程 CLI 默认引用自身版本的框架,生成的 go.mod 不需要本地 replace。开发者与 CI 使用同版 CLI;框架源码开发方式见 CLI 文档。

新项目:生成并运行

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

go-omni api new --module example.com/hello ./hello
cd hello
go mod tidy
go run .

另开终端验证:

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 停止服务。进入项目目录即可 go build .;该示例不需要数据库或其他外部服务。

生成的项目放什么

hello/
├── api/service.api                  # 维护接口定义,也可 import 其他 .api
├── main.go                          # 生成器维护:启动、路由注册、优雅退出
├── etc/config.yaml                  # 项目配置
├── omni.gen.json                    # 项目维护的生成选项与可选映射
├── internal/
│   ├── config/                      # 配置读取
│   ├── svc/                         # 数据库、日志、外部客户端等依赖
│   ├── types/*Gen.go                # 按 .api 文件生成 DTO
│   ├── handler/                     # 生成的绑定、响应策略、路由及文档
│   ├── logic/greet/                 # 在这里实现业务逻辑
│   ├── repository/                  # 按需创建,默认不生成空骨架
│   └── middleware/                  # 声明鉴权等中间件后生成工厂
├── docs/openapi.json                # 生成的 OpenAPI 3.0.3
└── .goOmni/apiManifest.json         # 生成文件所有权与校验信息,一起提交 Git

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

api new 的问候接口可以直接运行;自定义接口未实现的 Logic 返回 501。新项目的具名中间件工厂未实现时直接阻止启动;v0.16 的原有 handler 模式仍返回 501。生成器不会根据 HTTP 字段猜数据库表或业务规则。

从自己的接口定义生成

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

syntax = "v1"

@app (defaultResponse: CommonResponse)

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 \
  --module example.com/catalog ./catalog
cd catalog
go mod tidy

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

package item

import (
    "context"

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

func (s *Logic) 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 .
# 另一个终端:
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 \
  --module example.com/catalogFull --database mysql --storage local \
  --log-output file --log-format json ./catalogFull

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

go-omni new 与 go-omni api new 统一生成契约项目。需要手写项目时,可直接引用框架包;生成器不再提供另一套 CRUD 启动目录。

已有项目:按模块接入

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

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

已有项目可直接复用现有目录、Service、Repository 和数据库连接,先选择一个接口接入。也可以只用 logx、storage 等包,暂时保留原来的 HTTP 框架。希望入口和 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/authFactory.go,由项目实现 NewAuth(deps);生成器负责初始化和注册。v0.16 或 handler 模式继续使用 internal/middleware/auth.go。凭据校验和身份注入由项目实现。声明会生成路由绑定与 OpenAPI 的认证描述,不会自动实现登录或授权规则;未实现的工厂阻止启动,原有 hook 模式返回 501。手写路由可使用 auth.RequireAuth(authenticator) 或自己的中间件。JWT、Session 和 Policy 按需选择。

业务错误可以集中定义,业务只选择发生了哪个错误。HTTP 状态由项目配置,业务失败可以返回 HTTP 200;框架不根据 HTTP 状态单独判断成功与否。

错误目录支持两种维护方式,按项目选择一种:Go 中定义并用 omni.WithErrors 注册一次,或者在公共 .api 中声明并生成调用函数。前者不要求 .api 重复声明目录或逐路由列出错误;生成路由默认继承 App 目录。

采用 .api 管理时,其他契约可通过 import 引用:

errorCatalog CommonErrors (
    defaultStatus: 200
    errors: {
        "UsernameInvalid": {"key": "USERNAME_INVALID", "code": 400002, "message": "用户名格式不正确"},
        "PasswordInvalid": {"key": "PASSWORD_INVALID", "code": 400002, "message": "密码格式不正确"},
        "PermissionDenied": {"key": "PERMISSION_DENIED", "status": 403, "code": 403001, "message": "没有权限"}
    }
)

UsernameInvalid 是生成函数名;key 是唯一逻辑标识;code 是项目对外的字符串或整数业务码,可以重复。条目没有填写 status 时使用目录的 defaultStatus,未配置目录默认值时为 400。这个默认值只作用于该目录的条目,不会把所有框架异常都改成 200。

在已有公共响应中保留 codeType: int32 / int64 / string、成功码、未定义错误的兜底码和返回体模板;不再重复填写这几条错误的 codes / messages。通过 @server (errors: UsernameInvalid, PasswordInvalid) 或路由 @errors UsernameInvalid 引用它们。

生成后业务调用:

return TokenResponse{}, apierrors.UsernameInvalid()
// 需要保留内部原因时:
return TokenResponse{}, apierrors.UsernameInvalid().WithCause(err)

生成器同时提供 internal/apierrors.Catalog()、错误函数、路由错误目录及 OpenAPI。目录会在 App 中间件执行前绑定到生成路由;无需手动改已经生成的 main。HTTP 200 的错误使用失败响应模板;OpenAPI 用 anyOf 同时保留成功 DTO 和错误体,多条错误共用数字码时仍保留各自示例。完整契约见 errors.api。

手写项目可在启动时加载自己的目录,保留原来的响应配置:

// omnierrors 是 git.yuntuo.hk/k2safe/go-omni/errors。
catalog := omnierrors.Catalog{
    DefaultStatus: 200,
    Definitions: map[string]omnierrors.Definition{
        "USERNAME_INVALID": {Code: 400002, Message: "用户名格式不正确"},
        "PASSWORD_INVALID": {Code: 400002, Message: "密码格式不正确"},
    },
}
app := omni.New(
    omni.WithErrors(catalog),
    omni.WithResponse(response.Config{
        CodeType: "int32", SuccessCode: 0, ErrorCode: 9999,
        MessageField: "msg", DataMode: "always",
    }),
)
// Service 只返回标准 error,不依赖 App 或 HTTP Context:
// return omnierrors.ByCode("USERNAME_INVALID").WithCause(err)

目录也可放入 config.Config.Errors,由 JSON/YAML 加载;生成项目的路径为 server.errors,生成路由只有显式引用错误定义时才使用 .api 独立目录,未声明则继承 App。不同 App 独立持有配置,单路由也可通过 response.WithErrors 绑定专用目录与自定义 Encoder。直接输出文本、XML、下载或已提交的响应继续保留原协议。

访问日志保留实际 HTTP 状态,并增加失败标记、逻辑错误标识和目录业务码;指标、链路及框架标准中间件/幂等重放也保留失败语义。未知标识使用系统错误兜底,内部 cause 不写入默认公开响应。旧 error (...)、New / BadRequest / Codes / Messages 继续支持;新目录与旧映射冲突会报错,避免静默覆盖。

动态提示也可以集中配置,在目录中声明 message: "{label}不能超过{max}个字符" 和有序 params(label/string、max/int),生成带类型的调用:

return apierrors.TextTooLong("企业名称", 100)
// 最终提示:企业名称不能超过100个字符

运行时支持 omnierrors.ByCode("TEXT_TOO_LONG").WithParams(...),与生成函数共用解析器。固定错误继续无参数调用;缺参或类型错误使用系统兜底。模板、参数类型及具体响应示例同步写入 OpenAPI。完整示例见 errorParams.api。

更详细的配置、作用域和升级步骤见 统一错误目录。

可选结构转换与深拷贝

普通接口不需要任何拷贝配置,直接使用生成的 Request/Response。独立业务结构之间有转换需求时:

// import "git.yuntuo.hk/k2safe/go-omni/copyx"
var input DomainInput
if err := copyx.Copy(&input, req); err != nil { return err }

// 源与目标之间的指针、map、slice 需要隔离时:
snapshot, err := copyx.Clone(source)
if err != nil { return err }

默认浅拷贝,同名同类型字段自动赋值,保留零值;默认不会把 nil 填成非可空目标的零值。不要为使用工具新增一套重复模型。

需要重复使用或自定义规则时创建 Copier:

copier, err := copyx.New(
    copyx.WithDeepCopy(),
    copyx.WithFieldNames[Input, View](map[string]string{"Name": "DisplayName"}),
    copyx.WithConverter(func(v time.Time) (string, error) {
        return v.UTC().Format(time.RFC3339), nil
    }),
)
if err != nil { return err }
if err := copier.Copy(&view, input); err != nil { return err }

自定义函数的源/目标类型由 Go 泛型确定。深拷贝时返回原指针、原 map、子切片或嵌套旧引用都会报错;字符串/整数原值可以返回。差异规则、nil 策略、字段覆盖、资源限制和原 mapping 移除步骤见 copyx 指南,完整类型及可运行代码见 examples/copying。工具使用运行时检查,类型对需测试;深拷贝不适用于连接、锁等资源。

减少接入代码:四个常用入口

server:
  log:
    timeZone: Asia/Shanghai

时区由 Logger 实例管理,不修改全局 time.Local。非法日志级别、数据库端口/连接池等配置会指出 server.log.level、database.port 等字段和原因,不打印输入值。详见 配置诊断、日志时区。

// 本次请求只接受 HTTP 200;默认仍接受所有 2xx。
err := client.JSON(ctx, http.MethodGet, url, nil, &result,
    httpc.WithSuccessStatuses(http.StatusOK),
)

// CI/启动时检查类型,无需构造样本;仍使用 Copy 的同一组 Option。
err = copyx.Check[Request, BusinessInput](options...)

状态不符合要求返回 *httpc.StatusError,不会写入 result,响应体仍由框架关闭。Check 不执行 converter;动态 interface、具体 nil/转换失败和深拷贝引用检查有明确边界,见 类型预检。完整可运行代码见 developerTools。

数据库、存储及其他模块

生成项目在 internal/svc 中创建依赖,提供给 Logic/Repository,并用 c.OnClose(db.Close) 登记自有资源。生成的 main 在 HTTP 排空后关闭 svc,再关闭日志。直接使用框架的手写项目则可以用 app.OnShutdown(db.Close);不要对同一资源重复登记。

已有项目使用 driver.Connector 时,直接 database.OpenConnector(ctx, connector, database.DefaultPoolConfig())。MySQL 可直接用 database/mysql 配置独立 TLS、Logger、时区与拨号参数;任意驱动也可由项目构造 Connector。框架负责连接池、启动 Ping、健康检查及事务。无需转成 DSN,也无需注册全局 TLS/Logger。具体示例和资源归属见 Connector 接入。

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

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

配置与运行

生成项目使用 Default() → 选定文件的显式引用 → 类型绑定 → Validate()。固定值不会被环境变量覆盖;配置规则、转义及迁移见 配置指南。手写入口显式加载配置,再传入 omni.New(omni.WithConfig(settings))。

文件字段 用途
server.address 监听地址,生成项目默认 127.0.0.1:8080
server.transport nethttp(默认)、native、http2
server.tls.certFile/keyFile 成对配置 TLS 证书和私钥路径
server.http.readHeaderTimeout/readTimeout 请求头 / 请求读取超时
server.http.writeTimeout/idleTimeout 响应写入 / Keep-Alive 空闲超时
server.http.maxHeaderBytes 请求头大小限制,字节数
server.shutdownTimeout 优雅退出等待时间,默认 10s
server.enableLogger / server.log.level 访问日志开关、日志级别
server.trustedProxies 可信代理 IP/CIDR,文件数组
database.host/user/password/database 官方 MySQL 结构化连接配置

初始生成文件已为地址、网络层和选中模块的必要配置声明环境引用;其他字段按需要添加固定值或引用。

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

GO_OMNI_TRANSPORT=native go run .

手写入口可用 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。

读写超时也可以在生成项目的 etc/config.yaml 中声明(放在 server.http 下):

server:
  http:
    readHeaderTimeout: 5s
    readTimeout: 30s
    writeTimeout: 30s
    idleTimeout: 60s
    maxHeaderBytes: 1048576

代码配置使用 omni.WithHTTPConfig(omni.HTTPConfig{ReadTimeout: 30*time.Second, WriteTimeout: 30*time.Second})。Run、RunContext、ServeContext 会应用它;NewServer 会配置返回的标准 http.Server,包含可选的 Go 字段 ErrorLog。正值覆盖、零值保留所选网络层的原有默认行为、负值报错;不配置时升级行为不变。超时含义与支持范围见 HTTP 配置,不要把网络 I/O 超时当成业务执行时限。

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

生成项目的依赖可调用 svcCtx.RequestShutdown(err) 主动申请退出,框架排空请求、清理依赖后保留故障原因并以非零状态退出。健康检查与 OpenAPI 的开关、路径通过 server.endpoints 配置,健康响应默认沿用项目公共体;未就绪保持 HTTP 503。完整配置和 v0.12 项目的升级步骤见 项目指南。

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 ./...

main.go、Gen.go、docs/openapi.json 和 manifest 由生成器管理,手动修改会报告冲突。Logic、Repository、中间件实现、配置和依赖装配属于项目,重复生成保留手写内容。契约变化会提示受影响的 Logic 文件和 DTO 字段,明确的签名错误会给出原签名与期望签名;生成后 --check 仍能检出未修复的问题。将 .api、omni.gen.json、生成文件和 .goOmni/apiManifest.json 一起提交,CI 同时运行 generate --check 与 go test ./...。详见 契约变更提示与检查边界。

CLI 与业务 module 固定使用 v0.24.0-preview。v0.21 及更早项目先按 配置迁移 整理旧标签、文件与 Load,再升级依赖与 CLI 并重新生成,框架维护的 main/runtimeGen 自动更新;v0.13 及更早项目按 升级预览 处理,不直接覆盖手写入口或业务文件。使用过旧 mapping 的项目必须先完成 移除迁移,旧配置或旧生成文件会阻止生成。升级后执行生成检查与业务回归。版本变化、迁移边界和验收记录见 发布说明。

按需深入

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

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

后续开发约定

每次功能开发完成,同步更新这份功能索引、用法说明、examples/ 和发布记录;删除或改变功能必须注明影响与迁移。代码、示例和文档一起回归后再发布。约定已记录在 AGENTS.md,后续开发继续遵循。

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