2026-09-22 01:38:42 +08:00

GoOmni

GoOmni 是一套面向业务复用的 Go 后端基础框架。当前默认使用只依赖标准库的网络适配器,业务代码只依赖 GoOmni 的 transport-neutral API,后续可以替换为自研网络层。

远程 module 地址是 git.yuntuo.hk/k2safe/go-omni。业务项目可以直接引用指定版本:

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

v0.4.0 移除了 Gin 和 Gin 专属的 WithMode 配置;omni.New() 直接使用标准库路由,已有业务 Handler / Service 无需修改。

创建新业务项目:

go install git.yuntuo.hk/k2safe/go-omni/cmd/go-omni@v0.6.0-preview
go-omni new --module example.com/billing ./billing
cd billing
go mod tidy
go run ./cmd/server

远程安装的 CLI 默认引用自身版本,生成的项目不需要本地 replace。 具体版本和本地开发选项见 CLI 文档。

需要数据库持久化时,使用 go-omni new --database mysql --module example.com/billing ./billing。 生成项目包含完整用户 CRUD、数据库 Repository、ServiceContext、建表脚本和就绪探针。 先应用生成的 schema.sql,再配置 GO_OMNI_DB_DSN;启动时会检查连接与表结构,退出时释放连接池。

目录结构

go-omni/
├── app/          # 应用启动和生命周期
├── httpx/        # 与网络框架无关的 HTTP 抽象
├── response/     # 统一响应
├── errors/       # 统一错误
├── health/       # 存活和就绪检查
├── middleware/   # 请求 ID 等基础中间件
├── logx/         # 业务层结构化日志
├── metrics/      # 请求指标与状态统计
├── config/       # 配置管理
├── validate/     # 参数校验
├── auth/         # 认证和权限接口
├── database/     # database/sql 生命周期和事务
├── transport/nethttp/ # 标准库网络适配器
├── transport/native/  # 自研 HTTP/1.1 服务端预览
├── transport/http2/   # opt-in HTTP/2 实验适配器
├── examples/     # 示例项目
└── cmd/          # go-omni 项目脚手架

快速开始

package main

import omni "git.yuntuo.hk/k2safe/go-omni"

func main() {
	app := omni.New()

	app.GET("/health", func(ctx *omni.Context) error {
		return ctx.OK(map[string]string{"status": "ok"})
	})

	app.Run(":8080")
}

成功响应默认使用统一结构:

{
  "code": "OK",
  "message": "success",
  "data": {"status": "ok"},
  "requestId": "..."
}

业务错误可以直接返回:

return omni.NotFound("USER_NOT_FOUND", "user was not found")

默认网络适配器位于 transport/nethttp。业务层不需要导入具体网络库;如果后续实现自己的网络层,只需要实现 httpx.Router / httpx.RouterGroup,然后通过 omni.NewWithRouter 启动应用:

application := omni.NewWithRouter(myRouter)

仓库还提供了使用标准库路由的 transport/nethttp 参考实现。两种适配器已通过同一套路由、业务处理链和取消传播测试,完整用户 CRUD 复用同一份 Handler / Service。

自研适配器可以直接运行 transport/transporttest.Run 验收。具体规则、v0.4.0 的斜杠 / Any / 405 行为调整,以及自研网络内核与当前 HTTP 接口的边界,见 适配层契约。

app.Engine() 返回标准 http.Handler,ctx.Raw() 返回适配器提供的 http.ResponseWriter,业务 API 不锁定具体网络实现。

需要试用第一版自研网络层时,业务注册代码无需改变,只替换启动器:

import (
	"context"
	"log"
	"os"
	"os/signal"
	"syscall"

	omni "git.yuntuo.hk/k2safe/go-omni"
	"git.yuntuo.hk/k2safe/go-omni/transport/native"
)

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()

app := omni.New()
app.GET("/health", func(ctx *omni.Context) error {
	return ctx.OK(map[string]string{"status": "ok"})
})
if err := app.RunWith(ctx, native.New(nil), ":8080"); err != nil {
	log.Fatal(err)
}

transport/native 当前支持 HTTP/1.1、Keep-Alive、Content-Length 和 chunked 请求体、http.Flusher 流式响应、可选 TLS、读写超时和优雅关闭;不支持的传输编码会被拒绝。它会复用连接状态和响应对象,并可通过 MaxConnections、MaxConcurrentRequests 限制内存峰值。它仍通过标准 HTTP 请求/响应接口连接业务层,适合在现有 Handler / Service 上做网络层验证。

