10 KiB
10 KiB
皮肤服务 (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 |
上传流程说明:
- 客户端调用
GenerateTextureUploadURL获取预签名上传URL和表单数据 - 客户端使用返回的
post_url和form_data直接向MinIO上传文件 - 上传成功后,客户端调用
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)
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; // 更新时间
}
材质类型枚举
enum TextureType {
SKIN = 0; // 皮肤
CAPE = 1; // 披风
}
收藏夹相关数据模型
收藏材质信息 (FavoriteTextureInfo)
message FavoriteTextureInfo {
TextureInfo texture = 1; // 材质信息
string favorite_at = 2; // 收藏时间
}
存储结构
MinIO 对象存储结构
textures/ # 存储桶根目录
├── skins/ # 皮肤材质目录
│ └── user_{userId}/ # 按用户分组
│ └── {timestamp}_{filename}.png
└── capes/ # 披风材质目录
└── user_{userId}/ # 按用户分组
└── {timestamp}_{filename}.png
数据库表结构
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 表
-- 用户材质收藏表
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)
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
启动服务
# 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 部署
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缓存效果监控
- 错误率统计: 接口错误率和错误类型分析
开发指南
添加新接口
- 在
docs/textures.proto中定义新的RPC方法 - 运行
goctl rpc protoc生成代码 - 在
internal/logic/中实现业务逻辑 - 添加相应的数据库查询方法(如需要)
自定义数据库查询
在 internal/model/texturesModel.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
}
故障排查
常见问题
- 上传失败: 检查MinIO连接和存储桶配置
- 查询缓慢: 检查数据库索引和Redis缓存
- 文件不一致: 检查MinIO文件清理逻辑
- 权限错误: 检查用户ID和材质所有权
调试技巧
- 启用详细日志: 设置日志级别为
debug - 检查MinIO状态: 使用MinIO控制台查看文件状态
- 监控数据库: 使用慢查询日志分析性能问题