fix
Some checks failed
部署后端服务 / 🧪 测试后端 (push) Failing after 5m8s
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped

This commit is contained in:
xujiang
2025-07-10 18:09:11 +08:00
parent 35004f224e
commit 010fe2a8c7
96 changed files with 23709 additions and 19 deletions

View File

@ -0,0 +1,119 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"photography-backend/internal/model/entity"
"photography-backend/internal/model/dto"
"photography-backend/internal/service/auth"
"photography-backend/internal/api/middleware"
"photography-backend/pkg/response"
)
// AuthHandler 认证处理器
type AuthHandler struct {
authService *auth.AuthService
}
// NewAuthHandler 创建认证处理器
func NewAuthHandler(authService *auth.AuthService) *AuthHandler {
return &AuthHandler{
authService: authService,
}
}
// Login 用户登录
func (h *AuthHandler) Login(c *gin.Context) {
var req dto.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, response.Error(http.StatusBadRequest, err.Error()))
return
}
loginResp, err := h.authService.Login(&req)
if err != nil {
c.JSON(http.StatusUnauthorized, response.Error(http.StatusUnauthorized, err.Error()))
return
}
c.JSON(http.StatusOK, response.Success(loginResp))
}
// Register 用户注册
func (h *AuthHandler) Register(c *gin.Context) {
var req dto.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, response.Error(http.StatusBadRequest, err.Error()))
return
}
user, err := h.authService.Register(&req)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error(http.StatusBadRequest, err.Error()))
return
}
c.JSON(http.StatusCreated, response.Success(user))
}
// RefreshToken 刷新令牌
func (h *AuthHandler) RefreshToken(c *gin.Context) {
var req dto.RefreshTokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, response.Error(http.StatusBadRequest, err.Error()))
return
}
loginResp, err := h.authService.RefreshToken(&req)
if err != nil {
c.JSON(http.StatusUnauthorized, response.Error(http.StatusUnauthorized, err.Error()))
return
}
c.JSON(http.StatusOK, response.Success(loginResp))
}
// GetProfile 获取用户资料
func (h *AuthHandler) GetProfile(c *gin.Context) {
userID, exists := middleware.GetCurrentUser(c)
if !exists {
c.JSON(http.StatusUnauthorized, response.Error(http.StatusUnauthorized, "User not authenticated"))
return
}
user, err := h.authService.GetUserByID(userID)
if err != nil {
c.JSON(http.StatusInternalServerError, response.Error(http.StatusInternalServerError, err.Error()))
return
}
c.JSON(http.StatusOK, response.Success(user))
}
// UpdatePassword 更新密码
func (h *AuthHandler) UpdatePassword(c *gin.Context) {
userID, exists := middleware.GetCurrentUser(c)
if !exists {
c.JSON(http.StatusUnauthorized, response.Error(http.StatusUnauthorized, "User not authenticated"))
return
}
var req dto.ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, response.Error(http.StatusBadRequest, err.Error()))
return
}
if err := h.authService.UpdatePassword(userID, &req); err != nil {
c.JSON(http.StatusBadRequest, response.Error(http.StatusBadRequest, err.Error()))
return
}
c.JSON(http.StatusOK, response.Success(gin.H{"message": "Password updated successfully"}))
}
// Logout 用户登出
func (h *AuthHandler) Logout(c *gin.Context) {
// 简单实现实际应用中可能需要将token加入黑名单
c.JSON(http.StatusOK, response.Success(gin.H{"message": "Logged out successfully"}))
}

View File

