package handler import ( "carrotskin/internal/container" "carrotskin/internal/model" "carrotskin/internal/service" "carrotskin/internal/types" "strconv" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // TextureHandler 材质处理器(依赖注入版本) type TextureHandler struct { container *container.Container logger *zap.Logger } // NewTextureHandler 创建TextureHandler实例 func NewTextureHandler(c *container.Container) *TextureHandler { return &TextureHandler{ container: c, logger: c.Logger, } } // Get 获取材质详情 func (h *TextureHandler) Get(c *gin.Context) { id, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } texture, err := h.container.TextureService.GetByID(c.Request.Context(), id) if err != nil { RespondNotFound(c, err.Error()) return } RespondSuccess(c, TextureToTextureInfo(texture)) } // Search 搜索材质 func (h *TextureHandler) Search(c *gin.Context) { keyword := c.Query("keyword") textureTypeStr := c.Query("type") publicOnly := c.Query("public_only") == "true" page := parseIntWithDefault(c.DefaultQuery("page", "1"), 1) pageSize := parseIntWithDefault(c.DefaultQuery("page_size", "20"), 20) var textureType model.TextureType switch textureTypeStr { case "SKIN": textureType = model.TextureTypeSkin case "CAPE": textureType = model.TextureTypeCape } textures, total, err := h.container.TextureService.Search(c.Request.Context(), keyword, textureType, publicOnly, page, pageSize) if err != nil { h.logger.Error("搜索材质失败", zap.String("keyword", keyword), zap.Error(err)) RespondServerError(c, "搜索材质失败", err) return } // 返回格式: // { // "code": 200, // "message": "操作成功", // "data": { // "list": [...], // "total": 1, // "page": 1, // "per_page": 5 // } // } RespondSuccess(c, gin.H{ "list": TexturesToTextureInfos(textures), "total": total, "page": page, "per_page": pageSize, }) } // RenderTexture 渲染皮肤/披风预览 func (h *TextureHandler) RenderTexture(c *gin.Context) { textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } renderType := service.RenderType(c.DefaultQuery("type", string(service.RenderTypeIsometric))) size := parseIntWithDefault(c.DefaultQuery("size", "256"), 256) format := service.ImageFormat(c.DefaultQuery("format", string(service.ImageFormatPNG))) result, err := h.container.TextureRenderService.RenderTexture(c.Request.Context(), textureID, renderType, size, format) if err != nil { RespondBadRequest(c, err.Error(), err) return } RespondSuccess(c, toRenderResponse(result)) } // RenderAvatar 渲染头像(2D/3D) func (h *TextureHandler) RenderAvatar(c *gin.Context) { textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } mode := service.AvatarMode(c.DefaultQuery("mode", string(service.AvatarMode2D))) size := parseIntWithDefault(c.DefaultQuery("size", "256"), 256) format := service.ImageFormat(c.DefaultQuery("format", string(service.ImageFormatPNG))) result, err := h.container.TextureRenderService.RenderAvatar(c.Request.Context(), textureID, size, mode, format) if err != nil { RespondBadRequest(c, err.Error(), err) return } RespondSuccess(c, toRenderResponse(result)) } // RenderCape 渲染披风 func (h *TextureHandler) RenderCape(c *gin.Context) { textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } size := parseIntWithDefault(c.DefaultQuery("size", "256"), 256) format := service.ImageFormat(c.DefaultQuery("format", string(service.ImageFormatPNG))) result, err := h.container.TextureRenderService.RenderCape(c.Request.Context(), textureID, size, format) if err != nil { RespondBadRequest(c, err.Error(), err) return } RespondSuccess(c, toRenderResponse(result)) } // RenderPreview 自动选择预览(皮肤走等距,披风走披风渲染) func (h *TextureHandler) RenderPreview(c *gin.Context) { textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } size := parseIntWithDefault(c.DefaultQuery("size", "256"), 256) format := service.ImageFormat(c.DefaultQuery("format", string(service.ImageFormatPNG))) result, err := h.container.TextureRenderService.RenderPreview(c.Request.Context(), textureID, size, format) if err != nil { RespondBadRequest(c, err.Error(), err) return } RespondSuccess(c, toRenderResponse(result)) } // toRenderResponse 转换为API响应 func toRenderResponse(r *service.RenderResult) *types.RenderResponse { if r == nil { return nil } resp := &types.RenderResponse{ URL: r.URL, ContentType: r.ContentType, ETag: r.ETag, Size: r.Size, } if !r.LastModified.IsZero() { t := r.LastModified resp.LastModified = &t } return resp } // Update 更新材质 func (h *TextureHandler) Update(c *gin.Context) { userID, ok := GetUserIDFromContext(c) if !ok { return } textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } var req types.UpdateTextureRequest if err := c.ShouldBindJSON(&req); err != nil { RespondBadRequest(c, "请求参数错误", err) return } texture, err := h.container.TextureService.Update(c.Request.Context(), textureID, userID, req.Name, req.Description, req.IsPublic) if err != nil { h.logger.Error("更新材质失败", zap.Int64("user_id", userID), zap.Int64("texture_id", textureID), zap.Error(err), ) RespondForbidden(c, err.Error()) return } RespondSuccess(c, TextureToTextureInfo(texture)) } // Delete 删除材质 func (h *TextureHandler) Delete(c *gin.Context) { userID, ok := GetUserIDFromContext(c) if !ok { return } textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } if err := h.container.TextureService.Delete(c.Request.Context(), textureID, userID); err != nil { h.logger.Error("删除材质失败", zap.Int64("user_id", userID), zap.Int64("texture_id", textureID), zap.Error(err), ) RespondForbidden(c, err.Error()) return } RespondSuccess(c, nil) } // ToggleFavorite 切换收藏状态 func (h *TextureHandler) ToggleFavorite(c *gin.Context) { userID, ok := GetUserIDFromContext(c) if !ok { return } textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } isFavorited, err := h.container.TextureService.ToggleFavorite(c.Request.Context(), userID, textureID) if err != nil { h.logger.Error("切换收藏状态失败", zap.Int64("user_id", userID), zap.Int64("texture_id", textureID), zap.Error(err), ) RespondBadRequest(c, err.Error(), nil) return } RespondSuccess(c, map[string]bool{"is_favorited": isFavorited}) } // GetUserTextures 获取用户上传的材质列表 func (h *TextureHandler) GetUserTextures(c *gin.Context) { userID, ok := GetUserIDFromContext(c) if !ok { return } page := parseIntWithDefault(c.DefaultQuery("page", "1"), 1) pageSize := parseIntWithDefault(c.DefaultQuery("page_size", "20"), 20) textures, total, err := h.container.TextureService.GetByUserID(c.Request.Context(), userID, page, pageSize) if err != nil { h.logger.Error("获取用户材质列表失败", zap.Int64("user_id", userID), zap.Error(err)) RespondServerError(c, "获取材质列表失败", err) return } RespondSuccess(c, gin.H{ "list": TexturesToTextureInfos(textures), "total": total, "page": page, "per_page": pageSize, }) } // GetUserFavorites 获取用户收藏的材质列表 func (h *TextureHandler) GetUserFavorites(c *gin.Context) { userID, ok := GetUserIDFromContext(c) if !ok { return } page := parseIntWithDefault(c.DefaultQuery("page", "1"), 1) pageSize := parseIntWithDefault(c.DefaultQuery("page_size", "20"), 20) textures, total, err := h.container.TextureService.GetUserFavorites(c.Request.Context(), userID, page, pageSize) if err != nil { h.logger.Error("获取用户收藏列表失败", zap.Int64("user_id", userID), zap.Error(err)) RespondServerError(c, "获取收藏列表失败", err) return } RespondSuccess(c, gin.H{ "list": TexturesToTextureInfos(textures), "total": total, "page": page, "per_page": pageSize, }) } // Upload 直接上传材质文件 func (h *TextureHandler) Upload(c *gin.Context) { userID, ok := GetUserIDFromContext(c) if !ok { return } // 解析multipart表单 if err := c.Request.ParseMultipartForm(32 << 20); err != nil { // 32MB RespondBadRequest(c, "解析表单失败", err) return } // 获取文件 file, err := c.FormFile("file") if err != nil { RespondBadRequest(c, "获取文件失败", err) return } // 读取文件内容 src, err := file.Open() if err != nil { RespondBadRequest(c, "打开文件失败", err) return } defer src.Close() fileData := make([]byte, file.Size) if _, err := src.Read(fileData); err != nil { RespondBadRequest(c, "读取文件失败", err) return } // 获取表单字段 name := c.PostForm("name") if name == "" { RespondBadRequest(c, "名称不能为空", nil) return } description := c.PostForm("description") textureType := c.PostForm("type") if textureType == "" { textureType = "SKIN" // 默认值 } isPublic := c.PostForm("is_public") == "true" isSlim := c.PostForm("is_slim") == "true" // 检查上传限制 maxTextures := h.container.UserService.GetMaxTexturesPerUser() if err := h.container.TextureService.CheckUploadLimit(c.Request.Context(), userID, maxTextures); err != nil { RespondBadRequest(c, err.Error(), nil) return } // 调用服务上传 texture, err := h.container.TextureService.UploadTexture( c.Request.Context(), userID, name, description, textureType, fileData, file.Filename, isPublic, isSlim, ) if err != nil { h.logger.Error("上传材质失败", zap.Int64("user_id", userID), zap.String("file_name", file.Filename), zap.Error(err), ) RespondBadRequest(c, err.Error(), nil) return } RespondSuccess(c, TextureToTextureInfo(texture)) }