go-todo-api/handlers/todo_handler.go
2025-12-02 18:58:25 +08:00

270 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"errors"
"fmt"
"go-todo-api/constants"
"go-todo-api/models"
"go-todo-api/services"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// 辅助函数:从 Gin Context 中获取用户 ID
// ❗ 修正:将此函数放在包级别 (不在任何结构体内)
func getUserIDFromContext(c *gin.Context) (uint, error) {
// Note: UserIDKey must be imported from the constants package
// ... (Your implementation using constants.UserIDKey)
userIDStr, exists := c.Get(constants.UserIDKey)
if !exists {
return 0, errors.New("User ID not found in context")
}
// 转换 string 到 uint
userIDUint, err := strconv.ParseUint(userIDStr.(string), 10, 32)
if err != nil {
return 0, errors.New("Invalid user ID format in context")
}
return uint(userIDUint), nil
}
// TodoHandler 结构体持有 Service 接口,用于处理 HTTP 请求
type TodoHandler struct {
// Handler 层依赖于 Service 接口
Service services.TodoService
}
// NewTodoHandler 实例化一个新的 TodoHandler
func NewTodoHandler(service services.TodoService) *TodoHandler {
return &TodoHandler{Service: service}
}
// FindAllTodosHandler 处理 GET /todos 请求
func (h *TodoHandler) FindAllTodosHandler(c *gin.Context) {
userID, err := getUserIDFromContext(c) // 1. 获取 UserID
if err != nil || userID == 0 {
// 401 错误,使用自定义代码 CodeInvalidAuth
c.JSON(http.StatusUnauthorized, constants.StandardResponse{
Code: constants.CodeInvalidAuth,
Message: "Unauthorized or Invalid User ID",
})
return
}
// 1. 调用 Service 层获取数据 (Service 层调用 Repository 层)
todos, err := h.Service.FindAllTodos(userID)
if err != nil {
// 500 内部错误,使用自定义代码 CodeInternalError
c.JSON(http.StatusInternalServerError, constants.StandardResponse{
Code: constants.CodeInternalError,
Message: fmt.Sprintf("Failed to retrieve todos: %v", err),
})
return
}
// 200 成功响应,使用 CodeSuccess
// 2. 返回 JSON 响应 (Handler 负责 HTTP 细节)
c.JSON(http.StatusOK, constants.StandardResponse{
Code: constants.CodeSuccess,
Message: "Successfully retrieved todos",
Data: todos,
})
}
// FindTodoByIDHandler 处理 GET /todos/:id 请求
func (h *TodoHandler) FindTodoByIDHandler(c *gin.Context) {
userID, err := getUserIDFromContext(c) // 1. 获取 UserID
if err != nil || userID == 0 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized or Invalid User ID"})
return
}
// 1. 获取 URL 参数 ID
idStr := c.Param("id")
// 将字符串 ID 转换为无符号整数 (uint)
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid Todo ID format"})
return
}
// 2. 调用 Service 层获取数据
todo, err := h.Service.FindTodoByID(uint(id), userID)
if err != nil {
// 检查是否是“未找到记录”错误
if errors.Is(err, gorm.ErrRecordNotFound) {
// 404 资源未找到,使用 CodeResourceNotFound
c.JSON(http.StatusNotFound, constants.StandardResponse{
Code: constants.CodeResourceNotFound,
Message: "Todo item not found",
})
return
}
// 其他数据库错误
c.JSON(http.StatusInternalServerError, constants.StandardResponse{
Code: constants.CodeInternalError,
Message: fmt.Sprintf("Failed to retrieve todo: %v", err),
})
return
}
// 3. 返回 JSON 响应
c.JSON(http.StatusOK, constants.StandardResponse{
Code: constants.CodeSuccess,
Message: "Successfully retrieved todo",
Data: todo,
})
}
// DeleteTodoByIDHandler 处理 DELETE /todos/:id 请求
func (h *TodoHandler) DeleteTodoByIDHandler(c *gin.Context) {
userID, err := getUserIDFromContext(c) // 1. 获取 UserID
if err != nil || userID == 0 {
// 401 错误,使用自定义代码 CodeInvalidAuth
c.JSON(http.StatusUnauthorized, constants.StandardResponse{
Code: constants.CodeInvalidAuth,
Message: "Unauthorized or Invalid User ID",
})
return
}
// 1. 获取并解析 ID
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, constants.StandardResponse{
Code: constants.CodeInvalidAuth,
Message: "Invalid Todo ID format",
})
return
}
// 2. 调用 Service 层执行删除逻辑
err = h.Service.DeleteTodoByID(uint(id), userID)
if err != nil {
// 检查是否是“未找到记录”错误Repository层返回的gorm.ErrRecordNotFound
if errors.Is(err, gorm.ErrRecordNotFound) {
// 404 资源未找到,使用 CodeResourceNotFound
c.JSON(http.StatusNotFound, constants.StandardResponse{
Code: constants.CodeResourceNotFound,
Message: "Todo item not found",
})
return
}
// 其他数据库错误
c.JSON(http.StatusInternalServerError, constants.StandardResponse{
Code: constants.CodeInternalError,
Message: fmt.Sprintf("Failed to delete todo item: %v", err),
})
return
}
// 3. 返回成功响应
c.JSON(http.StatusOK, constants.StandardResponse{
Code: constants.CodeSuccess,
Message: "Todo deleted successfully (soft deleted)",
})
}
// CreateTodoHandler 处理 POST /todos 请求,创建新的待办事项
func (h *TodoHandler) CreateTodoHandler(c *gin.Context) {
var newTodo models.Todo
// 1. 绑定前端 JSON 数据
// Todo 结构体中应有 binding:"required" 标签来确保数据完整性
if err := c.ShouldBindJSON(&newTodo); err != nil {
c.JSON(http.StatusBadRequest, constants.StandardResponse{
Code: constants.CodeValidationError,
Message: err.Error(),
})
return
}
userID, err := getUserIDFromContext(c) // 获取 UserID
if err != nil || userID == 0 { // ❗ 核心修正:新增检查 userID == 0
// 如果获取失败或者 ID 为 0视为未授权或无效令牌
c.JSON(http.StatusUnauthorized, constants.StandardResponse{
Code: constants.CodeInvalidAuth,
Message: "Unauthorized or Invalid User ID",
})
return
}
// 2. 调用 Service 层创建数据 (Service 会调用 Repository)
if err := h.Service.CreateTodo(&newTodo, userID); err != nil {
c.JSON(http.StatusInternalServerError, constants.StandardResponse{
Code: constants.CodeInternalError,
Message: fmt.Sprintf("Failed to save todo item: %v", err),
})
return
}
// 3. 返回成功响应
// newTodo 现在包含了 GORM 自动生成的 ID 和时间戳
c.JSON(http.StatusCreated, constants.StandardResponse{
Code: constants.CodeSuccess,
Message: "Todo created successfully (via Service)",
Data: newTodo,
})
}
// UpdateTodoHandler 处理 PATCH /todos/:id 请求
func (h *TodoHandler) UpdateTodoHandler(c *gin.Context) {
userID, err := getUserIDFromContext(c) // 1. 获取 UserID
if err != nil || userID == 0 {
c.JSON(http.StatusUnauthorized, constants.StandardResponse{
Code: constants.CodeInvalidAuth,
Message: "Unauthorized or Invalid User ID",
})
return
}
// 1. 获取并解析 ID
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, constants.StandardResponse{
Code: constants.CodeValidationError,
Message: "Invalid Todo ID format",
})
return
}
// 2. 接收更新的 JSON 数据到 map 中(用于部分更新)
var input map[string]interface{}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, constants.StandardResponse{
Code: constants.CodeValidationError,
Message: err.Error(),
})
return
}
// 3. 调用 Service 层执行更新逻辑
// 注意Service 层将负责查找记录,然后执行更新
updatedTodo, err := h.Service.UpdateTodo(uint(id), userID, input)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, constants.StandardResponse{
Code: constants.CodeResourceNotFound,
Message: "Todo item not found",
})
return
}
c.JSON(http.StatusInternalServerError, constants.StandardResponse{
Code: constants.CodeInternalError,
Message: fmt.Sprintf("Failed to update todo item: %v", err),
})
return
}
// 4. 返回更新后的记录
c.JSON(http.StatusOK, constants.StandardResponse{
Code: constants.CodeSuccess,
Message: "Todo updated successfully",
Data: updatedTodo,
})
}