@ -0,0 +1,430 @@
package handlers
import (
"errors"
"net/http"
"strconv"
"photography-backend/internal/model/entity"
"photography-backend/internal/model/dto"
"photography-backend/internal/service"
"photography-backend/pkg/response"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type CategoryHandler struct {
categoryService *service.CategoryService
logger *zap.Logger
}
func NewCategoryHandler(categoryService *service.CategoryService, logger *zap.Logger) *CategoryHandler {
return &CategoryHandler{
categoryService: categoryService,
logger: logger,
}
}
// GetCategories 获取分类列表
// @Summary 获取分类列表
// @Description 获取分类列表,可指定父分类
// @Tags categories
// @Accept json
// @Produce json
// @Param parent_id query int false "父分类ID"
// @Success 200 {array} models.Category
// @Failure 500 {object} response.Error
// @Router /categories [get]
func (h *CategoryHandler) GetCategories(c *gin.Context) {
var parentID *uint
if parentIDStr := c.Query("parent_id"); parentIDStr != "" {
id, err := strconv.ParseUint(parentIDStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error(http.StatusBadRequest, "Parent ID must be a valid number"))
return
}
parentIDUint := uint(id)
parentID = &parentIDUint
}
categories, err := h.categoryService.GetCategories(c.Request.Context(), parentID)
if err != nil {
h.logger.Error("Failed to get categories", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error(http.StatusInternalServerError, err.Error()))
return
}
c.JSON(http.StatusOK, categories)
}
// GetCategoryTree 获取分类树
// @Summary 获取分类树
// @Description 获取完整的分类树结构
// @Tags categories
// @Accept json
// @Produce json
// @Success 200 {array} models.CategoryTree
// @Failure 500 {object} response.Error
// @Router /categories/tree [get]
func (h *CategoryHandler) GetCategoryTree(c *gin.Context) {
tree, err := h.categoryService.GetCategoryTree(c.Request.Context())
if err != nil {
h.logger.Error("Failed to get category tree", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get category tree",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, tree)
}
// GetCategory 获取分类详情
// @Summary 获取分类详情
// @Description 根据ID获取分类详情
// @Tags categories
// @Accept json
// @Produce json
// @Param id path int true "分类ID"
// @Success 200 {object} models.Category
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories/{id} [get]
func (h *CategoryHandler) GetCategory(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid category ID",
Message: "Category ID must be a valid number",
})
return
}
category, err := h.categoryService.GetCategoryByID(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "category not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Category not found",
Message: "The requested category does not exist",
})
return
}
h.logger.Error("Failed to get category", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get category",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, category)
}
// GetCategoryBySlug 根据slug获取分类
// @Summary 根据slug获取分类
// @Description 根据slug获取分类详情
// @Tags categories
// @Accept json
// @Produce json
// @Param slug path string true "分类slug"
// @Success 200 {object} models.Category
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories/slug/{slug} [get]
func (h *CategoryHandler) GetCategoryBySlug(c *gin.Context) {
slug := c.Param("slug")
category, err := h.categoryService.GetCategoryBySlug(c.Request.Context(), slug)
if err != nil {
if err.Error() == "category not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Category not found",
Message: "The requested category does not exist",
})
return
}
h.logger.Error("Failed to get category by slug", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get category",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, category)
}
// CreateCategory 创建分类
// @Summary 创建分类
// @Description 创建新的分类
// @Tags categories
// @Accept json
// @Produce json
// @Param category body models.CreateCategoryRequest true "分类信息"
// @Success 201 {object} models.Category
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories [post]
func (h *CategoryHandler) CreateCategory(c *gin.Context) {
var req entity.CreateCategoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
// 验证请求数据
if err := h.validateCreateCategoryRequest(&req); err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request data",
Message: err.Error(),
})
return
}
category, err := h.categoryService.CreateCategory(c.Request.Context(), &req)
if err != nil {
h.logger.Error("Failed to create category", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to create category",
Message: err.Error(),
})
return
}
c.JSON(http.StatusCreated, category)
}
// UpdateCategory 更新分类
// @Summary 更新分类
// @Description 更新分类信息
// @Tags categories
// @Accept json
// @Produce json
// @Param id path int true "分类ID"
// @Param category body models.UpdateCategoryRequest true "分类信息"
// @Success 200 {object} models.Category
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories/{id} [put]
func (h *CategoryHandler) UpdateCategory(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid category ID",
Message: "Category ID must be a valid number",
})
return
}
var req entity.UpdateCategoryRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
category, err := h.categoryService.UpdateCategory(c.Request.Context(), uint(id), &req)
if err != nil {
if err.Error() == "category not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Category not found",
Message: "The requested category does not exist",
})
return
}
h.logger.Error("Failed to update category", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to update category",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, category)
}
// DeleteCategory 删除分类
// @Summary 删除分类
// @Description 删除分类
// @Tags categories
// @Accept json
// @Produce json
// @Param id path int true "分类ID"
// @Success 204 "No Content"
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories/{id} [delete]
func (h *CategoryHandler) DeleteCategory(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid category ID",
Message: "Category ID must be a valid number",
})
return
}
err = h.categoryService.DeleteCategory(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "category not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Category not found",
Message: "The requested category does not exist",
})
return
}
h.logger.Error("Failed to delete category", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to delete category",
Message: err.Error(),
})
return
}
c.Status(http.StatusNoContent)
}
// ReorderCategories 重新排序分类
// @Summary 重新排序分类
// @Description 重新排序分类
// @Tags categories
// @Accept json
// @Produce json
// @Param request body models.ReorderCategoriesRequest true "排序请求"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories/reorder [post]
func (h *CategoryHandler) ReorderCategories(c *gin.Context) {
var req entity.ReorderCategoriesRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
if len(req.CategoryIDs) == 0 {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request",
Message: "No category IDs provided",
})
return
}
err := h.categoryService.ReorderCategories(c.Request.Context(), req.ParentID, req.CategoryIDs)
if err != nil {
h.logger.Error("Failed to reorder categories", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to reorder categories",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, entity.SuccessResponse{
Message: "Categories reordered successfully",
})
}
// GetCategoryStats 获取分类统计信息
// @Summary 获取分类统计信息
// @Description 获取分类统计信息
// @Tags categories
// @Accept json
// @Produce json
// @Success 200 {object} models.CategoryStats
// @Failure 500 {object} response.Error
// @Router /categories/stats [get]
func (h *CategoryHandler) GetCategoryStats(c *gin.Context) {
stats, err := h.categoryService.GetCategoryStats(c.Request.Context())
if err != nil {
h.logger.Error("Failed to get category stats", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get category stats",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, stats)
}
// GenerateSlug 生成分类slug
// @Summary 生成分类slug
// @Description 根据分类名称生成唯一的slug
// @Tags categories
// @Accept json
// @Produce json
// @Param request body models.GenerateSlugRequest true "生成slug请求"
// @Success 200 {object} models.GenerateSlugResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories/generate-slug [post]
func (h *CategoryHandler) GenerateSlug(c *gin.Context) {
var req entity.GenerateSlugRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
if req.Name == "" {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request",
Message: "Name is required",
})
return
}
slug, err := h.categoryService.GenerateSlug(c.Request.Context(), req.Name)
if err != nil {
h.logger.Error("Failed to generate slug", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to generate slug",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, entity.GenerateSlugResponse{
Slug: slug,
})
}
// validateCreateCategoryRequest 验证创建分类请求
func (h *CategoryHandler) validateCreateCategoryRequest(req *models.CreateCategoryRequest) error {
if req.Name == "" {
return errors.New("name is required")
}
if req.Slug == "" {
return errors.New("slug is required")
}
return nil
}

