498 lines
13 KiB
Markdown
498 lines
13 KiB
Markdown
# 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 和 MySQL;SQLite 应在目标平台原生构建:
|
||
|
||
```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
|
||
|
||
当前仓库尚未包含许可证文件。正式公开分发前,请由项目维护者选择并添加合适的开源许可证。
|