2025-11-28 23:30:49 +08:00
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"carrotskin/internal/model"
|
|
|
|
|
"carrotskin/internal/service"
|
|
|
|
|
"carrotskin/internal/types"
|
|
|
|
|
"carrotskin/pkg/config"
|
|
|
|
|
"carrotskin/pkg/database"
|
|
|
|
|
"carrotskin/pkg/logger"
|
|
|
|
|
"carrotskin/pkg/storage"
|
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// GenerateTextureUploadURL 生成材质上传URL
|
|
|
|
|
// @Summary 生成材质上传URL
|
|
|
|
|
// @Description 生成预签名URL用于上传材质文件
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Security BearerAuth
|
|
|
|
|
// @Param request body types.GenerateTextureUploadURLRequest true "上传URL请求"
|
|
|
|
|
// @Success 200 {object} model.Response "生成成功"
|
|
|
|
|
// @Failure 400 {object} model.ErrorResponse "请求参数错误"
|
|
|
|
|
// @Router /api/v1/texture/upload-url [post]
|
|
|
|
|
func GenerateTextureUploadURL(c *gin.Context) {
|
2025-12-02 10:33:19 +08:00
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var req types.GenerateTextureUploadURLRequest
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, "请求参数错误", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
storageClient := storage.MustGetClient()
|
|
|
|
|
cfg := *config.MustGetRustFSConfig()
|
|
|
|
|
result, err := service.GenerateTextureUploadURL(
|
|
|
|
|
c.Request.Context(),
|
|
|
|
|
storageClient,
|
|
|
|
|
cfg,
|
2025-12-02 10:33:19 +08:00
|
|
|
userID,
|
2025-11-28 23:30:49 +08:00
|
|
|
req.FileName,
|
|
|
|
|
string(req.TextureType),
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.MustGetLogger().Error("生成材质上传URL失败",
|
2025-12-02 10:33:19 +08:00
|
|
|
zap.Int64("user_id", userID),
|
2025-11-28 23:30:49 +08:00
|
|
|
zap.String("file_name", req.FileName),
|
|
|
|
|
zap.String("texture_type", string(req.TextureType)),
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, err.Error(), nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondSuccess(c, &types.GenerateTextureUploadURLResponse{
|
2025-11-28 23:30:49 +08:00
|
|
|
PostURL: result.PostURL,
|
|
|
|
|
FormData: result.FormData,
|
|
|
|
|
TextureURL: result.FileURL,
|
2025-12-02 10:33:19 +08:00
|
|
|
ExpiresIn: 900,
|
|
|
|
|
})
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateTexture 创建材质记录
|
|
|
|
|
// @Summary 创建材质记录
|
|
|
|
|
// @Description 文件上传完成后,创建材质记录到数据库
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Security BearerAuth
|
|
|
|
|
// @Param request body types.CreateTextureRequest true "创建材质请求"
|
|
|
|
|
// @Success 200 {object} model.Response "创建成功"
|
|
|
|
|
// @Failure 400 {object} model.ErrorResponse "请求参数错误"
|
|
|
|
|
// @Router /api/v1/texture [post]
|
|
|
|
|
func CreateTexture(c *gin.Context) {
|
2025-12-02 10:33:19 +08:00
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var req types.CreateTextureRequest
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, "请求参数错误", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
maxTextures := service.GetMaxTexturesPerUser()
|
|
|
|
|
if err := service.CheckTextureUploadLimit(database.MustGetDB(), userID, maxTextures); err != nil {
|
|
|
|
|
RespondBadRequest(c, err.Error(), nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
texture, err := service.CreateTexture(database.MustGetDB(),
|
2025-12-02 10:33:19 +08:00
|
|
|
userID,
|
2025-11-28 23:30:49 +08:00
|
|
|
req.Name,
|
|
|
|
|
req.Description,
|
|
|
|
|
string(req.Type),
|
|
|
|
|
req.URL,
|
|
|
|
|
req.Hash,
|
|
|
|
|
req.Size,
|
|
|
|
|
req.IsPublic,
|
|
|
|
|
req.IsSlim,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.MustGetLogger().Error("创建材质失败",
|
2025-12-02 10:33:19 +08:00
|
|
|
zap.Int64("user_id", userID),
|
2025-11-28 23:30:49 +08:00
|
|
|
zap.String("name", req.Name),
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, err.Error(), nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondSuccess(c, TextureToTextureInfo(texture))
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetTexture 获取材质详情
|
|
|
|
|
// @Summary 获取材质详情
|
|
|
|
|
// @Description 根据ID获取材质详细信息
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Param id path int true "材质ID"
|
|
|
|
|
// @Success 200 {object} model.Response "获取成功"
|
|
|
|
|
// @Failure 404 {object} model.ErrorResponse "材质不存在"
|
|
|
|
|
// @Router /api/v1/texture/{id} [get]
|
|
|
|
|
func GetTexture(c *gin.Context) {
|
2025-12-02 10:33:19 +08:00
|
|
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, "无效的材质ID", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
texture, err := service.GetTextureByID(database.MustGetDB(), id)
|
|
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondNotFound(c, err.Error())
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondSuccess(c, TextureToTextureInfo(texture))
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SearchTextures 搜索材质
|
|
|
|
|
// @Summary 搜索材质
|
|
|
|
|
// @Description 根据关键词和类型搜索材质
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Param keyword query string false "关键词"
|
|
|
|
|
// @Param type query string false "材质类型(SKIN/CAPE)"
|
|
|
|
|
// @Param public_only query bool false "只看公开材质"
|
|
|
|
|
// @Param page query int false "页码" default(1)
|
|
|
|
|
// @Param page_size query int false "每页数量" default(20)
|
|
|
|
|
// @Success 200 {object} model.PaginationResponse "搜索成功"
|
|
|
|
|
// @Router /api/v1/texture [get]
|
|
|
|
|
func SearchTextures(c *gin.Context) {
|
|
|
|
|
keyword := c.Query("keyword")
|
|
|
|
|
textureTypeStr := c.Query("type")
|
|
|
|
|
publicOnly := c.Query("public_only") == "true"
|
|
|
|
|
|
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
|
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
|
|
|
|
|
|
|
|
var textureType model.TextureType
|
|
|
|
|
switch textureTypeStr {
|
|
|
|
|
case "SKIN":
|
|
|
|
|
textureType = model.TextureTypeSkin
|
|
|
|
|
case "CAPE":
|
|
|
|
|
textureType = model.TextureTypeCape
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textures, total, err := service.SearchTextures(database.MustGetDB(), keyword, textureType, publicOnly, page, pageSize)
|
|
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
logger.MustGetLogger().Error("搜索材质失败", zap.String("keyword", keyword), zap.Error(err))
|
|
|
|
|
RespondServerError(c, "搜索材质失败", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
c.JSON(200, model.NewPaginationResponse(TexturesToTextureInfos(textures), total, page, pageSize))
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UpdateTexture 更新材质
|
|
|
|
|
// @Summary 更新材质
|
|
|
|
|
// @Description 更新材质信息(仅上传者可操作)
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Security BearerAuth
|
|
|
|
|
// @Param id path int true "材质ID"
|
|
|
|
|
// @Param request body types.UpdateTextureRequest true "更新材质请求"
|
|
|
|
|
// @Success 200 {object} model.Response "更新成功"
|
|
|
|
|
// @Failure 403 {object} model.ErrorResponse "无权操作"
|
|
|
|
|
// @Router /api/v1/texture/{id} [put]
|
|
|
|
|
func UpdateTexture(c *gin.Context) {
|
2025-12-02 10:33:19 +08:00
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
textureID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, "无效的材质ID", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var req types.UpdateTextureRequest
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, "请求参数错误", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
texture, err := service.UpdateTexture(database.MustGetDB(), textureID, userID, req.Name, req.Description, req.IsPublic)
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
|
|
|
|
logger.MustGetLogger().Error("更新材质失败",
|
2025-12-02 10:33:19 +08:00
|
|
|
zap.Int64("user_id", userID),
|
2025-11-28 23:30:49 +08:00
|
|
|
zap.Int64("texture_id", textureID),
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondForbidden(c, err.Error())
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondSuccess(c, TextureToTextureInfo(texture))
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeleteTexture 删除材质
|
|
|
|
|
// @Summary 删除材质
|
|
|
|
|
// @Description 删除材质(软删除,仅上传者可操作)
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Security BearerAuth
|
|
|
|
|
// @Param id path int true "材质ID"
|
|
|
|
|
// @Success 200 {object} model.Response "删除成功"
|
|
|
|
|
// @Failure 403 {object} model.ErrorResponse "无权操作"
|
|
|
|
|
// @Router /api/v1/texture/{id} [delete]
|
|
|
|
|
func DeleteTexture(c *gin.Context) {
|
2025-12-02 10:33:19 +08:00
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
textureID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, "无效的材质ID", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
if err := service.DeleteTexture(database.MustGetDB(), textureID, userID); err != nil {
|
2025-11-28 23:30:49 +08:00
|
|
|
logger.MustGetLogger().Error("删除材质失败",
|
2025-12-02 10:33:19 +08:00
|
|
|
zap.Int64("user_id", userID),
|
2025-11-28 23:30:49 +08:00
|
|
|
zap.Int64("texture_id", textureID),
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondForbidden(c, err.Error())
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondSuccess(c, nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ToggleFavorite 切换收藏状态
|
|
|
|
|
// @Summary 切换收藏状态
|
|
|
|
|
// @Description 收藏或取消收藏材质
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Security BearerAuth
|
|
|
|
|
// @Param id path int true "材质ID"
|
|
|
|
|
// @Success 200 {object} model.Response "切换成功"
|
|
|
|
|
// @Router /api/v1/texture/{id}/favorite [post]
|
|
|
|
|
func ToggleFavorite(c *gin.Context) {
|
2025-12-02 10:33:19 +08:00
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
textureID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, "无效的材质ID", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
isFavorited, err := service.ToggleTextureFavorite(database.MustGetDB(), userID, textureID)
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
|
|
|
|
logger.MustGetLogger().Error("切换收藏状态失败",
|
2025-12-02 10:33:19 +08:00
|
|
|
zap.Int64("user_id", userID),
|
2025-11-28 23:30:49 +08:00
|
|
|
zap.Int64("texture_id", textureID),
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
)
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondBadRequest(c, err.Error(), nil)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
RespondSuccess(c, map[string]bool{"is_favorited": isFavorited})
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetUserTextures 获取用户上传的材质列表
|
|
|
|
|
// @Summary 获取用户上传的材质列表
|
|
|
|
|
// @Description 获取当前用户上传的所有材质
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Security BearerAuth
|
|
|
|
|
// @Param page query int false "页码" default(1)
|
|
|
|
|
// @Param page_size query int false "每页数量" default(20)
|
|
|
|
|
// @Success 200 {object} model.PaginationResponse "获取成功"
|
|
|
|
|
// @Router /api/v1/texture/my [get]
|
|
|
|
|
func GetUserTextures(c *gin.Context) {
|
2025-12-02 10:33:19 +08:00
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
|
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
textures, total, err := service.GetUserTextures(database.MustGetDB(), userID, page, pageSize)
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
logger.MustGetLogger().Error("获取用户材质列表失败", zap.Int64("user_id", userID), zap.Error(err))
|
|
|
|
|
RespondServerError(c, "获取材质列表失败", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
c.JSON(200, model.NewPaginationResponse(TexturesToTextureInfos(textures), total, page, pageSize))
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetUserFavorites 获取用户收藏的材质列表
|
|
|
|
|
// @Summary 获取用户收藏的材质列表
|
|
|
|
|
// @Description 获取当前用户收藏的所有材质
|
|
|
|
|
// @Tags texture
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
|
|
|
|
// @Security BearerAuth
|
|
|
|
|
// @Param page query int false "页码" default(1)
|
|
|
|
|
// @Param page_size query int false "每页数量" default(20)
|
|
|
|
|
// @Success 200 {object} model.PaginationResponse "获取成功"
|
|
|
|
|
// @Router /api/v1/texture/favorites [get]
|
|
|
|
|
func GetUserFavorites(c *gin.Context) {
|
2025-12-02 10:33:19 +08:00
|
|
|
userID, ok := GetUserIDFromContext(c)
|
|
|
|
|
if !ok {
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
|
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
textures, total, err := service.GetUserTextureFavorites(database.MustGetDB(), userID, page, pageSize)
|
2025-11-28 23:30:49 +08:00
|
|
|
if err != nil {
|
2025-12-02 10:33:19 +08:00
|
|
|
logger.MustGetLogger().Error("获取用户收藏列表失败", zap.Int64("user_id", userID), zap.Error(err))
|
|
|
|
|
RespondServerError(c, "获取收藏列表失败", err)
|
2025-11-28 23:30:49 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 10:33:19 +08:00
|
|
|
c.JSON(200, model.NewPaginationResponse(TexturesToTextureInfos(textures), total, page, pageSize))
|
2025-11-28 23:30:49 +08:00
|
|
|
}
|