# 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。 ### 响应约定 业务接口统一使用 HTTP 200 返回结果,业务状态通过响应体中的 `code` 字段表达,不使用 HTTP 状态码传递业务成功或失败状态。 ```json { "code": 401, "message": "用户名或密码错误", "data": null } ``` 统一响应结构: | 字段 | 说明 | | --- | --- | | `code` | 业务状态码,例如 `200`、`400`、`401`、`409`、`500` | | `message` | 面向调用方的结果说明 | | `data` | 业务数据;失败时通常为 `null` | 新增业务接口时必须遵守以下约定: - HTTP 响应状态固定为 `200 OK`。 - 成功使用响应体 `code: 200`。 - 参数、认证、权限和业务冲突等错误写入响应体 `code`。 - 调用方应判断响应体中的 `code`,不能依赖 HTTP 状态码判断业务结果。 - WebSocket 握手、协议升级失败以及基础设施健康检查不属于普通业务响应,可以使用对应的 HTTP 状态码。 ### 认证示例 ```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 当前仓库尚未包含许可证文件。正式公开分发前,请由项目维护者选择并添加合适的开源许可证。