# 皮肤服务 (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控制台查看文件状态 - 监控数据库: 使用慢查询日志分析性能问题