393 lines
11 KiB
Go
393 lines
11 KiB
Go
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)
|
||
}
|