stack-be-go — Go 微服务技术栈规范
以 10 年经验 Go 架构师的标准生成、重构、解释所有 Go 后端代码。所有规范的可执行载体是 assets/backend-template/:一个可直接编译的脚手架工程,含一套 Project CRUD 参考实现。写代码前先想清楚对应模板中的哪一层、哪个文件,与其保持一致。
一、技术栈基准
| 类别 | 选型 | 版本 | 关键约束 |
|---|---|---|---|
| 语言 | Go | 1.23+ | CGO_ENABLED=0 静态编译;go vet 零告警 |
| Web 框架 | Gin | v1.12+ | ReleaseMode(prod);DefaultWriter 桥接 slog |
| ORM | GORM | v1.31+ | MySQL 驱动;软删 gorm.DeletedAt;钩子自动审计 |
| 数据库 | MySQL | 8.0 | 表名下划线 t_xxx;连接池 database/sql |
| 缓存 | Redis | go-redis/v9 | 连接池配置对齐 Jedis |
| 配置 | Viper | v1.21+ | base + env 覆盖;APP_ENV 切环境 |
| 日志 | log/slog | Go 标准库 | tab 分隔;console/file 双通道;按 size/time 滚动 |
| 日志滚动 | lumberjack + file-rotatelogs | 最新 | size 滚动 / time 滚动 |
| 鉴权 | go-jose/v4 | v4.1+ | JWE dir+A256GCM 解密 x-tai-identity |
| 链路追踪 | X-Request-Id + context | — | UUID v4 去横杠;context 透传 |
| 腾讯特色 | adhome 部署脚本 / TAI 票据 / tlinux 基础镜像 | 见模板 | 统一响应、Docker 打包、部署脚本 |
统一响应(违反视为严重错误):
- 所有对外响应用 response.Ok(c, data) / response.Created(c, data);分页用 dto.PageResult[T]
- 业务异常用 errs.NewNotFound/NewConflict/NewValidation/NewUnauthorized;携带 HTTP 状态码 + 业务 code
- 严禁自造响应信封、自造分页结构、自造错误码体系
二、新建项目:使用模板脚手架
新建任何 Go 微服务时,必须从模板初始化,禁止从零手写工程文件:
> SkillHub 用户注意:.go/.mod/Dockerfile/.gitignore 文件因平台白名单限制,以代码块形式存放在 references/go-template-files.md,需先按该文件还原到 assets/backend-template/ 对应路径。
- 复制
assets/backend-template/到目标位置,根目录改名<service>-be-go - 全局批量替换(先长后短):
git.woa.com/gatesma/template/template-be-go→ 实际 module path;template-be-go→<service>-be-go(go.mod、binary);template_be_go→<service>_be_go(PNAME、shell 脚本);template→<service>(配置前缀、健康检查服务名);Template→<Service>(类型名TemplateService等,同步重命名文件) go.mod确认 module path 与 Go 版本;configs/config.yaml确认api_prefix与port- 替换
configs/config-{dev,prod}.yaml中 MySQL/Redis 地址、tai_token(环境变量注入密钥,勿硬编码生产密钥)、Dockerfile 的MAINTAINER - 保留
Project一组文件作为分层参考实现,编写真实领域代码后删除 - 构建验证:
go build ./cmd/server;本地运行:APP_ENV=dev go run ./cmd/server
模板已含:adhome 部署脚本(dockerfile/bin)、slog 双通道日志(业务 + access)、GORM 审计钩子、鉴权中间件(JWE 解密 + dev mock 回退)、trace/recovery/access 三件套中间件、统一响应与错误码、Viper 环境配置、优雅关停。
三、分层架构铁律
app(装配)→ router → handler → service → repository → model
↓ ↓
response infra(MySQL/Redis/外部客户端)
- app 是唯一做依赖组装的层;handler、service 全部构造注入,不自行 new 依赖
- handler 只做协议转换:参数绑定 → 调 service → 组装响应;不写业务
- service 接口+实现分离(
XxxService接口 +xxxServiceImpl私有实现);返回 dto,不返回 model - repository 接口+实现分离(
XxxRepository接口 +xxxRepository私有实现);全项目唯一直接操作 DB 的层 - model 是纯 GORM 实体,与表一一对应,不含业务方法
- dto 不依赖 model,两者通过 service 层手动转换
- 严禁 handler 直调 repository;严禁下层反向依赖上层;跨层只通过接口依赖
- 目录固定为
internal/{app,config,dto,errs,handler,infra,log,middleware,model,repository,response,router,service,utils}/,不得新增
各层详细职责与对象规范(Entity/DTO/Repository 接口模式)见 references/layer-rules.md。
四、API 设计铁律
- URL 固定不含资源 id:动作语义路径,如
/projects/create、/projects/detail;严禁 RESTful/{pid}路径参数 - id 类参数一律放 JSON body;对外 id 一律
string(utils.EncodeID/DecodeID),内部int64 - 响应包装:
response.Ok(c, data)/response.Created(c, data)/response.AbortWithError(c, err) - 路由注册:
POST /<resource>/<action>,全部 POST(读接口也用 POST + body 传参) - 异常即状态:资源不存在
errs.NewNotFound(404);唯一冲突errs.NewConflict(409);参数非法errs.NewValidation(422);未登录errs.NewUnauthorized(401);严禁返回 null 或 200+错误码 - 错误码按模块分段(业务 20000 / 通用 30000 / 鉴权 40000),追加不 scattered
- 中间件链:Trace → Recovery → Access → CORS → Auth(JWE 解密
x-tai-identity,fail-closed)
五、常见场景速查
| 需求 | 应该做 | 不应该做 |
|---|---|---|
| 返回数据 | response.Ok(c, data) |
裸返回结构体 / 自造信封 |
| 分页列表 | dto.NewPageResult(list, total, page, size) |
手拼分页 JSON |
| 传 id 操作 | id 放 body,URL 用 /detail /update |
/{pid} 路径参数 |
| 查询数据库 | r.db.Where(...).First(&p) + 软删自动过滤 |
手写 SQL 字符串 |
| 复杂查询条件 | 抽 func buildQuery(...) |
堆砌在查询方法里 |
| 实体映射 DTO | toXxxResp(entity) 纯函数 |
handler 里逐字段赋值 |
| 跨 service 通用逻辑 | 抽 engine/ 或 infra/ 客户端 |
复制到多个 service |
| 错误处理 | errs.NewNotFound("项目不存在: %d", id) |
返回 nil 让上层猜 |
| 日志记录 | log.Infof / log.ErrorContextf(ctx, ...) |
fmt.Println / log.Println |
| 链路追踪 | log.WithTraceID(ctx, tid) + context 透传 |
全局变量存 traceID |
| 获取当前用户 | utils.CurrentLoginName(ctx) |
从 handler 传参到 service |
| 切换环境 | APP_ENV=prod |
改 yaml 提交 |
| 构建 | go build ./cmd/server |
用 IDE 直接跑 |
| Docker 打包 | ./build/build-docker.sh |
手动 docker build |
| 优雅关停 | signal.Notify(SIGINT, SIGTERM) + srv.Shutdown |
os.Exit(1) |
六、编码硬约束(Go 官方 + Effective Go)
- 命名:导出标识符 UpperCamelCase / 非导出 lowerCamelCase;包名全小写单词;禁止拼音缩写;接口名不带
I前缀(ProjectService不是IProjectService) - 格式:
gofmt/goimports强制;K&R 行尾大括号;if/for不加多余括号;Tab 缩进(Go 标准);魔法值抽命名常量 - 函数 ≤80 行(硬上限),if 嵌套 ≤3 层(guard clause 拆解);文件 ≤500 行超出拆分
- 每个导出函数/类型/常量必须有中文注释(
// FuncName 功能描述.);包级注释用// Package xxx ... - 关键逻辑必须用
// 1. xxx编号步骤注释标明每一步(双斜线+空格+数字+半角句号+空格+≤30字动宾,步骤间不留空行) - error 必须处理:
if err != nil及时返回或包装(fmt.Errorf("xxx: %w", err));严禁_ = err吞错误 - 新增三方依赖前必须先向用户确认必要性与用途,获批后执行
go get;版本锁定在go.mod - 写新工具方法前先查
internal/utils/已有实现,能复用不新造 - context 作为函数首参透传:
func Xxx(ctx context.Context, ...);严禁context.Background()在业务代码中硬编码(main/init 除外) - goroutine 必须有退出机制(
context.Done()或 channel);严禁裸go func()无超时无退出
设计原则(SOLID/DRY/YAGNI)、Go 惯用法、并发安全见 references/coding-style.md。
七、提交前自检清单
生成或修改代码后逐条自检,违反立即修正:
- [ ] handler 无业务逻辑?service 未直接操作 DB?repository 未反向依赖?
- [ ] 所有 URL 固定不含 id?id 在 body 且对外 string?
- [ ] 响应统一
response.Ok/response.Created?未自造信封? - [ ] 函数 ≤80 行?if 嵌套 ≤3 层?魔法值已抽常量?关键逻辑已加
// 1. xxx编号步骤注释? - [ ] 无拼音缩写?命名符合规范?所有导出标识符有中文注释?
- [ ] error 全部处理?无
_ = err吞错误? - [ ] 查询显式判
errors.Is(err, repository.ErrRecordNotFound)抛领域异常而非返回 nil? - [ ] DTO 用
omitempty?id 输出utils.EncodeID()?时间用formatTime()格式化? - [ ] context 作为首参透传?业务代码无
context.Background()硬编码? - [ ] 重复逻辑已抽取(DRY)?未引入用不上的抽象(YAGNI)?
- [ ] 新增依赖已获用户确认且版本落
go.mod?新工具方法已确认无现有可复用实现? - [ ] 实体带齐 TenantID/CreatedAt/UpdatedAt/CreatedBy/UpdatedBy/DeletedAt?GORM 钩子已注册?
- [ ]
go vet ./...零告警?go build ./cmd/server成功?