Files
go-skeleton/README.md
T

498 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Go Skeleton
面向实际项目开发的 Go Web API 脚手架。它将 HTTP、WebSocket、认证、数据库迁移、缓存、接口文档和可观测性组织成可继续扩展的基础工程。
> 当前模块名为 `skeleton`。创建新项目后,请先使用初始化脚本替换成自己的 Go module path。
## 特性
- 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 |
## 快速开始
### 环境要求
- Go 1.25+
- Atlas CLI,仅执行数据库迁移时需要
- Swag CLI,仅重新生成 OpenAPI 文档时需要
- Docker,可选
- C 编译器,使用 SQLite 时需要
### 1. 创建自己的项目
```bash
git clone https://gitea.xchoumc.online/xchou/go-skeleton.git
cd go-skeleton
./scripts/init.sh github.com/yourname/your-project
```
### 2. 使用 SQLite 快速运行
修改 `config/app.yaml`
```yaml
database:
driver: sqlite
sqlite_path: data/skeleton.db
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 .
```
默认服务地址:
| 服务 | 地址 |
| --- | --- |
| 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` |
## 项目结构
```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
```
## 配置
应用默认读取 `config/app.yaml`。使用其他配置文件:
```bash
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
{
"type": "message",
"room": "lobby",
"client_id": "terminal-1",
"data": "hello websocket",
"timestamp": "2026-08-09T00:00:00Z"
}
```
事件类型:
| 类型 | 说明 |
| --- | --- |
| `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
当前仓库尚未包含许可证文件。正式公开分发前,请由项目维护者选择并添加合适的开源许可证。