- 新增'对象存储配置'章节:存储桶创建、匿名只读策略、CORS 配置(前端3D渲染必需) - 明确后端不自动创建存储桶,需手动配置 - 按当前 dev 代码更新:Go 1.25+、Casbin v3、fx 子配置 Provider - 补充默认管理员账号、RUSTFS_PUBLIC_URL 说明、Docker 部署细节 - 修正启动命令、目录结构、Swagger 产物说明
13 KiB
CarrotSkin Backend
一个功能完善的 Minecraft 皮肤站后端,基于 Go + Gin 构建,覆盖用户认证、材质管理、角色档案、审计日志等核心能力,并提供完整的 Swagger 文档与容器友好的环境变量配置。
✨ 功能亮点
- 账号体系:注册 / 登录 / JWT 鉴权 / Yggdrasil 密码同步 / 用户积分
- 邮箱与验证码:验证码发送频率控制、邮箱绑定与变更
- 材质中心:皮肤/披风上传、搜索、收藏、下载统计、Hash 去重
- 角色档案:Minecraft Profile 管理、RSA 密钥对生成、活跃档案切换
- 存储与上传:RustFS/MinIO(S3 兼容)直接上传,Hash 分片存储
- 任务与日志:登录日志、操作审计、材质下载记录、定时任务
- 权限体系:Casbin RBAC,支持细粒度路线授权
- 配置管理:100% 依赖环境变量,
SERVER_SWAGGER_ENABLED控制 Swagger - 可观测性:Zap 结构化日志、统一 API 响应模型
🛠 技术栈
| 类型 | 选型 |
|---|---|
| 语言 / 运行时 | Go 1.25+ |
| Web 框架 | Gin |
| ORM | GORM (PostgreSQL 驱动) |
| 数据库 | PostgreSQL 15+ |
| 缓存 / 消息 | Redis 6+ |
| 对象存储 | RustFS / MinIO(S3 兼容) |
| 权限控制 | Casbin v3 |
| 依赖注入 | uber-go/fx |
| 配置 | Viper + godotenv + .env |
| API 文档 | swaggo / Swagger UI |
| 滑动验证码 | wenlng/go-captcha |
| 日志 | Uber Zap |
📁 目录结构
backend/
├── cmd/server/ # 应用入口(main.go:极简 fx 装配)
├── internal/
│ ├── app/ # uber-go/fx 装配层(唯一依赖注入入口)
│ │ ├── module.go # 聚合根 Module
│ │ ├── infra_module.go # Logger/DB/Redis/Storage/Email/JWT/Casbin + 子配置 Provider + Lifecycle
│ │ ├── container_module.go # Container 聚合 + 迁移/路由/HTTP/任务生命周期
│ │ ├── repository_module.go # Repository Provider(占位,Container 模式暂未启用)
│ │ ├── service_module.go # Service Provider(占位)
│ │ ├── handler_module.go # Handler Provider(占位)
│ │ ├── server_module.go # HTTP 服务器模块(占位)
│ │ └── task_module.go # 后台任务模块(占位)
│ ├── container/ # 过渡期手动 DI 容器(fx 迁移期的聚合层)
│ ├── handler/ # HTTP Handler 与 Swagger 注解
│ ├── service/ # 业务逻辑(接口 + 实现)
│ ├── repository/ # 数据访问(接口 + GORM 实现)
│ ├── model/ # GORM 数据模型与响应结构
│ ├── types/ # 请求/响应 DTO
│ ├── errors/ # 统一错误定义(apperrors)
│ ├── middleware/ # Gin 中间件
│ └── task/ # 定时任务与后台作业
├── pkg/ # 可复用组件(config、database、auth、logger、redis、storage、email、utils)
├── configs/casbin/ # Casbin RBAC 模型定义(rbac_model.conf)
├── start.sh # 启动脚本(自动 swag init)
├── docker-compose.yml # 本地容器编排(含 rustfs-init 自动建桶)
├── .env.example # 环境变量示例
└── go.mod # Go Module 定义
✅ 前置要求
- Go 1.25+
- PostgreSQL 15+
- Redis 6+
- RustFS / MinIO(或其他兼容 S3 的对象存储,用于皮肤与头像)
- swag(生成 Swagger 文档,见 https://github.com/swaggo/swag)
🚀 快速开始
-
克隆仓库
git clone <repo> cd backend -
安装依赖
go mod download -
配置环境变量
cp .env.example .env # 根据实际环境填写数据库、Redis、对象存储、邮件等信息 -
初始化数据库
createdb carrotskin # 或 psql -c "CREATE DATABASE carrotskin;"应用启动时会执行
AutoMigrate,自动创建 / 更新表结构,并写入种子数据(默认管理员 + Casbin 规则)。 -
准备对象存储(创建存储桶 + 配置策略与跨域,见下方 🪣 对象存储配置 章节)
-
启动服务
- 推荐:
./start.sh(自动swag init,随后go run ./cmd/server) - 手动启动:
swag init -g cmd/server/main.go -o docs go run ./cmd/server
- 推荐:
-
访问接口
- API Root:
http://localhost:8080 - Swagger:
http://localhost:8080/swagger/index.html(需SERVER_SWAGGER_ENABLED=true) - 健康检查:
http://localhost:8080/health
- API Root:
默认管理员
首次启动会自动 seed 一个管理员账号,首次登录后请立即修改密码:
| 用户名 | 密码 | 邮箱 |
|---|---|---|
admin |
admin123456 |
admin@example.com |
🪣 对象存储配置
后端使用 S3 兼容的对象存储(RustFS / MinIO)保存皮肤与头像,需要两个存储桶:
| 桶名(由环境变量指定) | 默认名 | 用途 |
|---|---|---|
RUSTFS_BUCKET_TEXTURES |
carrot-skin-textures |
皮肤 / 披风文件 |
RUSTFS_BUCKET_AVATARS |
carrot-skin-avatars |
用户头像文件 |
⚠️ 后端不会自动创建存储桶。请在启动后端前,手动创建这两个桶并配置好访问策略与跨域,否则上传会失败、前端 3D 皮肤将无法渲染。
步骤 1:创建存储桶
在 RustFS / MinIO 控制台(默认 http://<host>:9001)创建上述两个桶,或使用 mc 客户端:
mc alias set myrustfs http://<host>:9000 <access-key> <secret-key>
mc mb myrustfs/carrot-skin-textures --ignore-existing
mc mb myrustfs/carrot-skin-avatars --ignore-existing
步骤 2:配置桶访问策略(匿名只读)
皮肤和头像需要被浏览器与 Minecraft 启动器匿名下载(后端用密钥上传即可)。给每个桶配置如下 S3 Bucket Policy(以 carrot-skin-textures 为例,avatars 桶把 Resource 里的桶名换掉即可):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::carrot-skin-textures/*"
}
]
}
等价的 mc 命令:
mc anonymous set download myrustfs/carrot-skin-textures
mc anonymous set download myrustfs/carrot-skin-avatars
步骤 3:配置 CORS(跨域,前端 3D 渲染必需)
🔴 这一步极其关键。普通的
<img>标签加载图片不要求 CORS,但前端用 skinview3d / three.js 通过 WebGL 纹理渲染 3D 皮肤时,浏览器强制要求图片响应带Access-Control-Allow-Origin头,否则 canvas 会被判定为 tainted,3D 皮肤将加载失败/显示空白。
在 RustFS / MinIO 控制台为两个桶各配置一条 CORS 规则,允许前端来源以 GET 方法跨域访问。CORS 规则 JSON(贴入桶的 CORS 配置编辑器):
[
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 86400
}
]
生产环境建议把
AllowedOrigins收紧为你的前端实际域名(如["https://skins.example.com"]),而不是*。
等价的 mc 命令(将 JSON 存为 cors.json 后应用):
mc cors add myrustfs/carrot-skin-textures < cors.json
mc cors add myrustfs/carrot-skin-avatars < cors.json
验证存储配置是否生效
匿名 GET 任意一个桶内对象,期望返回 HTTP 200 且响应头包含 access-control-allow-origin:
curl -I -H "Origin: http://localhost:3000" http://<host>:9000/carrot-skin-textures/<某对象路径>
# 期望看到:
# HTTP/1.1 200 OK
# access-control-allow-origin: *
若 access-control-allow-origin 缺失,说明 CORS 未生效,前端 3D 皮肤将无法渲染。
关于 RUSTFS_PUBLIC_URL
RUSTFS_PUBLIC_URL 决定后端生成的文件访问 URL 前缀(<public_url>/<bucket>/<object>)。
- 留空时:用
RUSTFS_ENDPOINT+ 协议拼接(适合 endpoint 本身就是浏览器可达地址的场景) - 生产环境:务必填写浏览器可访问的公网地址,例如
https://rustfs.example.com,否则后端返回给前端的 URL 浏览器打不开
🐳 Docker 部署
docker-compose.yml 提供了完整的本地编排,包含 PostgreSQL、Redis、RustFS,以及一个一次性的 rustfs-init 服务自动创建存储桶并设置匿名下载策略:
# 启动全部基础设施(含存储)
docker compose --profile storage up -d
# 仅启动应用
docker compose up -d
rustfs-init使用mc完成建桶与策略配置。CORS 仍需按上文 步骤 3 手动配置。
⚙️ 关键环境变量
| 变量 | 说明 | 示例 |
|---|---|---|
SERVER_PORT |
服务监听端口 | :8080 |
SERVER_MODE |
Gin 模式(debug/release) | debug |
SERVER_SWAGGER_ENABLED |
是否暴露 Swagger UI | true |
DATABASE_HOST / DATABASE_PORT |
PostgreSQL 地址 | localhost / 5432 |
DATABASE_USERNAME / DATABASE_PASSWORD |
数据库凭据 | postgres |
DATABASE_NAME |
数据库名称 | carrotskin |
REDIS_HOST / REDIS_PORT |
Redis 地址 | localhost / 6379 |
REDIS_PASSWORD |
Redis 密码(无可为空) | `` |
RUSTFS_ENDPOINT |
RustFS/MinIO 访问地址(后端内网连接用) | 127.0.0.1:9000 |
RUSTFS_PUBLIC_URL |
生成文件 URL 的公开前缀(浏览器可达) | https://rustfs.example.com |
RUSTFS_ACCESS_KEY / RUSTFS_SECRET_KEY |
对象存储凭据 | minioadmin |
RUSTFS_BUCKET_TEXTURES / RUSTFS_BUCKET_AVATARS |
存储桶名称 | carrot-skin-textures |
JWT_SECRET |
JWT 签名密钥 | change-me |
EMAIL_ENABLED |
是否开启邮件服务 | true |
EMAIL_SMTP_HOST / EMAIL_SMTP_PORT |
SMTP 配置 | smtp.example.com / 587 |
ENVIRONMENT |
应用环境(用于 service 层 IsTestEnvironment() 短路) |
production |
SECURITY_ALLOWED_ORIGINS |
API CORS 允许的来源(逗号分隔) | * |
SECURITY_ALLOWED_DOMAINS |
头像/材质 URL 允许的域名(逗号分隔) | localhost,127.0.0.1 |
更多变量请参考 .env.example 与 .env.docker.example。
🧪 常用命令
# 运行单元测试
go test ./...
# 重新生成 swagger
swag init -g cmd/server/main.go -o docs
# 代码格式化 / 静态检查
gofmt -w .
golangci-lint run (若已安装)
🧱 架构说明
- 分层设计:Handler → Service → Repository → Model,层次清晰、职责单一。
- Handler 不得直连
*gorm.DB或*Repository,必须通过 Service。 - Service 的数据访问通过 Repository 接口(个别需要跨表事务的场景除外)。
- Handler 不得直连
- 依赖注入(uber-go/fx):
internal/app/是唯一的依赖装配入口。每个基础设施 / 仓储 / 服务作为fx.Provider注册。- 子配置 Provider:
infra_module.go从*config.Config提取DatabaseConfig/RedisConfig/RustFSConfig/LogConfig/EmailConfig等子结构,按值注入到各pkg/*包的构造函数。 - 生命周期管理:DB / Redis / Storage / Logger / HTTP Server / Task Runner 通过
fx.Lifecycle的OnStart/OnStopHook 统一管理,彻底替代散落在main()的defer Close()链。 - 优雅关闭:
fx.New(app.Module(cfg)).Run()监听SIGINT/SIGTERM,按依赖逆序触发OnStop,先关 HTTP Server(30s 超时),再关 Task Runner,最后关 DB/Redis。 - Dev/Test 自动回退:Redis 连接失败且环境为
development/test/dev/USE_MINIREDIS=true时自动启用miniredis。 - 启动时错误检查:循环依赖、缺失 Provider、Provider 返回 error 全部在
fx.New()阶段立即检测。
- 错误与响应统一:
- 错误统一在
internal/errors/(别名apperrors),通过errors.Is/As匹配。 - 响应统一使用
model.Response/model.PaginationResponse/model.NewErrorResponse。 - Middleware 错误响应也走
model.NewErrorResponse,不再使用裸gin.H。
- 错误统一在
- 配置优先级:
config.Load()读取.env→ 环境变量 → viper 默认值;*config.Config由fx.Supply注入。 - Swagger 注解:所有 Handler、模型、DTO 均补齐
@Summary/@Description/@Success,可直接生成 OpenAPI 文档。
📝 Swagger 说明
start.sh会在启动前执行swag init -g cmd/server/main.go -o docs,生成docs/目录(docs.go/swagger.json/swagger.yaml)。docs/为生成产物,不在仓库内跟踪,首次启动会自动生成。- 通过
SERVER_SWAGGER_ENABLED=false可在生产环境关闭 Swagger UI 暴露。
🤝 贡献指南
- Fork & Clone
- 创建特性分支:
git checkout -b feature/xxx - 编写代码并补全测试 / Swagger 注释
- 提交时附上变更说明
📄 许可证
该项目未附带开源许可证,默认保留所有权利。若需对外使用,请先与作者确认协议。
如需了解业务细节或 API 调用示例,请参考 docs/swagger.yaml 或运行服务后访问 Swagger UI。祝编码愉快!🍀