refactor: 重构后端架构为 go-zero 框架,优化项目结构

主要变更:
- 采用 go-zero 框架替代 Gin,提升开发效率
- 重构项目结构,API 文件模块化组织
- 将 model 移至 api/internal/model 目录
- 移除 common 包,改为标准 pkg 目录结构
- 实现统一的仓储模式,支持配置驱动数据库切换
- 简化测试策略,专注 API 集成测试
- 更新 CLAUDE.md 文档,提供详细的开发指导

技术栈更新:
- 框架: Gin → go-zero v1.6.0+
- 代码生成: 引入 goctl 工具
- 架构模式: 四层架构 → go-zero 三层架构 (Handler→Logic→Model)
- 项目布局: 遵循 Go 社区标准和 go-zero 最佳实践
This commit is contained in:
xujiang
2025-07-10 15:05:52 +08:00
parent a2f2f66f88
commit 39a42695d3
52 changed files with 6047 additions and 2349 deletions

View File

@ -5,8 +5,10 @@ import (
"net/http"
"strconv"
"photography-backend/internal/models"
"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"
@ -32,17 +34,14 @@ func NewCategoryHandler(categoryService *service.CategoryService, logger *zap.Lo
// @Produce json
// @Param parent_id query int false "父分类ID"
// @Success 200 {array} models.Category
// @Failure 500 {object} models.ErrorResponse
// @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, models.ErrorResponse{
Error: "Invalid parent_id",
Message: "Parent ID must be a valid number",
})
c.JSON(http.StatusBadRequest, response.Error(http.StatusBadRequest, "Parent ID must be a valid number"))
return
}
parentIDUint := uint(id)
@ -52,10 +51,7 @@ func (h *CategoryHandler) GetCategories(c *gin.Context) {
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, models.ErrorResponse{
Error: "Failed to get categories",
Message: err.Error(),
})
c.JSON(http.StatusInternalServerError, response.Error(http.StatusInternalServerError, err.Error()))
return
}
@ -69,13 +65,13 @@ func (h *CategoryHandler) GetCategories(c *gin.Context) {
// @Accept json
// @Produce json
// @Success 200 {array} models.CategoryTree
// @Failure 500 {object} models.ErrorResponse
// @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, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get category tree",
Message: err.Error(),
})
@ -93,15 +89,15 @@ func (h *CategoryHandler) GetCategoryTree(c *gin.Context) {
// @Produce json
// @Param id path int true "分类ID"
// @Success 200 {object} models.Category
// @Failure 400 {object} models.ErrorResponse
// @Failure 404 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @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, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid category ID",
Message: "Category ID must be a valid number",
})
@ -111,7 +107,7 @@ func (h *CategoryHandler) GetCategory(c *gin.Context) {
category, err := h.categoryService.GetCategoryByID(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "category not found" {
c.JSON(http.StatusNotFound, models.ErrorResponse{
c.JSON(http.StatusNotFound, response.Error{
Error: "Category not found",
Message: "The requested category does not exist",
})
@ -119,7 +115,7 @@ func (h *CategoryHandler) GetCategory(c *gin.Context) {
}
h.logger.Error("Failed to get category", zap.Error(err))
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get category",
Message: err.Error(),
})
@ -137,8 +133,8 @@ func (h *CategoryHandler) GetCategory(c *gin.Context) {
// @Produce json
// @Param slug path string true "分类slug"
// @Success 200 {object} models.Category
// @Failure 404 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @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")
@ -146,7 +142,7 @@ func (h *CategoryHandler) GetCategoryBySlug(c *gin.Context) {
category, err := h.categoryService.GetCategoryBySlug(c.Request.Context(), slug)
if err != nil {
if err.Error() == "category not found" {
c.JSON(http.StatusNotFound, models.ErrorResponse{
c.JSON(http.StatusNotFound, response.Error{
Error: "Category not found",
Message: "The requested category does not exist",
})
@ -154,7 +150,7 @@ func (h *CategoryHandler) GetCategoryBySlug(c *gin.Context) {
}
h.logger.Error("Failed to get category by slug", zap.Error(err))
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get category",
Message: err.Error(),
})
@ -172,14 +168,14 @@ func (h *CategoryHandler) GetCategoryBySlug(c *gin.Context) {
// @Produce json
// @Param category body models.CreateCategoryRequest true "分类信息"
// @Success 201 {object} models.Category
// @Failure 400 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories [post]
func (h *CategoryHandler) CreateCategory(c *gin.Context) {
var req models.CreateCategoryRequest
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, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
@ -188,7 +184,7 @@ func (h *CategoryHandler) CreateCategory(c *gin.Context) {
// 验证请求数据
if err := h.validateCreateCategoryRequest(&req); err != nil {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request data",
Message: err.Error(),
})
@ -198,7 +194,7 @@ func (h *CategoryHandler) CreateCategory(c *gin.Context) {
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, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to create category",
Message: err.Error(),
})
@ -217,25 +213,25 @@ func (h *CategoryHandler) CreateCategory(c *gin.Context) {
// @Param id path int true "分类ID"
// @Param category body models.UpdateCategoryRequest true "分类信息"
// @Success 200 {object} models.Category
// @Failure 400 {object} models.ErrorResponse
// @Failure 404 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @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, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid category ID",
Message: "Category ID must be a valid number",
})
return
}
var req models.UpdateCategoryRequest
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, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
@ -245,7 +241,7 @@ func (h *CategoryHandler) UpdateCategory(c *gin.Context) {
category, err := h.categoryService.UpdateCategory(c.Request.Context(), uint(id), &req)
if err != nil {
if err.Error() == "category not found" {
c.JSON(http.StatusNotFound, models.ErrorResponse{
c.JSON(http.StatusNotFound, response.Error{
Error: "Category not found",
Message: "The requested category does not exist",
})
@ -253,7 +249,7 @@ func (h *CategoryHandler) UpdateCategory(c *gin.Context) {
}
h.logger.Error("Failed to update category", zap.Error(err))
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to update category",
Message: err.Error(),
})
@ -271,15 +267,15 @@ func (h *CategoryHandler) UpdateCategory(c *gin.Context) {
// @Produce json
// @Param id path int true "分类ID"
// @Success 204 "No Content"
// @Failure 400 {object} models.ErrorResponse
// @Failure 404 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @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, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid category ID",
Message: "Category ID must be a valid number",
})
@ -289,7 +285,7 @@ func (h *CategoryHandler) DeleteCategory(c *gin.Context) {
err = h.categoryService.DeleteCategory(c.Request.Context(), uint(id))
if err != nil {
if err.Error() == "category not found" {
c.JSON(http.StatusNotFound, models.ErrorResponse{
c.JSON(http.StatusNotFound, response.Error{
Error: "Category not found",
Message: "The requested category does not exist",
})
@ -297,7 +293,7 @@ func (h *CategoryHandler) DeleteCategory(c *gin.Context) {
}
h.logger.Error("Failed to delete category", zap.Error(err))
c.JSON(http.StatusInternalServerError, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to delete category",
Message: err.Error(),
})
@ -315,14 +311,14 @@ func (h *CategoryHandler) DeleteCategory(c *gin.Context) {
// @Produce json
// @Param request body models.ReorderCategoriesRequest true "排序请求"
// @Success 200 {object} models.SuccessResponse
// @Failure 400 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories/reorder [post]
func (h *CategoryHandler) ReorderCategories(c *gin.Context) {
var req models.ReorderCategoriesRequest
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, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
@ -330,7 +326,7 @@ func (h *CategoryHandler) ReorderCategories(c *gin.Context) {
}
if len(req.CategoryIDs) == 0 {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request",
Message: "No category IDs provided",
})
@ -340,14 +336,14 @@ func (h *CategoryHandler) ReorderCategories(c *gin.Context) {
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, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to reorder categories",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, models.SuccessResponse{
c.JSON(http.StatusOK, entity.SuccessResponse{
Message: "Categories reordered successfully",
})
}
@ -359,13 +355,13 @@ func (h *CategoryHandler) ReorderCategories(c *gin.Context) {
// @Accept json
// @Produce json
// @Success 200 {object} models.CategoryStats
// @Failure 500 {object} models.ErrorResponse
// @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, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to get category stats",
Message: err.Error(),
})
@ -383,14 +379,14 @@ func (h *CategoryHandler) GetCategoryStats(c *gin.Context) {
// @Produce json
// @Param request body models.GenerateSlugRequest true "生成slug请求"
// @Success 200 {object} models.GenerateSlugResponse
// @Failure 400 {object} models.ErrorResponse
// @Failure 500 {object} models.ErrorResponse
// @Failure 400 {object} response.Error
// @Failure 500 {object} response.Error
// @Router /categories/generate-slug [post]
func (h *CategoryHandler) GenerateSlug(c *gin.Context) {
var req models.GenerateSlugRequest
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, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request body",
Message: err.Error(),
})
@ -398,7 +394,7 @@ func (h *CategoryHandler) GenerateSlug(c *gin.Context) {
}
if req.Name == "" {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
c.JSON(http.StatusBadRequest, response.Error{
Error: "Invalid request",
Message: "Name is required",
})
@ -408,14 +404,14 @@ func (h *CategoryHandler) GenerateSlug(c *gin.Context) {
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, models.ErrorResponse{
c.JSON(http.StatusInternalServerError, response.Error{
Error: "Failed to generate slug",
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, models.GenerateSlugResponse{
c.JSON(http.StatusOK, entity.GenerateSlugResponse{
Slug: slug,
})
}