package handler import ( "net/http" "strconv" "carrotskin/internal/container" "carrotskin/internal/model" "carrotskin/internal/types" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // AdminHandler 管理员处理器 type AdminHandler struct { container *container.Container } // NewAdminHandler 创建管理员处理器 func NewAdminHandler(c *container.Container) *AdminHandler { return &AdminHandler{container: c} } // SetUserRoleRequest 设置用户角色请求 type SetUserRoleRequest struct { UserID int64 `json:"user_id" binding:"required"` Role string `json:"role" binding:"required,oneof=user admin"` } // SetUserRole 设置用户角色 // @Summary 设置用户角色 // @Description 管理员设置指定用户的角色 // @Tags Admin // @Accept json // @Produce json // @Param request body SetUserRoleRequest true "设置角色请求" // @Success 200 {object} model.Response{data=map[string]interface{}} "更新成功" // @Failure 400 {object} model.ErrorResponse "参数错误" // @Failure 403 {object} model.ErrorResponse "无权操作" // @Security BearerAuth // @Router /api/v1/admin/users/role [put] func (h *AdminHandler) SetUserRole(c *gin.Context) { var req SetUserRoleRequest if err := c.ShouldBindJSON(&req); err != nil { RespondBadRequest(c, "参数错误", err) return } // 获取当前操作者ID operatorID, _ := c.Get("user_id") // 不能修改自己的角色 if req.UserID == operatorID.(int64) { c.JSON(http.StatusBadRequest, model.NewErrorResponse( model.CodeBadRequest, "不能修改自己的角色", nil, )) return } // 检查目标用户是否存在 targetUser, err := h.container.UserRepo.FindByID(c.Request.Context(), req.UserID) if err != nil || targetUser == nil { c.JSON(http.StatusNotFound, model.NewErrorResponse( model.CodeNotFound, "用户不存在", nil, )) return } // 更新用户角色 err = h.container.UserRepo.UpdateFields(c.Request.Context(), req.UserID, map[string]interface{}{ "role": req.Role, }) if err != nil { RespondServerError(c, "更新用户角色失败", err) return } h.container.Logger.Info("管理员修改用户角色", zap.Int64("operator_id", operatorID.(int64)), zap.Int64("target_user_id", req.UserID), zap.String("new_role", req.Role), ) c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "message": "用户角色更新成功", "user_id": req.UserID, "role": req.Role, })) } // GetUserList 获取用户列表 // @Summary 获取用户列表 // @Description 管理员获取所有用户列表 // @Tags Admin // @Produce json // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(20) // @Success 200 {object} model.Response{data=map[string]interface{}} "获取成功" // @Failure 403 {object} model.ErrorResponse "无权操作" // @Security BearerAuth // @Router /api/v1/admin/users [get] func (h *AdminHandler) GetUserList(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } // 使用数据库直接查询用户列表 var users []model.User var total int64 db := h.container.DB db.Model(&model.User{}).Count(&total) db.Offset((page - 1) * pageSize).Limit(pageSize).Order("id DESC").Find(&users) // 构建响应(隐藏敏感信息) userList := make([]gin.H, len(users)) for i, u := range users { userList[i] = gin.H{ "id": u.ID, "username": u.Username, "email": u.Email, "avatar": u.Avatar, "role": u.Role, "status": u.Status, "points": u.Points, "last_login_at": u.LastLoginAt, "created_at": u.CreatedAt, } } c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "users": userList, "total": total, "page": page, "page_size": pageSize, })) } // GetUserDetail 获取用户详情 // @Summary 获取用户详情 // @Description 管理员获取指定用户的详细信息 // @Tags Admin // @Produce json // @Param id path int true "用户ID" // @Success 200 {object} model.Response{data=map[string]interface{}} "获取成功" // @Failure 404 {object} model.ErrorResponse "用户不存在" // @Security BearerAuth // @Router /api/v1/admin/users/{id} [get] func (h *AdminHandler) GetUserDetail(c *gin.Context) { userID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的用户ID", err) return } user, err := h.container.UserRepo.FindByID(c.Request.Context(), userID) if err != nil || user == nil { c.JSON(http.StatusNotFound, model.NewErrorResponse( model.CodeNotFound, "用户不存在", nil, )) return } c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "id": user.ID, "username": user.Username, "email": user.Email, "avatar": user.Avatar, "role": user.Role, "status": user.Status, "points": user.Points, "properties": user.Properties, "last_login_at": user.LastLoginAt, "created_at": user.CreatedAt, "updated_at": user.UpdatedAt, })) } // SetUserStatusRequest 设置用户状态请求 type SetUserStatusRequest struct { UserID int64 `json:"user_id" binding:"required"` Status int16 `json:"status" binding:"required,oneof=1 0 -1"` // 1:正常, 0:禁用, -1:删除 } // SetUserStatus 设置用户状态 // @Summary 设置用户状态 // @Description 管理员设置用户状态(启用/禁用) // @Tags Admin // @Accept json // @Produce json // @Param request body SetUserStatusRequest true "设置状态请求" // @Success 200 {object} model.Response{data=map[string]interface{}} "更新成功" // @Failure 400 {object} model.ErrorResponse "参数错误" // @Security BearerAuth // @Router /api/v1/admin/users/status [put] func (h *AdminHandler) SetUserStatus(c *gin.Context) { var req SetUserStatusRequest if err := c.ShouldBindJSON(&req); err != nil { RespondBadRequest(c, "参数错误", err) return } operatorID, _ := c.Get("user_id") // 不能修改自己的状态 if req.UserID == operatorID.(int64) { c.JSON(http.StatusBadRequest, model.NewErrorResponse( model.CodeBadRequest, "不能修改自己的状态", nil, )) return } // 检查目标用户是否存在 targetUser, err := h.container.UserRepo.FindByID(c.Request.Context(), req.UserID) if err != nil || targetUser == nil { c.JSON(http.StatusNotFound, model.NewErrorResponse( model.CodeNotFound, "用户不存在", nil, )) return } // 更新用户状态 err = h.container.UserRepo.UpdateFields(c.Request.Context(), req.UserID, map[string]interface{}{ "status": req.Status, }) if err != nil { RespondServerError(c, "更新用户状态失败", err) return } statusText := map[int16]string{1: "正常", 0: "禁用", -1: "删除"}[req.Status] h.container.Logger.Info("管理员修改用户状态", zap.Int64("operator_id", operatorID.(int64)), zap.Int64("target_user_id", req.UserID), zap.Int16("new_status", req.Status), ) c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "message": "用户状态更新成功", "user_id": req.UserID, "status": req.Status, "status_text": statusText, })) } // DeleteTexture 管理员删除材质 // @Summary 管理员删除材质 // @Description 管理员可以删除任意材质(用于审核不当内容) // @Tags Admin // @Produce json // @Param id path int true "材质ID" // @Success 200 {object} model.Response{data=map[string]interface{}} "删除成功" // @Failure 404 {object} model.ErrorResponse "材质不存在" // @Security BearerAuth // @Router /api/v1/admin/textures/{id} [delete] func (h *AdminHandler) DeleteTexture(c *gin.Context) { textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } operatorID, _ := c.Get("user_id") // 检查材质是否存在 var texture model.Texture if err := h.container.DB.First(&texture, textureID).Error; err != nil { c.JSON(http.StatusNotFound, model.NewErrorResponse( model.CodeNotFound, "材质不存在", nil, )) return } // 删除材质 if err := h.container.DB.Delete(&texture).Error; err != nil { RespondServerError(c, "删除材质失败", err) return } h.container.Logger.Info("管理员删除材质", zap.Int64("operator_id", operatorID.(int64)), zap.Int64("texture_id", textureID), zap.Int64("uploader_id", texture.UploaderID), zap.String("texture_name", texture.Name), ) c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "message": "材质删除成功", "texture_id": textureID, })) } // GetTextureList 管理员获取材质列表 // @Summary 管理员获取材质列表 // @Description 管理员获取所有材质列表(用于审核) // @Tags Admin // @Produce json // @Param page query int false "页码" default(1) // @Param page_size query int false "每页数量" default(20) // @Success 200 {object} model.Response{data=map[string]interface{}} "获取成功" // @Security BearerAuth // @Router /api/v1/admin/textures [get] func (h *AdminHandler) GetTextureList(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } var textures []model.Texture var total int64 db := h.container.DB db.Model(&model.Texture{}).Count(&total) db.Preload("Uploader").Offset((page - 1) * pageSize).Limit(pageSize).Order("id DESC").Find(&textures) // 构建响应 textureList := make([]gin.H, len(textures)) for i, t := range textures { uploaderName := "" if t.Uploader != nil { uploaderName = t.Uploader.Username } textureList[i] = gin.H{ "id": t.ID, "name": t.Name, "type": t.Type, "hash": t.Hash, "uploader_id": t.UploaderID, "uploader_name": uploaderName, "is_public": t.IsPublic, "download_count": t.DownloadCount, "created_at": t.CreatedAt, } } c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "textures": textureList, "total": total, "page": page, "page_size": pageSize, })) } // SearchUsers 搜索用户 // @Summary 搜索用户 // @Description 管理员根据条件搜索用户 // @Tags Admin // @Produce json // @Param keyword query string false "搜索关键词(用户名或邮箱)" // @Param role query string false "角色筛选" // @Param status query int false "状态筛选" // @Param sort_by query string false "排序字段" // @Param sort_desc query bool false "是否降序" // @Param page query int false "页码" // @Param page_size query int false "每页数量" // @Success 200 {object} model.Response{data=map[string]interface{}} "搜索成功" // @Security BearerAuth // @Router /api/v1/admin/users/search [get] func (h *AdminHandler) SearchUsers(c *gin.Context) { var req types.AdminUserSearchRequest if err := c.ShouldBindQuery(&req); err != nil { RespondBadRequest(c, "参数错误", err) return } if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 || req.PageSize > 100 { req.PageSize = 20 } db := h.container.DB.Model(&model.User{}) // 关键词搜索 if req.Keyword != "" { db = db.Where("username LIKE ? OR email LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%") } // 角色筛选 if req.Role != "" { db = db.Where("role = ?", req.Role) } // 状态筛选 if req.Status != nil { db = db.Where("status = ?", *req.Status) } // 排序 sortBy := "id" if req.SortBy != "" { sortBy = req.SortBy } order := sortBy if req.SortDesc { order += " DESC" } db = db.Order(order) // 分页 var total int64 db.Count(&total) var users []model.User db.Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find(&users) // 构建响应 userList := make([]gin.H, len(users)) for i, u := range users { userList[i] = gin.H{ "id": u.ID, "username": u.Username, "email": u.Email, "avatar": u.Avatar, "role": u.Role, "status": u.Status, "points": u.Points, "last_login_at": u.LastLoginAt, "created_at": u.CreatedAt, } } c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "users": userList, "total": total, "page": req.Page, "page_size": req.PageSize, })) } // DeleteUser 删除用户 // @Summary 删除用户 // @Description 管理员删除用户(软删除) // @Tags Admin // @Produce json // @Param id path int true "用户ID" // @Success 200 {object} model.Response{data=map[string]interface{}} "删除成功" // @Failure 400 {object} model.ErrorResponse "不能删除自己" // @Failure 404 {object} model.ErrorResponse "用户不存在" // @Security BearerAuth // @Router /api/v1/admin/users/{id} [delete] func (h *AdminHandler) DeleteUser(c *gin.Context) { userID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的用户ID", err) return } operatorID, _ := c.Get("user_id") // 不能删除自己 if userID == operatorID.(int64) { c.JSON(http.StatusBadRequest, model.NewErrorResponse( model.CodeBadRequest, "不能删除自己", nil, )) return } // 检查用户是否存在 user, err := h.container.UserRepo.FindByID(c.Request.Context(), userID) if err != nil || user == nil { c.JSON(http.StatusNotFound, model.NewErrorResponse( model.CodeNotFound, "用户不存在", nil, )) return } // 软删除 if err := h.container.DB.Delete(user).Error; err != nil { RespondServerError(c, "删除用户失败", err) return } h.container.Logger.Info("管理员删除用户", zap.Int64("operator_id", operatorID.(int64)), zap.Int64("deleted_user_id", userID), zap.String("username", user.Username), ) c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "message": "用户删除成功", "user_id": userID, })) } // BatchSetUserRole 批量设置用户角色 // @Summary 批量设置用户角色 // @Description 管理员批量设置多个用户的角色 // @Tags Admin // @Accept json // @Produce json // @Param request body map[string]interface{} true "批量设置请求" // @Success 200 {object} model.Response{data=map[string]interface{}} "设置成功" // @Failure 400 {object} model.ErrorResponse "参数错误" // @Security BearerAuth // @Router /api/v1/admin/users/batch-role [put] func (h *AdminHandler) BatchSetUserRole(c *gin.Context) { var req struct { UserIDs []int64 `json:"user_ids" binding:"required,min=1"` Role string `json:"role" binding:"required,oneof=user admin"` } if err := c.ShouldBindJSON(&req); err != nil { RespondBadRequest(c, "参数错误", err) return } operatorID, _ := c.Get("user_id") // 检查是否包含自己 for _, uid := range req.UserIDs { if uid == operatorID.(int64) { c.JSON(http.StatusBadRequest, model.NewErrorResponse( model.CodeBadRequest, "不能修改自己的角色", nil, )) return } } // 批量更新 if err := h.container.DB.Model(&model.User{}). Where("id IN ?", req.UserIDs). Update("role", req.Role).Error; err != nil { RespondServerError(c, "批量更新角色失败", err) return } h.container.Logger.Info("管理员批量设置用户角色", zap.Int64("operator_id", operatorID.(int64)), zap.Int("count", len(req.UserIDs)), zap.String("role", req.Role), ) c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "message": "批量设置角色成功", "count": len(req.UserIDs), "role": req.Role, })) } // BatchDeleteUsers 批量删除用户 // @Summary 批量删除用户 // @Description 管理员批量删除多个用户 // @Tags Admin // @Accept json // @Produce json // @Param request body map[string]interface{} true "批量删除请求" // @Success 200 {object} model.Response{data=map[string]interface{}} "删除成功" // @Failure 400 {object} model.ErrorResponse "参数错误" // @Security BearerAuth // @Router /api/v1/admin/users/batch-delete [delete] func (h *AdminHandler) BatchDeleteUsers(c *gin.Context) { var req struct { UserIDs []int64 `json:"user_ids" binding:"required,min=1"` } if err := c.ShouldBindJSON(&req); err != nil { RespondBadRequest(c, "参数错误", err) return } operatorID, _ := c.Get("user_id") // 检查是否包含自己 for _, uid := range req.UserIDs { if uid == operatorID.(int64) { c.JSON(http.StatusBadRequest, model.NewErrorResponse( model.CodeBadRequest, "不能删除自己", nil, )) return } } // 批量软删除 if err := h.container.DB.Where("id IN ?", req.UserIDs).Delete(&model.User{}).Error; err != nil { RespondServerError(c, "批量删除用户失败", err) return } h.container.Logger.Info("管理员批量删除用户", zap.Int64("operator_id", operatorID.(int64)), zap.Int("count", len(req.UserIDs)), ) c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "message": "批量删除用户成功", "count": len(req.UserIDs), })) } // GetRoles 获取角色列表 // @Summary 获取角色列表 // @Description 管理员获取所有可用角色 // @Tags Admin // @Produce json // @Success 200 {object} model.Response{data=types.AdminRoleListResponse} "获取成功" // @Security BearerAuth // @Router /api/v1/admin/roles [get] func (h *AdminHandler) GetRoles(c *gin.Context) { roles := []types.RoleInfo{ { Name: "user", DisplayName: "普通用户", Description: "拥有基本用户权限", }, { Name: "admin", DisplayName: "管理员", Description: "拥有所有管理权限", }, } c.JSON(http.StatusOK, model.NewSuccessResponse(types.AdminRoleListResponse{ Roles: roles, })) } // SearchTextures 搜索材质 // @Summary 搜索材质 // @Description 管理员根据条件搜索材质 // @Tags Admin // @Produce json // @Param keyword query string false "搜索关键词" // @Param type query string false "材质类型" // @Param status query int false "状态筛选" // @Param uploader_id query int false "上传者ID" // @Param sort_by query string false "排序字段" // @Param sort_desc query bool false "是否降序" // @Param page query int false "页码" // @Param page_size query int false "每页数量" // @Success 200 {object} model.Response{data=map[string]interface{}} "搜索成功" // @Security BearerAuth // @Router /api/v1/admin/textures/search [get] func (h *AdminHandler) SearchTextures(c *gin.Context) { var req types.AdminTextureSearchRequest if err := c.ShouldBindQuery(&req); err != nil { RespondBadRequest(c, "参数错误", err) return } if req.Page < 1 { req.Page = 1 } if req.PageSize < 1 || req.PageSize > 100 { req.PageSize = 20 } db := h.container.DB.Model(&model.Texture{}) // 关键词搜索 if req.Keyword != "" { db = db.Where("name LIKE ?", "%"+req.Keyword+"%") } // 类型筛选 if req.Type != "" { db = db.Where("type = ?", req.Type) } // 状态筛选 if req.Status != nil { db = db.Where("status = ?", *req.Status) } // 上传者筛选 if req.UploaderID != nil { db = db.Where("uploader_id = ?", *req.UploaderID) } // 排序 sortBy := "id" if req.SortBy != "" { sortBy = req.SortBy } order := sortBy if req.SortDesc { order += " DESC" } db = db.Order(order) // 分页 var total int64 db.Count(&total) var textures []model.Texture db.Preload("Uploader").Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find(&textures) // 构建响应 textureList := make([]gin.H, len(textures)) for i, t := range textures { uploaderName := "" if t.Uploader != nil { uploaderName = t.Uploader.Username } textureList[i] = gin.H{ "id": t.ID, "name": t.Name, "type": t.Type, "hash": t.Hash, "uploader_id": t.UploaderID, "uploader_name": uploaderName, "is_public": t.IsPublic, "download_count": t.DownloadCount, "favorite_count": t.FavoriteCount, "status": t.Status, "created_at": t.CreatedAt, } } c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "textures": textureList, "total": total, "page": req.Page, "page_size": req.PageSize, })) } // UpdateTexture 更新材质 // @Summary 更新材质 // @Description 管理员更新材质信息 // @Tags Admin // @Accept json // @Produce json // @Param id path int true "材质ID" // @Param request body types.AdminTextureUpdateRequest true "更新材质请求" // @Success 200 {object} model.Response{data=map[string]interface{}} "更新成功" // @Failure 400 {object} model.ErrorResponse "参数错误" // @Failure 404 {object} model.ErrorResponse "材质不存在" // @Security BearerAuth // @Router /api/v1/admin/textures/{id} [put] func (h *AdminHandler) UpdateTexture(c *gin.Context) { textureID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { RespondBadRequest(c, "无效的材质ID", err) return } var req types.AdminTextureUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { RespondBadRequest(c, "参数错误", err) return } // 检查材质是否存在 var texture model.Texture if err := h.container.DB.First(&texture, textureID).Error; err != nil { c.JSON(http.StatusNotFound, model.NewErrorResponse( model.CodeNotFound, "材质不存在", nil, )) return } // 构建更新字段 updates := make(map[string]interface{}) if req.Name != nil { updates["name"] = *req.Name } if req.Description != nil { updates["description"] = *req.Description } if req.IsPublic != nil { updates["is_public"] = *req.IsPublic } if req.Status != nil { updates["status"] = *req.Status } // 执行更新 if len(updates) > 0 { if err := h.container.DB.Model(&texture).Updates(updates).Error; err != nil { RespondServerError(c, "更新材质失败", err) return } } operatorID, _ := c.Get("user_id") h.container.Logger.Info("管理员更新材质", zap.Int64("operator_id", operatorID.(int64)), zap.Int64("texture_id", textureID), ) c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "message": "材质更新成功", "texture_id": textureID, })) } // BatchDeleteTextures 批量删除材质 // @Summary 批量删除材质 // @Description 管理员批量删除多个材质 // @Tags Admin // @Accept json // @Produce json // @Param request body map[string]interface{} true "批量删除请求" // @Success 200 {object} model.Response{data=map[string]interface{}} "删除成功" // @Failure 400 {object} model.ErrorResponse "参数错误" // @Security BearerAuth // @Router /api/v1/admin/textures/batch-delete [delete] func (h *AdminHandler) BatchDeleteTextures(c *gin.Context) { var req struct { TextureIDs []int64 `json:"texture_ids" binding:"required,min=1"` } if err := c.ShouldBindJSON(&req); err != nil { RespondBadRequest(c, "参数错误", err) return } operatorID, _ := c.Get("user_id") // 批量删除 if err := h.container.DB.Where("id IN ?", req.TextureIDs).Delete(&model.Texture{}).Error; err != nil { RespondServerError(c, "批量删除材质失败", err) return } h.container.Logger.Info("管理员批量删除材质", zap.Int64("operator_id", operatorID.(int64)), zap.Int("count", len(req.TextureIDs)), ) c.JSON(http.StatusOK, model.NewSuccessResponse(gin.H{ "message": "批量删除材质成功", "count": len(req.TextureIDs), })) } // GetStats 获取统计信息 // @Summary 获取统计信息 // @Description 管理员获取系统统计数据 // @Tags Admin // @Produce json // @Success 200 {object} model.Response{data=types.AdminStatsResponse} "获取成功" // @Security BearerAuth // @Router /api/v1/admin/stats [get] func (h *AdminHandler) GetStats(c *gin.Context) { var stats types.AdminStatsResponse // 用户统计 h.container.DB.Model(&model.User{}).Count(&stats.TotalUsers) h.container.DB.Model(&model.User{}).Where("status = ?", 1).Count(&stats.ActiveUsers) h.container.DB.Model(&model.User{}).Where("status = ?", 0).Count(&stats.BannedUsers) h.container.DB.Model(&model.User{}).Where("role = ?", "admin").Count(&stats.AdminUsers) // 材质统计 h.container.DB.Model(&model.Texture{}).Count(&stats.TotalTextures) h.container.DB.Model(&model.Texture{}).Where("is_public = ?", true).Count(&stats.PublicTextures) h.container.DB.Model(&model.Texture{}).Where("status = ?", 0).Count(&stats.PendingTextures) // 下载和收藏统计 h.container.DB.Model(&model.Texture{}).Select("COALESCE(SUM(download_count), 0)").Scan(&stats.TotalDownloads) h.container.DB.Model(&model.Texture{}).Select("COALESCE(SUM(favorite_count), 0)").Scan(&stats.TotalFavorites) c.JSON(http.StatusOK, model.NewSuccessResponse(stats)) }