native TLS 当前只支持并宣告 ALPN http/1.1;配置 h2 会在启动时快速失败。需要 HTTP/2 时,可由 Nginx/Cloudflare 终止后回源 HTTP/1.1,或使用默认的 transport/nethttp 适配器。

transport/http2 是独立的 opt-in 实验适配器,使用 x/net/http2 处理帧和流控,同时保留 GoOmni 的连接上限、全局请求并发闸门和生命周期边界。它不会改变 native 默认路径;实验入口、限制和验收结果见 HTTP/2 进入条件。

自研网络层还提供 server.Stats(),可读取 accepted、rejected、active connections 和 active requests,与上面的业务请求 metrics 组合后能区分连接堆积、过载拒绝和 Handler 慢请求。rejectedConnections 表示连接闸门拒绝数,rejectedRequests 表示并发请求闸门返回 503 的次数。

业务代码可以直接使用 git.yuntuo.hk/k2safe/go-omni/logx 打结构化日志,不需要依赖具体网络库:

func (s *UserService) Create(ctx *httpx.Context) error {
	logx.InfoContext(ctx.Context(), "user.create", "email", input.Email)
	return nil
}

需要基础运行指标时,可以注册 transport-neutral metrics middleware 和接口:

requestMetrics := omni.NewMetrics()
app.Use(requestMetrics.Middleware())
app.GET("/metrics", requestMetrics.Handler())

接口返回请求总数、错误数、当前处理中请求、累计耗时和 HTTP 状态分布。它是进程内快照,生产环境可由业务侧定时采集到监控系统。

WithLogger(true) 会启用 GoOmni 的统一 HTTP 访问日志;日志内容和业务日志共用 requestId。应用启动时可以调用 logx.Setup 配置 JSON/文本格式、级别、服务名和输出文件。

应用配置可以统一从 GO_OMNI_* 环境变量加载,再交给 omni.New:

settings, err := omni.LoadConfig("")
if err != nil {
	log.Fatal(err)
}
logger, err := logx.New(settings.Log)
if err != nil {
	log.Fatal(err)
}
application := omni.New(omni.WithConfig(settings))
application.OnShutdown(logger.Close)

常用变量包括 GO_OMNI_ENABLE_LOGGER、GO_OMNI_LOG_LEVEL、 GO_OMNI_LOG_FORMAT、GO_OMNI_LOG_OUTPUT、GO_OMNI_SERVICE、 GO_OMNI_ENVIRONMENT、GO_OMNI_HTTP_ADDR、GO_OMNI_SHUTDOWN_TIMEOUT 和 GO_OMNI_TRUSTED_PROXIES。可信代理变量使用逗号分隔的 IP/CIDR,例如 GO_OMNI_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8;只有来自这些地址的连接才会解析 X-Forwarded-For,默认只记录 socket 对端地址。数据库连接仍由 database.ConfigFromEnv 从 GO_OMNI_DB_* 加载。

App 同时实现标准 http.Handler。Run 监听 SIGINT / SIGTERM;RunContext 则由调用方的 context 控制退出,生成项目已使用 signal.NotifyContext 接好信号。 退出时先停止接收新请求,等待活跃请求完成,再执行资源清理。超过 ShutdownTimeout 时强制关闭连接、取消请求 context,并返回超时错误。业务代码应响应 context 取消;框架无法强制结束忽略取消的业务 goroutine。数据库连接池由 database.Open 创建,在服务退出时调用 Close。

应用资源可以直接挂到 omni.New 返回的生命周期上:

application := omni.New()
application.OnShutdown(db.Close)

清理函数会按注册顺序的逆序执行,application.Close() 和 RunContext 都会触发,且重复调用安全。

服务可以把数据库、缓存等依赖注册为就绪探针:

checker, err := health.New(health.Probe{
	Name: "database",
	Check: db.PingContext,
})
if err != nil {
	log.Fatal(err)
}
application.GET("/health/live", checker.LivenessHandler())
application.GET("/health/ready", checker.ReadinessHandler())

存活检查只确认进程在线;就绪检查会执行全部探针,任意依赖失败时返回 HTTP 503。

新项目脚手架会生成项目自己的 internal/servicecontext。它集中保存 Logger、数据库、缓存和外部客户端,Service 只依赖这个项目上下文,不把基础设施初始化散落到各个 Handler:

