319 lines
10 KiB
Markdown
319 lines
10 KiB
Markdown
|
|
# 皮肤服务 (SkinService)
|
|||
|
|
|
|||
|
|
此项目是一个使用 **Go-zero** 框架构建的、专注于材质管理的纯 RPC 微服务。它为CarrotSkin皮肤站提供了完整的材质上传、存储、查询和管理功能,并集成了基于 MinIO 的高效文件存储系统。
|
|||
|
|
|
|||
|
|
## 核心功能
|
|||
|
|
|
|||
|
|
- **安全材质上传**: 利用 MinIO 预签名 POST URL,实现客户端直传,支持PNG格式限制和文件大小控制(1KB-1MB)
|
|||
|
|
- **智能去重机制**: 基于 SHA-256 哈希值自动检测并复用相同材质,节省存储空间
|
|||
|
|
- **完整材质管理**: 提供材质的增、删、改、查(CRUD)等全套操作
|
|||
|
|
- **分类存储**: 支持皮肤(SKIN)和披风(CAPE)两种材质类型的分类管理
|
|||
|
|
- **个人皮肤库**: 用户可查看和管理自己上传的所有材质
|
|||
|
|
- **皮肤广场**: 公开材质展示平台,支持按类型浏览和搜索
|
|||
|
|
- **收藏夹功能**: 支持用户收藏和管理自己的收藏列表
|
|||
|
|
- **高性能查询**: 支持分页查询、批量获取和条件搜索
|
|||
|
|
- **权限控制**: 严格的材质所有权验证,确保用户只能操作自己的材质
|
|||
|
|
|
|||
|
|
## 技术特性
|
|||
|
|
|
|||
|
|
- **微服务架构**: 基于 gRPC 的高性能服务间通信
|
|||
|
|
- **对象存储**: MinIO 分布式存储,支持海量文件管理
|
|||
|
|
- **数据库缓存**: go-zero 内置缓存机制,提升查询性能
|
|||
|
|
- **类型安全**: 完整的 protobuf 定义和数据验证
|
|||
|
|
- **容错设计**: 优雅的错误处理和日志记录
|
|||
|
|
- **代码生成**: 使用 `goctl` 从 `.proto` 和 `.sql` 文件自动生成代码
|
|||
|
|
|
|||
|
|
## API 接口参考
|
|||
|
|
|
|||
|
|
服务通过 gRPC 暴露,接口定义于 `docs/textures.proto`。
|
|||
|
|
|
|||
|
|
### 材质上传流程
|
|||
|
|
|
|||
|
|
| 方法名 | 功能描述 | 请求类型 | 响应类型 |
|
|||
|
|
|--------|----------|----------|----------|
|
|||
|
|
| `GenerateTextureUploadURL` | 生成材质上传预签名URL | `GenerateTextureUploadURLRequest` | `GenerateTextureUploadURLResponse` |
|
|||
|
|
| `CreateTexture` | 创建材质记录(上传完成后调用) | `CreateTextureRequest` | `CreateTextureResponse` |
|
|||
|
|
|
|||
|
|
**上传流程说明**:
|
|||
|
|
1. 客户端调用 `GenerateTextureUploadURL` 获取预签名上传URL和表单数据
|
|||
|
|
2. 客户端使用返回的 `post_url` 和 `form_data` 直接向MinIO上传文件
|
|||
|
|
3. 上传成功后,客户端调用 `CreateTexture` 将材质信息记录到数据库
|
|||
|
|
|
|||
|
|
### 材质管理接口
|
|||
|
|
|
|||
|
|
| 方法名 | 功能描述 | 请求类型 | 响应类型 |
|
|||
|
|
|--------|----------|----------|----------|
|
|||
|
|
| `GetTexture` | 获取单个材质信息 | `GetTextureRequest` | `GetTextureResponse` |
|
|||
|
|
| `UpdateTexture` | 更新材质信息(公开/私有状态) | `UpdateTextureRequest` | `UpdateTextureResponse` |
|
|||
|
|
| `DeleteTexture` | 删除材质(含MinIO文件清理) | `DeleteTextureRequest` | `DeleteTextureResponse` |
|
|||
|
|
|
|||
|
|
### 查询接口
|
|||
|
|
|
|||
|
|
| 方法名 | 功能描述 | 请求类型 | 响应类型 |
|
|||
|
|
|--------|----------|----------|----------|
|
|||
|
|
| `GetUserTextures` | 获取用户个人材质库 | `GetUserTexturesRequest` | `GetUserTexturesResponse` |
|
|||
|
|
| `GetPublicTextures` | 获取皮肤广场公开材质 | `GetPublicTexturesRequest` | `GetPublicTexturesResponse` |
|
|||
|
|
| `SearchTextures` | 搜索材质 | `SearchTexturesRequest` | `SearchTexturesResponse` |
|
|||
|
|
|
|||
|
|
### 高级功能接口
|
|||
|
|
|
|||
|
|
| 方法名 | 功能描述 | 请求类型 | 响应类型 |
|
|||
|
|
|--------|----------|----------|----------|
|
|||
|
|
| `GetTextureByHash` | 根据哈希值查找材质(防重复上传) | `GetTextureByHashRequest` | `GetTextureByHashResponse` |
|
|||
|
|
| `GetTexturesByIds` | 批量获取材质信息 | `GetTexturesByIdsRequest` | `GetTexturesByIdsResponse` |
|
|||
|
|
|
|||
|
|
### 收藏夹功能接口
|
|||
|
|
|
|||
|
|
| 方法名 | 功能描述 | 请求类型 | 响应类型 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `AddFavorite` | 添加材质到收藏夹 | `AddFavoriteRequest` | `AddFavoriteResponse` |
|
|||
|
|
| `RemoveFavorite` | 从收藏夹移除材质 | `RemoveFavoriteRequest` | `RemoveFavoriteResponse` |
|
|||
|
|
| `GetUserFavorites` | 获取用户收藏列表 | `GetUserFavoritesRequest` | `GetUserFavoritesResponse` |
|
|||
|
|
| `CheckFavoriteStatus` | 检查材质收藏状态 | `CheckFavoriteStatusRequest` | `CheckFavoriteStatusResponse` |
|
|||
|
|
|
|||
|
|
## 数据模型
|
|||
|
|
|
|||
|
|
### 材质信息 (TextureInfo)
|
|||
|
|
|
|||
|
|
```protobuf
|
|||
|
|
message TextureInfo {
|
|||
|
|
int64 id = 1; // 材质ID
|
|||
|
|
int64 uploader_id = 2; // 上传者用户ID
|
|||
|
|
TextureType type = 3; // 材质类型(SKIN/CAPE)
|
|||
|
|
string url = 4; // MinIO中的永久访问URL
|
|||
|
|
string hash = 5; // SHA-256哈希值
|
|||
|
|
bool is_public = 6; // 是否公开到皮肤广场
|
|||
|
|
string created_at = 7; // 创建时间
|
|||
|
|
string updated_at = 8; // 更新时间
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 材质类型枚举
|
|||
|
|
|
|||
|
|
```protobuf
|
|||
|
|
enum TextureType {
|
|||
|
|
SKIN = 0; // 皮肤
|
|||
|
|
CAPE = 1; // 披风
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 收藏夹相关数据模型
|
|||
|
|
|
|||
|
|
#### 收藏材质信息 (FavoriteTextureInfo)
|
|||
|
|
|
|||
|
|
```protobuf
|
|||
|
|
message FavoriteTextureInfo {
|
|||
|
|
TextureInfo texture = 1; // 材质信息
|
|||
|
|
string favorite_at = 2; // 收藏时间
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 存储结构
|
|||
|
|
|
|||
|
|
### MinIO 对象存储结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
textures/ # 存储桶根目录
|
|||
|
|
├── skins/ # 皮肤材质目录
|
|||
|
|
│ └── user_{userId}/ # 按用户分组
|
|||
|
|
│ └── {timestamp}_{filename}.png
|
|||
|
|
└── capes/ # 披风材质目录
|
|||
|
|
└── user_{userId}/ # 按用户分组
|
|||
|
|
└── {timestamp}_{filename}.png
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 数据库表结构
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE textures (
|
|||
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
uploader_id BIGINT NOT NULL,
|
|||
|
|
type VARCHAR(10) NOT NULL, -- 'SKIN' 或 'CAPE'
|
|||
|
|
url VARCHAR(500) NOT NULL, -- MinIO访问URL
|
|||
|
|
hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256哈希值
|
|||
|
|
is_public BOOLEAN DEFAULT FALSE, -- 是否公开
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|||
|
|
|
|||
|
|
INDEX idx_uploader_type (uploader_id, type),
|
|||
|
|
INDEX idx_hash (hash),
|
|||
|
|
INDEX idx_public_type (is_public, type, created_at)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `user_texture_favorites` 表
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 用户材质收藏表
|
|||
|
|
CREATE TABLE `user_texture_favorites` (
|
|||
|
|
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '收藏记录的唯一ID',
|
|||
|
|
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID (对应UserService中的users.id)',
|
|||
|
|
`texture_id` BIGINT UNSIGNED NOT NULL COMMENT '收藏的材质ID (对应textures.id)',
|
|||
|
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间',
|
|||
|
|
PRIMARY KEY (`id`),
|
|||
|
|
UNIQUE KEY `uk_user_texture` (`user_id`, `texture_id`),
|
|||
|
|
INDEX `idx_user_id` (`user_id`),
|
|||
|
|
INDEX `idx_texture_id` (`texture_id`),
|
|||
|
|
INDEX `idx_created_at` (`created_at`)
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户材质收藏表';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 配置说明
|
|||
|
|
|
|||
|
|
### 服务配置 (etc/textures.yaml)
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
Name: textures.rpc
|
|||
|
|
ListenOn: 0.0.0.0:8080
|
|||
|
|
|
|||
|
|
# 数据库配置
|
|||
|
|
DataSource: root:password@tcp(localhost:3306)/carrot_skin?charset=utf8mb4&parseTime=true&loc=Local
|
|||
|
|
|
|||
|
|
# 缓存配置
|
|||
|
|
CacheRedis:
|
|||
|
|
- Host: localhost:6379
|
|||
|
|
Pass: ""
|
|||
|
|
Type: node
|
|||
|
|
|
|||
|
|
# MinIO配置
|
|||
|
|
MinIO:
|
|||
|
|
Endpoint: "localhost:9000"
|
|||
|
|
AccessKeyID: "minioadmin"
|
|||
|
|
SecretAccessKey: "minioadmin"
|
|||
|
|
UseSSL: false
|
|||
|
|
Buckets:
|
|||
|
|
Textures: "carrot-skin-textures"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 安全特性
|
|||
|
|
|
|||
|
|
### 文件上传安全
|
|||
|
|
|
|||
|
|
- **格式限制**: 仅允许PNG格式文件
|
|||
|
|
- **大小限制**: 文件大小限制在1KB-1MB之间
|
|||
|
|
- **时效控制**: 预签名URL 15分钟过期
|
|||
|
|
- **内容验证**: MinIO层面的Content-Type验证
|
|||
|
|
|
|||
|
|
### 权限控制
|
|||
|
|
|
|||
|
|
- **所有权验证**: 用户只能删除/更新自己上传的材质
|
|||
|
|
- **参数校验**: 所有接口都有完整的输入验证
|
|||
|
|
- **错误处理**: 不暴露敏感的系统信息
|
|||
|
|
|
|||
|
|
### 数据完整性
|
|||
|
|
|
|||
|
|
- **哈希去重**: SHA-256确保文件唯一性
|
|||
|
|
- **事务处理**: 数据库操作的原子性保证
|
|||
|
|
- **文件同步**: 删除材质时同步清理MinIO文件
|
|||
|
|
|
|||
|
|
## 性能优化
|
|||
|
|
|
|||
|
|
### 查询优化
|
|||
|
|
|
|||
|
|
- **分页查询**: 支持高效的大数据量分页
|
|||
|
|
- **索引优化**: 针对常用查询场景建立复合索引
|
|||
|
|
- **缓存机制**: go-zero内置的Redis缓存层
|
|||
|
|
|
|||
|
|
### 存储优化
|
|||
|
|
|
|||
|
|
- **智能去重**: 相同文件自动复用,节省存储空间
|
|||
|
|
- **分类存储**: 按材质类型和用户分目录存储
|
|||
|
|
|
|||
|
|
## 部署说明
|
|||
|
|
|
|||
|
|
### 环境要求
|
|||
|
|
|
|||
|
|
- Go 1.19+
|
|||
|
|
- MySQL 8.0+
|
|||
|
|
- Redis 6.0+
|
|||
|
|
- MinIO Server
|
|||
|
|
|
|||
|
|
### 启动服务
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 1. 安装依赖
|
|||
|
|
go mod tidy
|
|||
|
|
|
|||
|
|
# 2. 生成代码(如果修改了proto或sql文件)
|
|||
|
|
goctl rpc protoc docs/textures.proto --go_out=./pb --go-grpc_out=./pb --zrpc_out=.
|
|||
|
|
goctl model mysql ddl --src="docs/textures.sql" --dir="./internal/model"
|
|||
|
|
|
|||
|
|
# 3. 启动服务
|
|||
|
|
go run textures.go -f etc/textures.yaml
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Docker 部署
|
|||
|
|
|
|||
|
|
```dockerfile
|
|||
|
|
FROM golang:1.19-alpine AS builder
|
|||
|
|
WORKDIR /app
|
|||
|
|
COPY . .
|
|||
|
|
RUN go mod tidy && go build -o textures textures.go
|
|||
|
|
|
|||
|
|
FROM alpine:latest
|
|||
|
|
RUN apk --no-cache add ca-certificates
|
|||
|
|
WORKDIR /root/
|
|||
|
|
COPY --from=builder /app/textures .
|
|||
|
|
COPY --from=builder /app/etc ./etc
|
|||
|
|
CMD ["./textures", "-f", "etc/textures.yaml"]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 监控与日志
|
|||
|
|
|
|||
|
|
### 日志记录
|
|||
|
|
|
|||
|
|
- **操作日志**: 记录所有材质操作的详细信息
|
|||
|
|
- **错误日志**: 详细的错误堆栈和上下文信息
|
|||
|
|
- **性能日志**: 查询耗时和系统性能指标
|
|||
|
|
|
|||
|
|
### 监控指标
|
|||
|
|
|
|||
|
|
- **接口调用量**: 各接口的QPS统计
|
|||
|
|
- **存储使用量**: MinIO存储空间使用情况
|
|||
|
|
- **缓存命中率**: Redis缓存效果监控
|
|||
|
|
- **错误率统计**: 接口错误率和错误类型分析
|
|||
|
|
|
|||
|
|
## 开发指南
|
|||
|
|
|
|||
|
|
### 添加新接口
|
|||
|
|
|
|||
|
|
1. 在 `docs/textures.proto` 中定义新的RPC方法
|
|||
|
|
2. 运行 `goctl rpc protoc` 生成代码
|
|||
|
|
3. 在 `internal/logic/` 中实现业务逻辑
|
|||
|
|
4. 添加相应的数据库查询方法(如需要)
|
|||
|
|
|
|||
|
|
### 自定义数据库查询
|
|||
|
|
|
|||
|
|
在 `internal/model/texturesModel.go` 中添加自定义查询方法:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 在 TexturesModel 接口中添加方法声明
|
|||
|
|
type TexturesModel interface {
|
|||
|
|
texturesModel
|
|||
|
|
// 自定义方法
|
|||
|
|
FindByCustomCondition(ctx context.Context, condition string) ([]*Textures, error)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 在 customTexturesModel 中实现方法
|
|||
|
|
func (m *customTexturesModel) FindByCustomCondition(ctx context.Context, condition string) ([]*Textures, error) {
|
|||
|
|
query := `SELECT * FROM textures WHERE custom_field = ?`
|
|||
|
|
var resp []*Textures
|
|||
|
|
err := m.QueryRowsNoCacheCtx(ctx, &resp, query, condition)
|
|||
|
|
return resp, err
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 故障排查
|
|||
|
|
|
|||
|
|
### 常见问题
|
|||
|
|
|
|||
|
|
1. **上传失败**: 检查MinIO连接和存储桶配置
|
|||
|
|
2. **查询缓慢**: 检查数据库索引和Redis缓存
|
|||
|
|
3. **文件不一致**: 检查MinIO文件清理逻辑
|
|||
|
|
4. **权限错误**: 检查用户ID和材质所有权
|
|||
|
|
|
|||
|
|
### 调试技巧
|
|||
|
|
|
|||
|
|
- 启用详细日志: 设置日志级别为 `debug`
|
|||
|
|
- 检查MinIO状态: 使用MinIO控制台查看文件状态
|
|||
|
|
- 监控数据库: 使用慢查询日志分析性能问题
|