270 lines
8.1 KiB
Go
270 lines
8.1 KiB
Go
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,
|
||
})
|
||
}
|