Files
frontend/skinserviceREADME .md

10 KiB
Raw Blame History

皮肤服务 (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_urlform_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)

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缓存效果监控
  • 错误率统计: 接口错误率和错误类型分析

开发指南

添加新接口

  1. docs/textures.proto 中定义新的RPC方法
  2. 运行 goctl rpc protoc 生成代码
  3. internal/logic/ 中实现业务逻辑
  4. 添加相应的数据库查询方法(如需要)

自定义数据库查询

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
}

故障排查

常见问题

  1. 上传失败: 检查MinIO连接和存储桶配置
  2. 查询缓慢: 检查数据库索引和Redis缓存
  3. 文件不一致: 检查MinIO文件清理逻辑
  4. 权限错误: 检查用户ID和材质所有权

调试技巧

  • 启用详细日志: 设置日志级别为 debug
  • 检查MinIO状态: 使用MinIO控制台查看文件状态
  • 监控数据库: 使用慢查询日志分析性能问题