Files
backend/README.md

309 lines
13 KiB
Markdown
Raw Normal View History

# CarrotSkin Backend
2025-12-26 21:32:32 +08:00
一个功能完善的 Minecraft 皮肤站后端,基于 Go + Gin 构建,覆盖用户认证、材质管理、角色档案、审计日志等核心能力,并提供完整的 Swagger 文档与容器友好的环境变量配置。
## ✨ 功能亮点
- **账号体系**:注册 / 登录 / JWT 鉴权 / Yggdrasil 密码同步 / 用户积分
- **邮箱与验证码**:验证码发送频率控制、邮箱绑定与变更
- **材质中心**:皮肤/披风上传、搜索、收藏、下载统计、Hash 去重
- **角色档案**Minecraft Profile 管理、RSA 密钥对生成、活跃档案切换
- **存储与上传**RustFS/MinIOS3 兼容直接上传Hash 分片存储
2025-12-26 21:32:32 +08:00
- **任务与日志**:登录日志、操作审计、材质下载记录、定时任务
- **权限体系**Casbin RBAC支持细粒度路线授权
- **配置管理**100% 依赖环境变量,`SERVER_SWAGGER_ENABLED` 控制 Swagger
- **可观测性**Zap 结构化日志、统一 API 响应模型
## 🛠 技术栈
| 类型 | 选型 |
| --- | --- |
| 语言 / 运行时 | Go 1.25+ |
2025-12-26 21:32:32 +08:00
| Web 框架 | Gin |
| ORM | GORM (PostgreSQL 驱动) |
| 数据库 | PostgreSQL 15+ |
| 缓存 / 消息 | Redis 6+ |
| 对象存储 | RustFS / MinIOS3 兼容) |
| 权限控制 | Casbin v3 |
| 依赖注入 | uber-go/fx |
| 配置 | Viper + godotenv + `.env` |
2025-12-26 21:32:32 +08:00
| API 文档 | swaggo / Swagger UI |
| 滑动验证码 | wenlng/go-captcha |
2025-12-26 21:32:32 +08:00
| 日志 | Uber Zap |
## 📁 目录结构
```
backend/
├── cmd/server/ # 应用入口main.go极简 fx 装配)
2025-12-26 21:32:32 +08:00
├── 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 定义
2025-12-26 21:32:32 +08:00
```
## ✅ 前置要求
- Go 1.25+
2025-12-26 21:32:32 +08:00
- PostgreSQL 15+
- Redis 6+
- RustFS / MinIO或其他兼容 S3 的对象存储,用于皮肤与头像)
- swag生成 Swagger 文档,见 <https://github.com/swaggo/swag>
2025-12-26 21:32:32 +08:00
## 🚀 快速开始
1. **克隆仓库**
```bash
git clone <repo>
cd backend
```
2. **安装依赖**
2025-12-26 21:32:32 +08:00
```bash
go mod download
```
2025-12-26 21:32:32 +08:00
3. **配置环境变量**
```bash
cp .env.example .env
# 根据实际环境填写数据库、Redis、对象存储、邮件等信息
```
4. **初始化数据库**
2025-12-26 21:32:32 +08:00
```bash
createdb carrotskin
# 或 psql -c "CREATE DATABASE carrotskin;"
```
> 应用启动时会执行 `AutoMigrate`,自动创建 / 更新表结构,并写入种子数据(默认管理员 + Casbin 规则)。
2025-12-26 21:32:32 +08:00
5. **准备对象存储**(创建存储桶 + 配置策略与跨域,**见下方 [🪣 对象存储配置](#-对象存储配置) 章节**
6. **启动服务**
- **推荐**`./start.sh`(自动 `swag init`,随后 `go run ./cmd/server`
2025-12-26 21:32:32 +08:00
- **手动启动**
```bash
swag init -g cmd/server/main.go -o docs
go run ./cmd/server
2025-12-26 21:32:32 +08:00
```
7. **访问接口**
2025-12-26 21:32:32 +08:00
- API Root: `http://localhost:8080`
- Swagger: `http://localhost:8080/swagger/index.html`(需 `SERVER_SWAGGER_ENABLED=true`
- 健康检查: `http://localhost:8080/health`
### 默认管理员
首次启动会自动 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` 客户端:
```bash
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` 里的桶名换掉即可):
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::carrot-skin-textures/*"
}
]
}
```
等价的 `mc` 命令:
```bash
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 会被判定为 tainted3D 皮肤将加载失败/显示空白。
在 RustFS / MinIO 控制台为**两个桶**各配置一条 CORS 规则,允许前端来源以 GET 方法跨域访问。CORS 规则 JSON贴入桶的 CORS 配置编辑器):
```json
[
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 86400
}
]
```
> 生产环境建议把 `AllowedOrigins` 收紧为你的前端实际域名(如 `["https://skins.example.com"]`),而不是 `*`。
等价的 `mc` 命令(将 JSON 存为 `cors.json` 后应用):
```bash
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`
```bash
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` 服务自动创建存储桶并设置匿名下载策略:
```bash
# 启动全部基础设施(含存储)
docker compose --profile storage up -d
# 仅启动应用
docker compose up -d
```
> `rustfs-init` 使用 `mc` 完成建桶与策略配置。CORS 仍需按上文 [步骤 3](#步骤-3配置-cors跨域前端-3d-渲染必需) 手动配置。
2025-12-26 21:32:32 +08:00
## ⚙️ 关键环境变量
| 变量 | 说明 | 示例 |
| --- | --- | --- |
| `SERVER_PORT` | 服务监听端口 | `:8080` |
2025-12-26 21:32:32 +08:00
| `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` |
2025-12-26 21:32:32 +08:00
| `RUSTFS_ACCESS_KEY` / `RUSTFS_SECRET_KEY` | 对象存储凭据 | `minioadmin` |
| `RUSTFS_BUCKET_TEXTURES` / `RUSTFS_BUCKET_AVATARS` | 存储桶名称 | `carrot-skin-textures` |
2025-12-26 21:32:32 +08:00
| `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` |
2025-12-26 21:32:32 +08:00
更多变量请参考 `.env.example``.env.docker.example`
## 🧪 常用命令
```bash
2025-12-26 21:32:32 +08:00
# 运行单元测试
go test ./...
2025-12-26 21:32:32 +08:00
# 重新生成 swagger
swag init -g cmd/server/main.go -o docs
2025-12-26 21:32:32 +08:00
# 代码格式化 / 静态检查
gofmt -w .
golangci-lint run (若已安装)
```
2025-12-26 21:32:32 +08:00
## 🧱 架构说明
- **分层设计**Handler → Service → Repository → Model层次清晰、职责单一。
- Handler 不得直连 `*gorm.DB``*Repository`,必须通过 Service。
- Service 的数据访问通过 Repository 接口(个别需要跨表事务的场景除外)。
- **依赖注入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` / `OnStop` Hook 统一管理,彻底替代散落在 `main()``defer Close()` 链。
- **优雅关闭**`fx.New(app.Module(cfg)).Run()` 监听 `SIGINT` / `SIGTERM`,按依赖逆序触发 `OnStop`,先关 HTTP Server30s 超时),再关 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` 注入。
2025-12-26 21:32:32 +08:00
- **Swagger 注解**:所有 Handler、模型、DTO 均补齐 `@Summary` / `@Description` / `@Success`,可直接生成 OpenAPI 文档。
2025-12-26 21:32:32 +08:00
## 📝 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 暴露。
2025-12-26 21:32:32 +08:00
## 🤝 贡献指南
2025-12-26 21:32:32 +08:00
1. Fork & Clone
2. 创建特性分支:`git checkout -b feature/xxx`
3. 编写代码并补全测试 / Swagger 注释
4. 提交时附上变更说明
2025-12-26 21:32:32 +08:00
## 📄 许可证
2025-12-26 21:32:32 +08:00
该项目未附带开源许可证,默认保留所有权利。若需对外使用,请先与作者确认协议。
2025-12-26 21:32:32 +08:00
---
2025-12-26 21:32:32 +08:00
如需了解业务细节或 API 调用示例,请参考 `docs/swagger.yaml` 或运行服务后访问 Swagger UI。祝编码愉快🍀