feat: 完善后端脚手架基础能力

This commit is contained in:
2026-08-09 01:06:21 +08:00
parent e311f416f2
commit f95311a107
61 changed files with 5630 additions and 521 deletions
+453 -174
View File
@@ -1,218 +1,497 @@
# Skeleton
# Go Skeleton
一个基于 Go 的后端脚手架项目,内置 Gin、GORM、Redis、JWT、Atlas 迁移管理和 `gin-docs` 自动接口文档,适合作为新项目起点
面向实际项目开发的 Go Web API 脚手架。它将 HTTP、WebSocket、认证、数据库迁移、缓存、接口文档和可观测性组织成可继续扩展的基础工程
> 当前模块名为 `skeleton`。创建新项目后,请先使用初始化脚本替换成自己的 Go module path。
## 特性
- Gin HTTP 框架
- 模块化路由组织
- 统一响应结构
- JWT 认证中间件
- CORS、日志、恢复、错误记录中间件
- PostgreSQL 和 Redis 初始化封装
- Atlas + GORM Schema 迁移管理
- `gin-docs` 自动扫描接口并生成 OpenAPI 文档
- 预留 WebSocket 挂载点
- Gin HTTP 路由及公开、JWT 私有路由分组
- PostgreSQL、MySQL、SQLite 三种 GORM 数据库驱动
- 按 SQL 方言隔离的 Atlas 数据库迁移
- 可选 Redis,以及 JWT 撤销记录存储
- Access Token、Refresh Token、刷新轮换和注销示例
- WebSocket 房间、广播、连接管理和心跳示例
- Swaggo OpenAPI 文档和 Swagger UI
- Zap 结构化日志、请求 ID、Prometheus 指标
- OpenTelemetry OTLP HTTP 链路追踪
- Dockerfile、Docker Compose、GitHub Actions
- Linux、macOS、Windows amd64 交叉编译
## 目录
- [快速开始](#快速开始)
- [项目结构](#项目结构)
- [配置](#配置)
- [HTTP API](#http-api)
- [WebSocket](#websocket)
- [数据库迁移](#数据库迁移)
- [接口文档](#接口文档)
- [可观测性](#可观测性)
- [测试](#测试)
- [编译](#编译)
- [Docker](#docker)
- [开发新模块](#开发新模块)
- [安全说明](#安全说明)
- [贡献](#贡献)
## 技术栈
| 能力 | 实现 |
| --- | --- |
| HTTP | Gin |
| WebSocket | Gorilla WebSocket |
| ORM | GORM |
| 数据库 | PostgreSQL / MySQL / SQLite |
| 数据迁移 | Atlas + Atlas GORM Provider |
| 缓存 | Redis,可选 |
| 认证 | JWT HS256 + bcrypt |
| API 文档 | Swaggo / Swagger UI |
| 日志 | Zap |
| 指标 | Prometheus |
| Trace | OpenTelemetry OTLP HTTP |
## 快速开始
### 1. 安装依赖
### 环境要求
- Go 1.25+
- Atlas CLI,仅执行数据库迁移时需要
- Swag CLI,仅重新生成 OpenAPI 文档时需要
- Docker,可选
- C 编译器,使用 SQLite 时需要
### 1. 创建自己的项目
```bash
go mod tidy
git clone https://gitea.xchoumc.online/xchou/go-skeleton.git
cd go-skeleton
./scripts/init.sh github.com/yourname/your-project
```
### 2. 配置环境
### 2. 使用 SQLite 快速运行
修改 [`config/app.yaml`](config/app.yaml)
修改 `config/app.yaml`
- `app`:主机、端口、版本、环境、时区
- `logger`:日志级别与输出方式
- `database`PostgreSQL 连接信息
- `redis`Redis 连接信息
- `jwt`Token 配置
```yaml
database:
driver: sqlite
sqlite_path: data/skeleton.db
### 3. 启动项目
```bash
go run .
```
默认监听地址由配置决定,示例为 `0.0.0.0:8080`
## 接口文档
项目在启动时通过 `gin-docs` 自动挂载接口文档,无需手写 Swagger 注释。
默认文档地址:
- `/docs`
常见输出:
- `/docs/openapi.json`
- `/docs/openapi.yaml`
- `/docs/export/postman`
- `/docs/export/insomnia`
文档会根据 Gin 路由和 GORM 模型自动推断生成。
## 路由结构
所有 API 默认挂载在 `/api` 下。
### 公开接口
- `GET /api/ping`
- `GET /api/health`
- `GET /api/example/hello`
### 私有接口
- `/api/private/*`
该分组默认挂载 JWT 认证中间件,适合后续需要登录态的业务接口。
### WebSocket
- `routes/ws/` 预留 WebSocket 扩展入口
## 模板初始化
如果这是通过 GitHub Template 生成的新仓库,可以直接运行初始化脚本,把 `go.mod` 和源码里的包路径一次性改掉:
```bash
./scripts/init.sh github.com/yourname/yourrepo
```
脚本会自动完成:
- 更新 `go.mod` 的 module 名称
- 替换源码中的旧包路径
- 执行 `go mod tidy`
## 项目结构
```text
.
├── main.go
├── config/
├── database/
├── middlewares/
├── models/
├── modules/
├── routes/
├── utils/
├── cmd/migrate/
├── atlas.hcl
├── atlas_loader.go
└── config/app.yaml
```
## 主要目录说明
- [`main.go`](main.go):启动入口,负责初始化配置、日志、JWT、数据库、Redis、路由和文档
- [`routes/`](routes)REST 和 WebSocket 路由挂载
- [`middlewares/`](middlewares):日志、CORS、JWT、恢复处理
- [`database/`](database)PostgreSQL 和 Redis 连接封装
- [`modules/`](modules):业务模块示例
- [`models/`](models)GORM 模型
- [`utils/`](utils):响应、分页、时区等通用工具
- [`cmd/migrate/`](cmd/migrate/main.go):迁移命令输出入口
## 数据库迁移
项目集成了 Atlas + GORM Provider,用于根据模型生成迁移。
### 相关文件
- [`atlas.hcl`](atlas.hcl)
- [`atlas_loader.go`](atlas_loader.go)
### 安装 Atlas
```bash
brew install ariga/tap/atlas
```
或者:
```bash
go install ariga.io/atlas/cmd/atlas@latest
```
### 迁移命令
`cmd/migrate` 会根据参数输出对应的 Atlas 命令。
查看状态:
```bash
go run cmd/migrate/main.go -action status
```
生成迁移:
```bash
go run cmd/migrate/main.go -action diff
go run cmd/migrate/main.go -action diff -name create_users
redis:
enabled: false
```
应用迁移:
```bash
export DATABASE_URL='sqlite://data/skeleton.db'
export DATABASE_DEV_URL='sqlite://dev?mode=memory&_fk=1'
go run cmd/migrate/main.go -action apply
```
验证迁移
启动服务
```bash
go run cmd/migrate/main.go -action validate
go run .
```
### 环境变量
默认服务地址:
`atlas.hcl` 使用以下环境变量:
| 服务 | 地址 |
| --- | --- |
| API | `http://localhost:8080/api` |
| Swagger UI | `http://localhost:8080/docs/index.html` |
| Prometheus Metrics | `http://localhost:8080/metrics` |
| WebSocket | `ws://localhost:8080/api/ws` |
- `DATABASE_URL`
- `DATABASE_DEV_URL`
## 项目结构
## 扩展新模块
```text
.
├── main.go # 应用启动与优雅关闭
├── config/ # YAML、环境变量及配置校验
├── database/ # GORM、Redis、Token 撤销和迁移封装
├── middlewares/ # JWT、日志、CORS、指标、Request ID
├── models/ # GORM 数据模型
├── modules/
│ ├── auth/ # 注册、登录、刷新和注销示例
│ ├── example/ # REST 业务模块示例
│ └── ws/ # WebSocket Hub、Client 和消息协议
├── observability/ # OpenTelemetry 初始化
├── routes/
│ ├── rest/ # REST 路由注册器
│ └── ws/ # WebSocket 路由挂载
├── docs/ # Swag 自动生成文件
├── migrations/
│ ├── postgres/
│ ├── mysql/
│ └── sqlite/
├── tools/atlas-loader/ # 独立 Atlas GORM Schema 工具模块
├── scripts/
│ ├── init.sh # 修改 Go module path
│ └── build.sh # 多平台 amd64 构建
├── Dockerfile
├── docker-compose.yml
└── Makefile
```
新增业务时建议按以下方式组织:
## 配置
1.`modules/` 下创建新模块目录
2.`routes/rest/` 中注册路由
3. 公共接口挂到公开路由组
4. 需要登录态的接口挂到 `/api/private`
5. 如有模型变更,先更新 `models/`,再生成迁移
## 示例模块
项目内置 `modules/example/` 作为参考实现,包含 controller、service 和 response 结构。
示例接口:
应用默认读取 `config/app.yaml`。使用其他配置文件:
```bash
GET /api/example/hello
export SKELETON_CONFIG=config/app.yaml
```
返回示例:
环境变量优先级高于 YAML。生产环境应使用部署平台的 Secret 管理能力注入凭据。
### 应用和日志
| 环境变量 | 说明 |
| --- | --- |
| `APP_HOST``APP_PORT` | HTTP 监听地址 |
| `APP_VERSION` | 应用版本 |
| `APP_ENVIRONMENT` | `development``production` |
| `APP_DEBUG` | Gin 调试模式 |
| `APP_TIMEZONE` | IANA 时区名称 |
| `LOG_LEVEL` | `debug``info``warn``error` |
| `LOG_FORMAT` | `console``json` |
| `LOG_OUTPUT` | `stdout``file` |
### 数据库
| 环境变量 | 说明 |
| --- | --- |
| `DATABASE_DRIVER` | `postgres``mysql``sqlite` |
| `DATABASE_DSN` | 完整连接串,优先于其他连接参数 |
| `DATABASE_HOST``DATABASE_PORT` | 服务端数据库地址 |
| `DATABASE_USERNAME``DATABASE_PASSWORD` | 数据库凭据 |
| `DATABASE_NAME` | 数据库名称 |
| `DATABASE_SSLMODE` | PostgreSQL SSL 模式 |
| `DATABASE_SQLITE_PATH` | SQLite 文件路径 |
| `DATABASE_MAX_IDLE_CONNS` | 最大空闲连接数 |
| `DATABASE_MAX_OPEN_CONNS` | 最大连接数 |
| `DATABASE_CONN_MAX_LIFETIME` | 连接最大存活分钟数 |
未设置 `database.driver` 时默认使用 PostgreSQL。SQLite 会创建数据文件父目录,并默认限制为单连接。
### Redis、JWT 和可观测性
| 环境变量 | 说明 |
| --- | --- |
| `REDIS_ENABLED` | 是否启用 Redis |
| `REDIS_HOST``REDIS_PORT``REDIS_PASSWORD` | Redis 连接信息 |
| `JWT_SECRET` | HS256 签名密钥 |
| `JWT_ACCESS_EXPIRE_MINUTES` | Access Token 有效分钟数 |
| `JWT_REFRESH_EXPIRE_HOURS` | Refresh Token 有效小时数 |
| `METRICS_ENABLED``METRICS_PATH` | Prometheus 指标开关和路径 |
| `TRACING_ENABLED` | 是否启用 OpenTelemetry |
| `OTEL_SERVICE_NAME` | Trace 服务名 |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP HTTP 地址,如 `localhost:4318` |
| `OTEL_EXPORTER_OTLP_INSECURE` | 是否使用明文 OTLP |
生产模式会拒绝默认或少于 32 个字符的 JWT 密钥。
## HTTP API
| 方法 | 路径 | 认证 | 说明 |
| --- | --- | --- | --- |
| `GET` | `/api/ping` | 否 | 进程存活检查 |
| `GET` | `/api/health` | 否 | 数据库和可选 Redis readiness 检查 |
| `GET` | `/api/example/hello` | 否 | REST 模块示例 |
| `POST` | `/api/auth/register` | 否 | 注册示例用户 |
| `POST` | `/api/auth/login` | 否 | 获取 Token Pair |
| `POST` | `/api/auth/refresh` | 否 | 轮换 Refresh Token |
| `POST` | `/api/private/auth/logout` | Bearer | 撤销 Access/Refresh Token |
| `GET` | `/api/ws` | 否 | WebSocket 握手入口 |
完整请求和响应模型请查看 Swagger UI。
### 认证示例
```bash
curl -X POST http://localhost:8080/api/auth/register \
-H 'Content-Type: application/json' \
-d '{"username":"demo","password":"change-me-123"}'
curl -X POST http://localhost:8080/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"demo","password":"change-me-123"}'
```
启用 Redis 时,Token 撤销记录存储在 Redis,适合多实例部署;关闭 Redis 时使用进程内存,仅适合开发或单实例部署。
## WebSocket
连接端点:
```text
ws://localhost:8080/api/ws?room=lobby&client_id=demo
```
查询参数:
| 参数 | 必填 | 说明 |
| --- | --- | --- |
| `room` | 否 | 房间名,默认 `lobby`,最多 64 个字符 |
| `client_id` | 否 | 客户端标识;未提供时服务端生成 |
使用 `websocat`
```bash
websocat 'ws://localhost:8080/api/ws?room=lobby&client_id=terminal-1'
```
发送消息:
```json
{"type":"message","data":"hello websocket"}
```
服务端事件:
```json
{
"code": 200,
"message": "success",
"data": {
"message": "hello from skeleton example"
}
"type": "message",
"room": "lobby",
"client_id": "terminal-1",
"data": "hello websocket",
"timestamp": "2026-08-09T00:00:00Z"
}
```
## 默认配置
事件类型:
`config/app.yaml` 提供了可直接运行的本地示例配置。程序启动时会自动补充未填写的默认值,方便快速开发。
| 类型 | 说明 |
| --- | --- |
| `welcome` | 当前客户端连接成功 |
| `message` | 房间广播消息 |
| `presence` | 客户端加入或离开 |
| `error` | 消息格式或类型错误 |
## 说明
浏览器示例:
这个脚手架的目标不是预置所有业务,而是把通用基础设施先搭好,方便后续按模块扩展。
```javascript
const socket = new WebSocket(
"ws://localhost:8080/api/ws?room=lobby&client_id=browser-1"
);
socket.onmessage = (event) => console.log(JSON.parse(event.data));
socket.onopen = () => {
socket.send(JSON.stringify({ type: "message", data: "hello" }));
};
```
WebSocket 实现包含:
- 按房间隔离广播
- 在线连接数量
- 单连接一个 reader 和一个 writer
- Ping/Pong 心跳与读取超时
- 8 KiB 单消息限制
- 有界发送队列和慢客户端清理
- 安全的默认同源 Origin 校验
当前 Hub 是单进程内存实现。多实例部署时,应在 `modules/ws/hub.go` 接入 Redis Pub/Sub、NATS 或其他消息系统。
## 数据库迁移
安装 Atlas
```bash
brew install ariga/tap/atlas
# 或
go install ariga.io/atlas/cmd/atlas@latest
```
设置当前数据库和开发数据库 URL
```bash
# PostgreSQL
export DATABASE_URL='postgres://user:pass@localhost:5432/app?sslmode=disable'
export DATABASE_DEV_URL='docker://postgres/17/dev'
# MySQL
export DATABASE_URL='mysql://user:pass@localhost:3306/app'
export DATABASE_DEV_URL='docker://mysql/8/dev'
# SQLite
export DATABASE_URL='sqlite://data/app.db'
export DATABASE_DEV_URL='sqlite://dev?mode=memory&_fk=1'
```
常用命令:
```bash
# 状态
go run cmd/migrate/main.go -action status
# 根据 GORM 模型生成迁移
go run cmd/migrate/main.go -action diff -name add_orders
# 校验
go run cmd/migrate/main.go -action validate
# 预览
go run cmd/migrate/main.go -action apply -dry-run
# 应用
go run cmd/migrate/main.go -action apply
# 显式选择 Atlas 环境
go run cmd/migrate/main.go -action status -env local_mysql
```
迁移工具会根据 `app.environment``database.driver` 自动选择对应的 Atlas 环境。三种数据库的迁移文件不可混用。
## 接口文档
安装 Swag 并重新生成:
```bash
go install github.com/swaggo/swag/cmd/swag@v1.8.12
swag init
```
生成文件位于 `docs/`,应用运行时不依赖 Swag CLI。
## 可观测性
- `GET /api/ping`liveness,不访问外部服务
- `GET /api/health`readiness,依赖异常时返回 HTTP 503
- `GET /metrics`Prometheus Counter 和 Histogram
- `X-Request-ID`:接收上游 ID 或自动生成,并写入响应和日志
- OpenTelemetry:启用后通过 OTLP HTTP 导出 Gin 请求 Span
Docker Compose 默认提供 Jaeger UI`http://localhost:16686`
## 测试
单元测试和静态检查:
```bash
go test ./...
go test -race ./config ./database ./middlewares ./modules/ws
go vet ./...
```
PostgreSQL 和 MySQL 集成测试:
```bash
export TEST_POSTGRES_DSN='postgres://skeleton:skeleton@127.0.0.1:5432/skeleton?sslmode=disable'
export TEST_MYSQL_DSN='skeleton:skeleton@tcp(127.0.0.1:3306)/skeleton?charset=utf8mb4&parseTime=True&loc=Local'
go test -tags=integration ./database
```
Makefile 快捷命令:
```bash
make test
make vet
make integration
```
## 编译
当前平台无 CGO 构建:
```bash
make build
```
Linux、macOS、Windows x64/amd64 交叉编译:
```bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/skeleton-linux-amd64 .
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o dist/skeleton-darwin-amd64 .
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/skeleton-windows-amd64.exe .
```
项目封装命令:
```bash
VERSION=0.1.0 make build-x64
```
SQLite 官方驱动依赖 CGO。跨平台 `CGO_ENABLED=0` 产物支持 PostgreSQL 和 MySQLSQLite 应在目标平台原生构建:
```bash
make build-native
```
## Docker
默认启动应用、PostgreSQL、Redis、Atlas 迁移和 Jaeger
```bash
docker compose up --build
```
额外启动 MySQL
```bash
docker compose --profile mysql up -d mysql
```
停止:
```bash
docker compose down
```
删除本地 Compose 数据卷:
```bash
docker compose down -v
```
## 开发新模块
推荐结构:
```text
modules/order/
├── controller.go
├── service.go
├── types.go
└── repository.go
```
开发流程:
1.`modules/` 添加业务代码。
2.`routes/rest/` 注册公开或 JWT 私有路由。
3.`models/` 添加或修改 GORM 模型。
4. 为目标数据库分别生成和检查 Atlas 迁移。
5. 添加单元测试和必要的集成测试。
6. 更新 Swag 注解并执行 `swag init`
## 安全说明
该仓库是脚手架,不应在未经审查的情况下直接用于生产:
- 必须修改默认 JWT 密钥和示例数据库密码。
- 根据实际前端域名收紧 CORS 和 WebSocket Origin 策略。
- 示例注册接口应增加邀请码、管理员权限或在生产环境关闭。
- 多实例 Token 撤销必须启用 Redis。
- 多实例 WebSocket 广播必须接入外部消息系统。
- TLS 应由网关、Ingress 或应用部署环境终止。
请通过私有渠道报告安全问题,不要在公开 Issue 中提交密钥或可利用细节。
## 贡献
欢迎提交 Issue 和 Pull Request。提交前请至少执行:
```bash
go fmt ./...
go test ./...
go vet ./...
```
建议每个 Pull Request 聚焦一个主题,并同步更新测试、迁移和文档。
## License
当前仓库尚未包含许可证文件。正式公开分发前,请由项目维护者选择并添加合适的开源许可证。