package app import ( "git.echol.cn/loser/st/server/global" "git.echol.cn/loser/st/server/middleware" "git.echol.cn/loser/st/server/model/app/request" "git.echol.cn/loser/st/server/model/common/response" "github.com/gin-gonic/gin" "go.uber.org/zap" "io" "strconv" ) type CharacterApi struct{} // GetPublicCharacterList 获取公开角色卡列表(无需鉴权) // @Summary 获取公开角色卡列表 // @Tags 角色卡 // @Accept json // @Produce json // @Param page query int false "页码" default(1) // @Param pageSize query int false "每页数量" default(20) // @Param keyword query string false "关键词" // @Param sortBy query string false "排序方式: newest, popular, mostChats, mostLikes" // @Success 200 {object} response.Response{data=response.CharacterListResponse} // @Router /app/character/public [get] func (ca *CharacterApi) GetPublicCharacterList(c *gin.Context) { var req request.CharacterListRequest req.Page, _ = strconv.Atoi(c.DefaultQuery("page", "1")) req.PageSize, _ = strconv.Atoi(c.DefaultQuery("pageSize", "20")) req.Keyword = c.Query("keyword") req.SortBy = c.Query("sortBy") if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 || req.PageSize > 100 { req.PageSize = 20 } // 尝试获取当前用户ID(如果已登录) userID := middleware.GetOptionalAppUserID(c) list, err := characterService.GetPublicCharacterList(req, userID) if err != nil { global.GVA_LOG.Error("获取公开角色卡列表失败", zap.Error(err)) response.FailWithMessage("获取列表失败", c) return } response.OkWithData(list, c) } // GetMyCharacterList 获取我的角色卡列表 // @Summary 获取我的角色卡列表 // @Tags 角色卡 // @Accept json // @Produce json // @Param page query int false "页码" default(1) // @Param pageSize query int false "每页数量" default(20) // @Param keyword query string false "关键词" // @Param sortBy query string false "排序方式" // @Success 200 {object} response.Response{data=response.CharacterListResponse} // @Router /app/character/my [get] // @Security ApiKeyAuth func (ca *CharacterApi) GetMyCharacterList(c *gin.Context) { userID := middleware.GetAppUserID(c) var req request.CharacterListRequest req.Page, _ = strconv.Atoi(c.DefaultQuery("page", "1")) req.PageSize, _ = strconv.Atoi(c.DefaultQuery("pageSize", "20")) req.Keyword = c.Query("keyword") req.SortBy = c.Query("sortBy") if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 || req.PageSize > 100 { req.PageSize = 20 } list, err := characterService.GetMyCharacterList(req, userID) if err != nil { global.GVA_LOG.Error("获取我的角色卡列表失败", zap.Error(err)) response.FailWithMessage("获取列表失败", c) return } response.OkWithData(list, c) } // GetCharacterDetail 获取角色卡详情 // @Summary 获取角色卡详情 // @Tags 角色卡 // @Accept json // @Produce json // @Param id path int true "角色卡ID" // @Success 200 {object} response.Response{data=response.CharacterResponse} // @Router /app/character/:id [get] func (ca *CharacterApi) GetCharacterDetail(c *gin.Context) { idStr := c.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { response.FailWithMessage("无效的ID", c) return } // 尝试获取当前用户ID(如果已登录) userID := middleware.GetOptionalAppUserID(c) character, err := characterService.GetCharacterDetail(uint(id), userID) if err != nil { global.GVA_LOG.Error("获取角色卡详情失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } response.OkWithData(character, c) } // CreateCharacter 创建角色卡 // @Summary 创建角色卡 // @Tags 角色卡 // @Accept json // @Produce json // @Param data body request.CreateCharacterRequest true "角色卡信息" // @Success 200 {object} response.Response{data=response.CharacterResponse} // @Router /app/character [post] // @Security ApiKeyAuth func (ca *CharacterApi) CreateCharacter(c *gin.Context) { userID := middleware.GetAppUserID(c) var req request.CreateCharacterRequest if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage(err.Error(), c) return } character, err := characterService.CreateCharacter(req, userID) if err != nil { global.GVA_LOG.Error("创建角色卡失败", zap.Error(err)) response.FailWithMessage("创建失败", c) return } response.OkWithData(character, c) } // UpdateCharacter 更新角色卡 // @Summary 更新角色卡 // @Tags 角色卡 // @Accept json // @Produce json // @Param data body request.UpdateCharacterRequest true "角色卡信息" // @Success 200 {object} response.Response{data=response.CharacterResponse} // @Router /app/character [put] // @Security ApiKeyAuth func (ca *CharacterApi) UpdateCharacter(c *gin.Context) { userID := middleware.GetAppUserID(c) var req request.UpdateCharacterRequest if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage(err.Error(), c) return } character, err := characterService.UpdateCharacter(req, userID) if err != nil { global.GVA_LOG.Error("更新角色卡失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } response.OkWithData(character, c) } // DeleteCharacter 删除角色卡 // @Summary 删除角色卡 // @Tags 角色卡 // @Accept json // @Produce json // @Param id path int true "角色卡ID" // @Success 200 {object} response.Response // @Router /app/character/:id [delete] // @Security ApiKeyAuth func (ca *CharacterApi) DeleteCharacter(c *gin.Context) { userID := middleware.GetAppUserID(c) idStr := c.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { response.FailWithMessage("无效的ID", c) return } err = characterService.DeleteCharacter(uint(id), userID) if err != nil { global.GVA_LOG.Error("删除角色卡失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } response.OkWithMessage("删除成功", c) } // ToggleFavorite 切换收藏状态 // @Summary 切换收藏状态 // @Tags 角色卡 // @Accept json // @Produce json // @Param data body request.CharacterActionRequest true "角色卡ID" // @Success 200 {object} response.Response{data=map[string]bool} // @Router /app/character/favorite [post] // @Security ApiKeyAuth func (ca *CharacterApi) ToggleFavorite(c *gin.Context) { userID := middleware.GetAppUserID(c) var req request.CharacterActionRequest if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage(err.Error(), c) return } isFavorited, err := characterService.ToggleFavorite(req.CharacterID, userID) if err != nil { global.GVA_LOG.Error("切换收藏失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } response.OkWithData(gin.H{"isFavorited": isFavorited}, c) } // LikeCharacter 点赞角色卡 // @Summary 点赞角色卡 // @Tags 角色卡 // @Accept json // @Produce json // @Param data body request.CharacterActionRequest true "角色卡ID" // @Success 200 {object} response.Response // @Router /app/character/like [post] func (ca *CharacterApi) LikeCharacter(c *gin.Context) { var req request.CharacterActionRequest if err := c.ShouldBindJSON(&req); err != nil { response.FailWithMessage(err.Error(), c) return } err := characterService.LikeCharacter(req.CharacterID) if err != nil { global.GVA_LOG.Error("点赞失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } response.OkWithMessage("点赞成功", c) } // ExportCharacter 导出角色卡为 JSON // @Summary 导出角色卡为 JSON // @Tags 角色卡 // @Accept json // @Produce json // @Param id path int true "角色卡ID" // @Success 200 {object} map[string]interface{} // @Router /app/character/:id/export [get] func (ca *CharacterApi) ExportCharacter(c *gin.Context) { idStr := c.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { response.FailWithMessage("无效的ID", c) return } // 尝试获取当前用户ID(如果已登录) userID := middleware.GetOptionalAppUserID(c) exportData, err := characterService.ExportCharacter(uint(id), userID) if err != nil { global.GVA_LOG.Error("导出角色卡失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } // 设置下载文件头 c.Header("Content-Type", "application/json") c.Header("Content-Disposition", "attachment; filename=character.json") c.JSON(200, exportData) } // ExportCharacterAsPNG 导出角色卡为 PNG // @Summary 导出角色卡为 PNG // @Tags 角色卡 // @Accept json // @Produce octet-stream // @Param id path int true "角色卡ID" // @Success 200 {file} binary // @Router /app/character/:id/export/png [get] func (ca *CharacterApi) ExportCharacterAsPNG(c *gin.Context) { idStr := c.Param("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { response.FailWithMessage("无效的ID", c) return } // 尝试获取当前用户ID(如果已登录) userID := middleware.GetOptionalAppUserID(c) pngData, err := characterService.ExportCharacterAsPNG(uint(id), userID) if err != nil { global.GVA_LOG.Error("导出 PNG 角色卡失败", zap.Error(err)) response.FailWithMessage(err.Error(), c) return } // 设置下载文件头 c.Header("Content-Type", "image/png") c.Header("Content-Disposition", "attachment; filename=character.png") c.Data(200, "image/png", pngData) } // ImportCharacter 导入角色卡 // @Summary 导入角色卡(支持 PNG 和 JSON) // @Tags 角色卡 // @Accept multipart/form-data // @Produce json // @Param file formData file true "角色卡文件(PNG 或 JSON)" // @Param isPublic formData bool false "是否公开" // @Success 200 {object} response.Response{data=response.CharacterResponse} // @Router /app/character/import [post] // @Security ApiKeyAuth func (ca *CharacterApi) ImportCharacter(c *gin.Context) { userID := middleware.GetAppUserID(c) // 获取上传的文件 file, err := c.FormFile("file") if err != nil { global.GVA_LOG.Error("获取上传文件失败", zap.Error(err)) response.FailWithMessage("请上传文件", c) return } // 检查文件大小(最大 10MB) maxSize := int64(10 * 1024 * 1024) if file.Size > maxSize { response.FailWithMessage("文件大小不能超过 10MB", c) return } global.GVA_LOG.Info("接收到文件上传", zap.String("filename", file.Filename), zap.Int64("size", file.Size)) // 读取文件内容 src, err := file.Open() if err != nil { global.GVA_LOG.Error("打开文件失败", zap.Error(err)) response.FailWithMessage("读取文件失败", c) return } defer src.Close() // 使用 io.ReadAll 读取完整文件内容 fileData, err := io.ReadAll(src) if err != nil { global.GVA_LOG.Error("读取文件内容失败", zap.Error(err)) response.FailWithMessage("读取文件内容失败: "+err.Error(), c) return } global.GVA_LOG.Info("文件读取成功", zap.Int("bytes", len(fileData))) // 获取是否公开参数 isPublic := c.DefaultPostForm("isPublic", "false") == "true" // 导入角色卡 character, err := characterService.ImportCharacter(fileData, file.Filename, userID, isPublic) if err != nil { global.GVA_LOG.Error("导入角色卡失败", zap.Error(err), zap.String("filename", file.Filename)) response.FailWithMessage(err.Error(), c) return } global.GVA_LOG.Info("角色卡导入成功", zap.Uint("characterId", character.ID), zap.String("name", character.Name)) response.OkWithData(character, c) }