services, err := servicecontext.New(ctx, logger)
if err != nil {
	return err
}
defer services.Close()
userService := service.NewUsers(services)

完整业务参考项目

examples/reference 提供按 Handler / Service / Repository / Model 分层的用户 CRUD 示例。默认使用并发安全的内存存储,支持分页、邮箱唯一校验、结构化请求日志和统一错误,无需配置数据库。

从远程版本生成并升级一个独立业务模块,可运行:

python3 scripts/verifyBusiness.py --version v0.6.0-preview

该验收会在临时目录中先固定 v0.2.0,再升级到 v0.6.0-preview,检查无本地 replace、配置加载、结构化日志、requestId、CRUD、校验错误、业务错误和优雅退出。

标准库与 native 的并发、延迟和长连接内存对照见 PERFORMANCE.md。python3 scripts/verifyPerformance.py 运行检查与 benchmark;增加 --load --output /tmp/goOmniPerf.json 可测量独立服务进程并保存报告。

HTTP/2 仍是显式 opt-in 实验适配器,不会改变 native 的 HTTP/1.1 默认路径。Linux TLS 长测使用 python3 scripts/verifyHTTP2.py --duration 30m,默认使用普通 Go 运行模式;--race 默认改为 60 秒,用于并发检查,其额外内存开销不能当成生产运行基线。

多连接混合场景使用 python3 scripts/verifyHTTP2.py --scenario mixed --duration 30m:8 条独立 TLS 连接,每条连接 14 个普通请求 worker、1 个慢上传 worker、1 个慢下载 worker,共 128 个 worker。上传为 256 KiB、每 4 KiB 等待 2 ms;下载为 8 MiB、每次最多读取 64 KiB 后等待 20 ms。日志分别记录三类成功数、传输字节数、每条连接的覆盖情况,以及停止并释放测试数据后两次 GC 的采样。

该脚本默认把测试容器限制为 2 核、2 GiB 内存(禁用 swap)、256 个进程/线程,整个命令最多运行 40 分钟,中断和超时会清理本次容器。这些限制可用 --cpus、--memory-mb、--pids-limit、--wall-timeout 调整;共享服务器应先用 --duration 60s 短测,再逐步延长。每 15 秒输出的堆、RSS 和 FD 统计包含同一进程内的客户端与服务端。

Linux amd64 已完成 30 分钟、单 TLS 连接、128 个 worker 的普通模式长测:58,102,476 次请求,0 失败、0 协议错误,采样 RSS 峰值约 22.6 MiB,连接/活动请求归零且 FD 回到基线。完整采样和原始日志见 HTTP/2 长测报告 与 原始日志,此前的 race 对照见 短测记录。这项结果覆盖单连接复用。

多连接慢客户端场景也已完成 Linux amd64 的 30 分钟长测:8 条 TLS 连接、128 个 worker,53,079,128 次普通请求、106,971 次慢上传、5,588 次慢下载,共 53,191,687 次,0 失败与协议错误。采样 RSS 峰值约 116.6 MiB(包含客户端、服务端和下载缓冲);退出后连接/请求归零,goroutine 回到 2、FD 回到 6;释放测试数据并两次 GC 后存活堆约 450 KiB,RSS 不要求立即归还操作系统。完整 120 份采样见 混合长测报告 和 原始日志。连接级窗口耗尽、WINDOW_UPDATE 恢复及零窗口写阻塞的关闭回收也已有独立测试;HTTP/2 回源代理的 GOAWAY/排空联调仍待完成,适配器保持实验状态。

在框架根目录运行:

go run ./examples/reference

默认地址为 http://127.0.0.1:8080。示例文档提供完整的 curl 操作流程。数据只保存在内存中,重启后清空。

框架的 App 也实现了标准 http.Handler,可以直接用于 http.Server 和 httptest.NewServer;业务路由可通过 *omni.RouterGroup 注册,无需导入网络库。

当前边界

缓存、消息队列和定时任务暂时作为后续可选模块。认证层只定义 Authenticator、Principal 和路由保护中间件,具体 JWT、Session 或 OAuth 由项目实现。核心数据库层只依赖 database/sql,具体驱动、迁移和表结构由项目选择。

当前成熟度与后续验收标准见 ROADMAP.md,版本变更见 CHANGELOG.md。

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