From cea22951b9aa535f11d1101042c0b8483aa013d0 Mon Sep 17 00:00:00 2001 From: Wu XiangYu Date: Wed, 8 Jul 2026 17:37:24 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20README,=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=AF=B9=E8=B1=A1=E5=AD=98=E5=82=A8=E6=A1=B6=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E4=B8=8E=20CORS=20=E9=85=8D=E7=BD=AE=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增'对象存储配置'章节:存储桶创建、匿名只读策略、CORS 配置(前端3D渲染必需) - 明确后端不自动创建存储桶,需手动配置 - 按当前 dev 代码更新:Go 1.25+、Casbin v3、fx 子配置 Provider - 补充默认管理员账号、RUSTFS_PUBLIC_URL 说明、Docker 部署细节 - 修正启动命令、目录结构、Swagger 产物说明 --- README.md | 168 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index e3e1233..7938253 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - **邮箱与验证码**:验证码发送频率控制、邮箱绑定与变更 - **材质中心**:皮肤/披风上传、搜索、收藏、下载统计、Hash 去重 - **角色档案**:Minecraft Profile 管理、RSA 密钥对生成、活跃档案切换 -- **存储与上传**:RustFS/MinIO 预签名 URL,减轻服务器带宽压力 +- **存储与上传**:RustFS/MinIO(S3 兼容)直接上传,Hash 分片存储 - **任务与日志**:登录日志、操作审计、材质下载记录、定时任务 - **权限体系**:Casbin RBAC,支持细粒度路线授权 - **配置管理**:100% 依赖环境变量,`SERVER_SWAGGER_ENABLED` 控制 Swagger @@ -18,15 +18,17 @@ | 类型 | 选型 | | --- | --- | -| 语言 / 运行时 | Go 1.24+ | +| 语言 / 运行时 | Go 1.25+ | | Web 框架 | Gin | | ORM | GORM (PostgreSQL 驱动) | | 数据库 | PostgreSQL 15+ | | 缓存 / 消息 | Redis 6+ | | 对象存储 | RustFS / MinIO(S3 兼容) | -| 权限控制 | Casbin | -| 配置 | Viper + `.env` | +| 权限控制 | Casbin v3 | +| 依赖注入 | uber-go/fx | +| 配置 | Viper + godotenv + `.env` | | API 文档 | swaggo / Swagger UI | +| 滑动验证码 | wenlng/go-captcha | | 日志 | Uber Zap | ## 📁 目录结构 @@ -37,8 +39,8 @@ backend/ ├── internal/ │ ├── app/ # uber-go/fx 装配层(唯一依赖注入入口) │ │ ├── module.go # 聚合根 Module -│ │ ├── infra_module.go # Logger/DB/Redis/Storage/Email/JWT/Casbin + Lifecycle -│ │ ├── container_module.go # Container 聚合 + 路由/HTTP/任务生命周期 +│ │ ├── 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(占位) @@ -54,19 +56,20 @@ backend/ │ ├── middleware/ # Gin 中间件 │ └── task/ # 定时任务与后台作业 ├── pkg/ # 可复用组件(config、database、auth、logger、redis、storage、email、utils) -├── docs/ # swagger 生成产物(docs.go / swagger.json / swagger.yaml) +├── configs/casbin/ # Casbin RBAC 模型定义(rbac_model.conf) ├── start.sh # 启动脚本(自动 swag init) -├── docker-compose.yml # 本地容器编排 +├── docker-compose.yml # 本地容器编排(含 rustfs-init 自动建桶) ├── .env.example # 环境变量示例 └── go.mod # Go Module 定义 ``` ## ✅ 前置要求 -- Go 1.24+ +- Go 1.25+ - PostgreSQL 15+ - Redis 6+ - RustFS / MinIO(或其他兼容 S3 的对象存储,用于皮肤与头像) +- swag(生成 Swagger 文档,见 ) ## 🚀 快速开始 @@ -92,25 +95,144 @@ backend/ createdb carrotskin # 或 psql -c "CREATE DATABASE carrotskin;" ``` - > 应用启动时会执行 `AutoMigrate`,自动创建 / 更新表结构。 + > 应用启动时会执行 `AutoMigrate`,自动创建 / 更新表结构,并写入种子数据(默认管理员 + Casbin 规则)。 -5. **启动服务** - - **推荐**:`./start.sh`(自动 `swag init`,随后 `go run cmd/server/main.go`) +5. **准备对象存储**(创建存储桶 + 配置策略与跨域,**见下方 [🪣 对象存储配置](#-对象存储配置) 章节**) + +6. **启动服务** + - **推荐**:`./start.sh`(自动 `swag init`,随后 `go run ./cmd/server`) - **手动启动**: ```bash swag init -g cmd/server/main.go -o docs - go run cmd/server/main.go + go run ./cmd/server ``` -6. **访问接口** +7. **访问接口** - 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://:9001`)创建上述两个桶,或使用 `mc` 客户端: + +```bash +mc alias set myrustfs http://:9000 +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 渲染必需**) + +> 🔴 **这一步极其关键**。普通的 `` 标签加载图片不要求 CORS,但前端用 skinview3d / three.js 通过 **WebGL 纹理**渲染 3D 皮肤时,浏览器**强制要求**图片响应带 `Access-Control-Allow-Origin` 头,否则 canvas 会被判定为 tainted,3D 皮肤将加载失败/显示空白。 + +在 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://: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 前缀(`//`)。 + +- 留空时:用 `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-渲染必需) 手动配置。 ## ⚙️ 关键环境变量 | 变量 | 说明 | 示例 | | --- | --- | --- | -| `SERVER_PORT` | 服务监听端口 | `8080` | +| `SERVER_PORT` | 服务监听端口 | `:8080` | | `SERVER_MODE` | Gin 模式(debug/release) | `debug` | | `SERVER_SWAGGER_ENABLED` | 是否暴露 Swagger UI | `true` | | `DATABASE_HOST` / `DATABASE_PORT` | PostgreSQL 地址 | `localhost` / `5432` | @@ -118,14 +240,15 @@ backend/ | `DATABASE_NAME` | 数据库名称 | `carrotskin` | | `REDIS_HOST` / `REDIS_PORT` | Redis 地址 | `localhost` / `6379` | | `REDIS_PASSWORD` | Redis 密码(无可为空) | `` | -| `RUSTFS_ENDPOINT` | RustFS/MinIO 访问地址 | `127.0.0.1:9000` | +| `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` | 存储桶名称 | `carrotskin-textures` | +| `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` | CORS 允许的来源(逗号分隔) | `*` | +| `SECURITY_ALLOWED_ORIGINS` | API CORS 允许的来源(逗号分隔) | `*` | | `SECURITY_ALLOWED_DOMAINS` | 头像/材质 URL 允许的域名(逗号分隔) | `localhost,127.0.0.1` | 更多变量请参考 `.env.example` 与 `.env.docker.example`。 @@ -148,9 +271,10 @@ golangci-lint run (若已安装) - **分层设计**:Handler → Service → Repository → Model,层次清晰、职责单一。 - Handler 不得直连 `*gorm.DB` 或 `*Repository`,必须通过 Service。 - - Service 不得持有 `*gorm.DB`,所有数据访问通过 Repository 接口。 + - 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 Server(30s 超时),再关 Task Runner,最后关 DB/Redis。 - **Dev/Test 自动回退**:Redis 连接失败且环境为 `development` / `test` / `dev` / `USE_MINIREDIS=true` 时自动启用 `miniredis`。 @@ -164,9 +288,9 @@ golangci-lint run (若已安装) ## 📝 Swagger 说明 -- `start.sh` 会在启动前执行 `swag init -g cmd/server/main.go -o docs` -- 若手动运行,需要保证 `docs/` 下的 `docs.go`、`swagger.json`、`swagger.yaml` 与代码同步 -- 通过 `SERVER_SWAGGER_ENABLED=false` 可在生产环境关闭 Swagger UI 暴露 +- `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 暴露。 ## 🤝 贡献指南