View File

@ -0,0 +1,480 @@
package handlers
import (
"errors"
"net/http"
"strconv"
"strings"
"photography-backend/internal/model/entity"
"photography-backend/internal/model/dto"
"photography-backend/internal/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type PhotoHandler struct {
photoService *service.PhotoService
logger *zap.Logger
}
func NewPhotoHandler(photoService *service.PhotoService, logger *zap.Logger) *PhotoHandler {
return &PhotoHandler{
photoService: photoService,
logger: logger,
}
}
// GetPhotos 获取照片列表
// @Summary 获取照片列表
// @Description 获取照片列表,支持分页、搜索、过滤
// @Tags photos
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param limit query int false "每页数量"
// @Param search query string false "搜索关键词"
// @Param status query string false "状态筛选"
// @Param category_id query int false "分类ID"
// @Param tags query string false "标签列表(逗号分隔)"
// @Param start_date query string false "开始日期"
// @Param end_date query string false "结束日期"
// @Param sort_by query string false "排序字段"
// @Param sort_order query string false "排序方向"
// @Success 200 {object} service.PhotoListResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /photos [get]
func (h *PhotoHandler) GetPhotos(c *gin.Context) {
var params service.PhotoListParams
// 解析查询参数
if err := c.ShouldBindQuery(&params); err != nil {
h.logger.Error("Failed to bind query params", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid query parameters",
Message: err.Error(),
})
return
}
// 解析标签参数
if tagsStr := c.Query("tags"); tagsStr != "" {
params.Tags = strings.Split(tagsStr, ",")
}
// 调用服务层
result, err := h.photoService.GetPhotos(c.Request.Context(), params)
if err != nil {
h.logger.Error("Failed to get photos", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get photos",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, result)
}
// GetPhoto 获取照片详情
// @Summary 获取照片详情
// @Description 根据ID获取照片详情
// @Tags photos
// @Accept json
// @Produce json
// @Param id path int true "照片ID"
// @Success 200 {object} models.Photo
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /photos/{id} [get]
func (h *PhotoHandler) GetPhoto(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid photo ID",
Message: "Photo ID must be a valid number",
})
return
}
photo, err := h.photoService.GetPhotoByID(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "photo not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Photo not found",
Message: "The requested photo does not exist",
})
return
}
h.logger.Error("Failed to get photo", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get photo",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, photo)
}
// CreatePhoto 创建照片
// @Summary 创建照片
// @Description 创建新的照片记录
// @Tags photos
// @Accept json
// @Produce json
// @Param photo body models.CreatePhotoRequest true "照片信息"
// @Success 201 {object} models.Photo
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /photos [post]
func (h *PhotoHandler) CreatePhoto(c *gin.Context) {
var req models.CreatePhotoRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
// 验证请求数据
if err := h.validateCreatePhotoRequest(&req); err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request data",
Message: err.Error(),
})
return
}
photo, err := h.photoService.CreatePhoto(c.Request.Context(), &req)
if err != nil {
h.logger.Error("Failed to create photo", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to create photo",
Message: err.Error(),
})
return
}
c.JSON(http.StatusCreated, photo)
}
// UpdatePhoto 更新照片
// @Summary 更新照片
// @Description 更新照片信息
// @Tags photos
// @Accept json
// @Produce json
// @Param id path int true "照片ID"
// @Param photo body models.UpdatePhotoRequest true "照片信息"
// @Success 200 {object} models.Photo
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /photos/{id} [put]
func (h *PhotoHandler) UpdatePhoto(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid photo ID",
Message: "Photo ID must be a valid number",
})
return
}
var req models.UpdatePhotoRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
photo, err := h.photoService.UpdatePhoto(c.Request.Context(), uint(id), &req)
if err != nil {
if err.Error() == "photo not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Photo not found",
Message: "The requested photo does not exist",
})
return
}
h.logger.Error("Failed to update photo", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to update photo",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, photo)
}
// DeletePhoto 删除照片
// @Summary 删除照片
// @Description 删除照片
// @Tags photos
// @Accept json
// @Produce json
// @Param id path int true "照片ID"
// @Success 204 "No Content"
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /photos/{id} [delete]
func (h *PhotoHandler) DeletePhoto(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid photo ID",
Message: "Photo ID must be a valid number",
})
return
}
err = h.photoService.DeletePhoto(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "photo not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Photo not found",
Message: "The requested photo does not exist",
})
return
}
h.logger.Error("Failed to delete photo", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to delete photo",
Message: err.Error(),
})
return
}
c.Status(http.StatusNoContent)
}
// UploadPhoto 上传照片
// @Summary 上传照片
// @Description 上传照片文件并创建记录
// @Tags photos
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "照片文件"
// @Param title formData string false "标题"
// @Param description formData string false "描述"
// @Param status formData string false "状态"
// @Param category_ids formData string false "分类ID列表(逗号分隔)"
// @Param tag_ids formData string false "标签ID列表(逗号分隔)"
// @Success 201 {object} models.Photo
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /photos/upload [post]
func (h *PhotoHandler) UploadPhoto(c *gin.Context) {
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "No file uploaded",
Message: "Please select a file to upload",
})
return
}
defer file.Close()
// 构建创建请求
req := &models.CreatePhotoRequest{
Title: c.PostForm("title"),
Description: c.PostForm("description"),
Status: c.PostForm("status"),
}
// 如果未指定状态,默认为草稿
if req.Status == "" {
req.Status = "draft"
}
// 解析分类ID
if categoryIDsStr := c.PostForm("category_ids"); categoryIDsStr != "" {
categoryIDStrs := strings.Split(categoryIDsStr, ",")
for _, idStr := range categoryIDStrs {
if id, err := strconv.ParseUint(strings.TrimSpace(idStr), 10, 32); err == nil {
req.CategoryIDs = append(req.CategoryIDs, uint(id))
}
}
}
// 解析标签ID
if tagIDsStr := c.PostForm("tag_ids"); tagIDsStr != "" {
tagIDStrs := strings.Split(tagIDsStr, ",")
for _, idStr := range tagIDStrs {
if id, err := strconv.ParseUint(strings.TrimSpace(idStr), 10, 32); err == nil {
req.TagIDs = append(req.TagIDs, uint(id))
}
}
}
// 上传照片
photo, err := h.photoService.UploadPhoto(c.Request.Context(), file, header, req)
if err != nil {
h.logger.Error("Failed to upload photo", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to upload photo",
Message: err.Error(),
})
return
}
c.JSON(http.StatusCreated, photo)
}
// BatchUpdatePhotos 批量更新照片
// @Summary 批量更新照片
// @Description 批量更新照片信息
// @Tags photos
// @Accept json
// @Produce json
// @Param request body models.BatchUpdatePhotosRequest true "批量更新请求"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /photos/batch/update [post]
func (h *PhotoHandler) BatchUpdatePhotos(c *gin.Context) {
var req models.BatchUpdatePhotosRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request",
Message: "No photo IDs provided",
})
return
}
err := h.photoService.BatchUpdatePhotos(c.Request.Context(), req.IDs, &req)
if err != nil {
h.logger.Error("Failed to batch update photos", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to batch update photos",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, models.SuccessResponse{
Message: "Photos updated successfully",
})
}
// BatchDeletePhotos 批量删除照片
// @Summary 批量删除照片
// @Description 批量删除照片
// @Tags photos
// @Accept json
// @Produce json
// @Param request body models.BatchDeleteRequest true "批量删除请求"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /photos/batch/delete [post]
func (h *PhotoHandler) BatchDeletePhotos(c *gin.Context) {
var req models.BatchDeleteRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request",
Message: "No photo IDs provided",
})
return
}
err := h.photoService.BatchDeletePhotos(c.Request.Context(), req.IDs)
if err != nil {
h.logger.Error("Failed to batch delete photos", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to batch delete photos",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, models.SuccessResponse{
Message: "Photos deleted successfully",
})
}
// GetPhotoStats 获取照片统计信息
// @Summary 获取照片统计信息
// @Description 获取照片统计信息
// @Tags photos
// @Accept json
// @Produce json
// @Success 200 {object} models.PhotoStats
// @Failure 500 {object} response.Error
// @Router /photos/stats [get]
func (h *PhotoHandler) GetPhotoStats(c *gin.Context) {
stats, err := h.photoService.GetPhotoStats(c.Request.Context())
if err != nil {
h.logger.Error("Failed to get photo stats", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get photo stats",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, stats)
}
// validateCreatePhotoRequest 验证创建照片请求
func (h *PhotoHandler) validateCreatePhotoRequest(req *models.CreatePhotoRequest) error {
if req.Title == "" {
return errors.New("title is required")
}
if req.Status == "" {
req.Status = "draft"
}
// 验证状态值
validStatuses := []string{"draft", "published", "archived", "processing"}
isValidStatus := false
for _, status := range validStatuses {
if req.Status == status {
isValidStatus = true
break
}
}
if !isValidStatus {
return errors.New("invalid status value")
}
return nil
}

View File

@ -0,0 +1,536 @@
package handlers
import (
"errors"
"net/http"
"strconv"
"photography-backend/internal/model/entity"
"photography-backend/internal/model/dto"
"photography-backend/internal/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type TagHandler struct {
tagService *service.TagService
logger *zap.Logger
}
func NewTagHandler(tagService *service.TagService, logger *zap.Logger) *TagHandler {
return &TagHandler{
tagService: tagService,
logger: logger,
}
}
// GetTags 获取标签列表
// @Summary 获取标签列表
// @Description 获取标签列表,支持分页、搜索、过滤
// @Tags tags
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param limit query int false "每页数量"
// @Param search query string false "搜索关键词"
// @Param is_active query bool false "是否激活"
// @Param sort_by query string false "排序字段"
// @Param sort_order query string false "排序方向"
// @Success 200 {object} service.TagListResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags [get]
func (h *TagHandler) GetTags(c *gin.Context) {
var params service.TagListParams
// 解析查询参数
if err := c.ShouldBindQuery(&params); err != nil {
h.logger.Error("Failed to bind query params", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid query parameters",
Message: err.Error(),
})
return
}
// 调用服务层
result, err := h.tagService.GetTags(c.Request.Context(), params)
if err != nil {
h.logger.Error("Failed to get tags", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get tags",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, result)
}
// GetAllTags 获取所有活跃标签
// @Summary 获取所有活跃标签
// @Description 获取所有活跃标签(用于选择器)
// @Tags tags
// @Accept json
// @Produce json
// @Success 200 {array} models.Tag
// @Failure 500 {object} response.Error
// @Router /tags/all [get]
func (h *TagHandler) GetAllTags(c *gin.Context) {
tags, err := h.tagService.GetAllTags(c.Request.Context())
if err != nil {
h.logger.Error("Failed to get all tags", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get all tags",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, tags)
}
// GetTag 获取标签详情
// @Summary 获取标签详情
// @Description 根据ID获取标签详情
// @Tags tags
// @Accept json
// @Produce json
// @Param id path int true "标签ID"
// @Success 200 {object} models.Tag
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags/{id} [get]
func (h *TagHandler) GetTag(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid tag ID",
Message: "Tag ID must be a valid number",
})
return
}
tag, err := h.tagService.GetTagByID(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "tag not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Tag not found",
Message: "The requested tag does not exist",
})
return
}
h.logger.Error("Failed to get tag", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get tag",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, tag)
}
// GetTagBySlug 根据slug获取标签
// @Summary 根据slug获取标签
// @Description 根据slug获取标签详情
// @Tags tags
// @Accept json
// @Produce json
// @Param slug path string true "标签slug"
// @Success 200 {object} models.Tag
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags/slug/{slug} [get]
func (h *TagHandler) GetTagBySlug(c *gin.Context) {
slug := c.Param("slug")
tag, err := h.tagService.GetTagBySlug(c.Request.Context(), slug)
if err != nil {
if err.Error() == "tag not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Tag not found",
Message: "The requested tag does not exist",
})
return
}
h.logger.Error("Failed to get tag by slug", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get tag",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, tag)
}
// CreateTag 创建标签
// @Summary 创建标签
// @Description 创建新的标签
// @Tags tags
// @Accept json
// @Produce json
// @Param tag body models.CreateTagRequest true "标签信息"
// @Success 201 {object} models.Tag
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags [post]
func (h *TagHandler) CreateTag(c *gin.Context) {
var req models.CreateTagRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
// 验证请求数据
if err := h.validateCreateTagRequest(&req); err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request data",
Message: err.Error(),
})
return
}
tag, err := h.tagService.CreateTag(c.Request.Context(), &req)
if err != nil {
h.logger.Error("Failed to create tag", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to create tag",
Message: err.Error(),
})
return
}
c.JSON(http.StatusCreated, tag)
}
// UpdateTag 更新标签
// @Summary 更新标签
// @Description 更新标签信息
// @Tags tags
// @Accept json
// @Produce json
// @Param id path int true "标签ID"
// @Param tag body models.UpdateTagRequest true "标签信息"
// @Success 200 {object} models.Tag
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags/{id} [put]
func (h *TagHandler) UpdateTag(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid tag ID",
Message: "Tag ID must be a valid number",
})
return
}
var req models.UpdateTagRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
tag, err := h.tagService.UpdateTag(c.Request.Context(), uint(id), &req)
if err != nil {
if err.Error() == "tag not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Tag not found",
Message: "The requested tag does not exist",
})
return
}
h.logger.Error("Failed to update tag", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to update tag",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, tag)
}
// DeleteTag 删除标签
// @Summary 删除标签
// @Description 删除标签
// @Tags tags
// @Accept json
// @Produce json
// @Param id path int true "标签ID"
// @Success 204 "No Content"
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags/{id} [delete]
func (h *TagHandler) DeleteTag(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid tag ID",
Message: "Tag ID must be a valid number",
})
return
}
err = h.tagService.DeleteTag(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "tag not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "Tag not found",
Message: "The requested tag does not exist",
})
return
}
h.logger.Error("Failed to delete tag", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to delete tag",
Message: err.Error(),
})
return
}
c.Status(http.StatusNoContent)
}
// BatchDeleteTags 批量删除标签
// @Summary 批量删除标签
// @Description 批量删除标签
// @Tags tags
// @Accept json
// @Produce json
// @Param request body models.BatchDeleteRequest true "批量删除请求"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags/batch/delete [post]
func (h *TagHandler) BatchDeleteTags(c *gin.Context) {
var req models.BatchDeleteRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request",
Message: "No tag IDs provided",
})
return
}
err := h.tagService.BatchDeleteTags(c.Request.Context(), req.IDs)
if err != nil {
h.logger.Error("Failed to batch delete tags", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to batch delete tags",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, models.SuccessResponse{
Message: "Tags deleted successfully",
})
}
// GetPopularTags 获取热门标签
// @Summary 获取热门标签
// @Description 获取热门标签(按使用次数排序)
// @Tags tags
// @Accept json
// @Produce json
// @Param limit query int false "限制数量"
// @Success 200 {array} models.TagWithCount
// @Failure 500 {object} response.Error
// @Router /tags/popular [get]
func (h *TagHandler) GetPopularTags(c *gin.Context) {
limit := 10
if limitStr := c.Query("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
limit = parsedLimit
}
}
tags, err := h.tagService.GetPopularTags(c.Request.Context(), limit)
if err != nil {
h.logger.Error("Failed to get popular tags", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get popular tags",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, tags)
}
// GetTagCloud 获取标签云
// @Summary 获取标签云
// @Description 获取标签云数据
// @Tags tags
// @Accept json
// @Produce json
// @Success 200 {array} models.TagCloudItem
// @Failure 500 {object} response.Error
// @Router /tags/cloud [get]
func (h *TagHandler) GetTagCloud(c *gin.Context) {
cloud, err := h.tagService.GetTagCloud(c.Request.Context())
if err != nil {
h.logger.Error("Failed to get tag cloud", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get tag cloud",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, cloud)
}
// GetTagStats 获取标签统计信息
// @Summary 获取标签统计信息
// @Description 获取标签统计信息
// @Tags tags
// @Accept json
// @Produce json
// @Success 200 {object} models.TagStats
// @Failure 500 {object} response.Error
// @Router /tags/stats [get]
func (h *TagHandler) GetTagStats(c *gin.Context) {
stats, err := h.tagService.GetTagStats(c.Request.Context())
if err != nil {
h.logger.Error("Failed to get tag stats", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get tag stats",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, stats)
}
// SearchTags 搜索标签
// @Summary 搜索标签
// @Description 搜索标签(用于自动完成)
// @Tags tags
// @Accept json
// @Produce json
// @Param q query string true "搜索关键词"
// @Param limit query int false "限制数量"
// @Success 200 {array} models.Tag
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags/search [get]
func (h *TagHandler) SearchTags(c *gin.Context) {
query := c.Query("q")
if query == "" {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid query",
Message: "Search query is required",
})
return
}
limit := 10
if limitStr := c.Query("limit"); limitStr != "" {
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
limit = parsedLimit
}
}
tags, err := h.tagService.SearchTags(c.Request.Context(), query, limit)
if err != nil {
h.logger.Error("Failed to search tags", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to search tags",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, tags)
}
// GenerateSlug 生成标签slug
// @Summary 生成标签slug
// @Description 根据标签名称生成唯一的slug
// @Tags tags
// @Accept json
// @Produce json
// @Param request body models.GenerateSlugRequest true "生成slug请求"
// @Success 200 {object} models.GenerateSlugResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /tags/generate-slug [post]
func (h *TagHandler) GenerateSlug(c *gin.Context) {
var req models.GenerateSlugRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
if req.Name == "" {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request",
Message: "Name is required",
})
return
}
slug, err := h.tagService.GenerateSlug(c.Request.Context(), req.Name)
if err != nil {
h.logger.Error("Failed to generate slug", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to generate slug",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, models.GenerateSlugResponse{
Slug: slug,
})
}
// validateCreateTagRequest 验证创建标签请求
func (h *TagHandler) validateCreateTagRequest(req *models.CreateTagRequest) error {
if req.Name == "" {
return errors.New("name is required")
}
if req.Slug == "" {
return errors.New("slug is required")
}
return nil
}

View File

@ -0,0 +1,410 @@
package handlers
import (
"errors"
"net/http"
"strconv"
"photography-backend/internal/model/entity"
"photography-backend/internal/model/dto"
"photography-backend/internal/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type UserHandler struct {
userService *service.UserService
logger *zap.Logger
}
func NewUserHandler(userService *service.UserService, logger *zap.Logger) *UserHandler {
return &UserHandler{
userService: userService,
logger: logger,
}
}
// GetCurrentUser 获取当前用户信息
// @Summary 获取当前用户信息
// @Description 获取当前登录用户的详细信息
// @Tags users
// @Accept json
// @Produce json
// @Success 200 {object} models.UserResponse
// @Failure 401 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /me [get]
func (h *UserHandler) GetCurrentUser(c *gin.Context) {
userID := c.GetUint("user_id")
user, err := h.userService.GetUserByID(c.Request.Context(), userID)
if err != nil {
h.logger.Error("Failed to get current user", zap.Error(err), zap.Uint("user_id", userID))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get user information",
Message: err.Error(),
})
return
}
userResponse := &models.UserResponse{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Role: user.Role,
IsActive: user.IsActive,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
c.JSON(http.StatusOK, userResponse)
}
// UpdateCurrentUser 更新当前用户信息
// @Summary 更新当前用户信息
// @Description 更新当前登录用户的个人信息
// @Tags users
// @Accept json
// @Produce json
// @Param user body models.UpdateCurrentUserRequest true "用户信息"
// @Success 200 {object} models.UserResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /me [put]
func (h *UserHandler) UpdateCurrentUser(c *gin.Context) {
userID := c.GetUint("user_id")
var req models.UpdateCurrentUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
user, err := h.userService.UpdateCurrentUser(c.Request.Context(), userID, &req)
if err != nil {
h.logger.Error("Failed to update current user", zap.Error(err), zap.Uint("user_id", userID))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to update user information",
Message: err.Error(),
})
return
}
userResponse := &models.UserResponse{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Role: user.Role,
IsActive: user.IsActive,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
c.JSON(http.StatusOK, userResponse)
}
// GetUsers 获取用户列表 (管理员功能)
// @Summary 获取用户列表
// @Description 获取系统中所有用户列表
// @Tags admin
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param limit query int false "每页数量"
// @Param search query string false "搜索关键词"
// @Success 200 {object} service.UserListResponse
// @Failure 403 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /admin/users [get]
func (h *UserHandler) GetUsers(c *gin.Context) {
var params service.UserListParams
// 解析查询参数
if err := c.ShouldBindQuery(&params); err != nil {
h.logger.Error("Failed to bind query params", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid query parameters",
Message: err.Error(),
})
return
}
result, err := h.userService.GetUsers(c.Request.Context(), params)
if err != nil {
h.logger.Error("Failed to get users", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get users",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, result)
}
// GetUser 获取用户详情 (管理员功能)
// @Summary 获取用户详情
// @Description 根据ID获取用户详情
// @Tags admin
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Success 200 {object} models.UserResponse
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /admin/users/{id} [get]
func (h *UserHandler) GetUser(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid user ID",
Message: "User ID must be a valid number",
})
return
}
user, err := h.userService.GetUserByID(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "user not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "User not found",
Message: "The requested user does not exist",
})
return
}
h.logger.Error("Failed to get user", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get user",
Message: err.Error(),
})
return
}
userResponse := &models.UserResponse{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Role: user.Role,
IsActive: user.IsActive,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
c.JSON(http.StatusOK, userResponse)
}
// CreateUser 创建用户 (管理员功能)
// @Summary 创建用户
// @Description 创建新用户
// @Tags admin
// @Accept json
// @Produce json
// @Param user body models.CreateUserRequest true "用户信息"
// @Success 201 {object} models.UserResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /admin/users [post]
func (h *UserHandler) CreateUser(c *gin.Context) {
var req models.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
// 验证请求数据
if err := h.validateCreateUserRequest(&req); err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request data",
Message: err.Error(),
})
return
}
user, err := h.userService.CreateUser(c.Request.Context(), &req)
if err != nil {
h.logger.Error("Failed to create user", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to create user",
Message: err.Error(),
})
return
}
userResponse := &models.UserResponse{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Role: user.Role,
IsActive: user.IsActive,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
c.JSON(http.StatusCreated, userResponse)
}
// UpdateUser 更新用户 (管理员功能)
// @Summary 更新用户
// @Description 更新用户信息
// @Tags admin
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Param user body models.UpdateUserRequest true "用户信息"
// @Success 200 {object} models.UserResponse
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /admin/users/{id} [put]
func (h *UserHandler) UpdateUser(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid user ID",
Message: "User ID must be a valid number",
})
return
}
var req models.UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Failed to bind JSON", zap.Error(err))
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
return
}
user, err := h.userService.UpdateUser(c.Request.Context(), uint(id), &req)
if err != nil {
if err.Error() == "user not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "User not found",
Message: "The requested user does not exist",
})
return
}
h.logger.Error("Failed to update user", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to update user",
Message: err.Error(),
})
return
}
userResponse := &models.UserResponse{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Role: user.Role,
IsActive: user.IsActive,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
c.JSON(http.StatusOK, userResponse)
}
// DeleteUser 删除用户 (管理员功能)
// @Summary 删除用户
// @Description 删除用户
// @Tags admin
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Success 204 "No Content"
// @Failure 400 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /admin/users/{id} [delete]
func (h *UserHandler) DeleteUser(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid user ID",
Message: "User ID must be a valid number",
})
return
}
// 防止删除自己
currentUserID := c.GetUint("user_id")
if uint(id) == currentUserID {
c.JSON(http.StatusBadRequest, response.Error{
Error: "Cannot delete yourself",
Message: "You cannot delete your own account",
})
return
}
err = h.userService.DeleteUser(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "user not found" {
c.JSON(http.StatusNotFound, response.Error{
Error: "User not found",
Message: "The requested user does not exist",
})
return
}
h.logger.Error("Failed to delete user", zap.Error(err))
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to delete user",
Message: err.Error(),
})
return
}
c.Status(http.StatusNoContent)
}
// validateCreateUserRequest 验证创建用户请求
func (h *UserHandler) validateCreateUserRequest(req *models.CreateUserRequest) error {
if req.Username == "" {
return errors.New("username is required")
}
if req.Email == "" {
return errors.New("email is required")
}
if req.Password == "" {
return errors.New("password is required")
}
if req.Role == "" {
req.Role = "user"
}
// 验证角色
validRoles := []string{"user", "editor", "admin"}
isValidRole := false
for _, role := range validRoles {
if req.Role == role {
isValidRole = true
break
}
}
if !isValidRole {
return errors.New("invalid role value")
}
return nil
}