```
feat(api): 初始化项目基础结构与API定义 新增 `.gitignore` 文件,忽略编译输出、生成代码及数据库文件。 新增 `README.md`,包含 Gin 框架和 Swagger 工具的安装与使用说明。 新增 `config/api.yaml`,定义 bookmarks 相关的文件夹与书签操作的 OpenAPI 3.0 接口规范。 新增 `config/cfg.yaml`,配置 oapi-codegen 工具生成 Gin 服务和模型代码。 新增 `go.mod` 和 `go.sum` 文件,初始化 Go 模块并引入 Gin、GORM、SQLite 及 oapi-codegen 等依赖。 ```
This commit is contained in:
277
internal/handlers/todo_list.go
Normal file
277
internal/handlers/todo_list.go
Normal file
@ -0,0 +1,277 @@
|
||||
// internal/handlers/todo_list.go
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Completed bool `json:"completed"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
type TaskRequest struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// 使用内存存储任务数据,按命名空间隔离
|
||||
var (
|
||||
tasks = make(map[string]map[int]Task) // namespace -> taskID -> Task
|
||||
taskMutex = sync.RWMutex{}
|
||||
nextTaskIDs = make(map[string]int) // namespace -> nextID
|
||||
)
|
||||
|
||||
func TodoHandler(r *gin.RouterGroup) {
|
||||
todos := r.Group("/todo/v1")
|
||||
{
|
||||
todos.GET("/tasks", getTasks)
|
||||
todos.POST("/tasks", createTask)
|
||||
todos.PUT("/tasks/:id", updateTask)
|
||||
todos.DELETE("/tasks/:id", deleteTask)
|
||||
todos.PATCH("/tasks/:id/complete", completeTask)
|
||||
}
|
||||
}
|
||||
|
||||
// getNamespace 从请求头或查询参数中获取命名空间
|
||||
func getNamespace(c *gin.Context) string {
|
||||
// 优先从请求头获取命名空间
|
||||
namespace := c.GetHeader("X-Namespace")
|
||||
if namespace == "" {
|
||||
// 如果没有则从查询参数获取
|
||||
namespace = c.Query("namespace")
|
||||
}
|
||||
|
||||
// 如果都没有提供,默认使用"default"
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
return namespace
|
||||
}
|
||||
|
||||
// @Summary 获取任务列表
|
||||
// @Description 获取所有待办任务
|
||||
// @Tags todo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param namespace header string false "命名空间"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Success 200 {array} Task
|
||||
// @Router /todo/v1/tasks [get]
|
||||
func getTasks(c *gin.Context) {
|
||||
namespace := getNamespace(c)
|
||||
|
||||
taskMutex.RLock()
|
||||
defer taskMutex.RUnlock()
|
||||
|
||||
namespaceTasks, exists := tasks[namespace]
|
||||
if !exists {
|
||||
c.JSON(http.StatusOK, []Task{})
|
||||
return
|
||||
}
|
||||
|
||||
taskList := make([]Task, 0, len(namespaceTasks))
|
||||
for _, task := range namespaceTasks {
|
||||
taskList = append(taskList, task)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, taskList)
|
||||
}
|
||||
|
||||
// @Summary 创建任务
|
||||
// @Description 创建一个新的待办任务
|
||||
// @Tags todo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param namespace header string false "命名空间"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Param task body TaskRequest true "任务信息"
|
||||
// @Success 201 {object} Task
|
||||
// @Router /todo/v1/tasks [post]
|
||||
func createTask(c *gin.Context) {
|
||||
namespace := getNamespace(c)
|
||||
|
||||
var req TaskRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
taskMutex.Lock()
|
||||
defer taskMutex.Unlock()
|
||||
|
||||
// 初始化命名空间
|
||||
if tasks[namespace] == nil {
|
||||
tasks[namespace] = make(map[int]Task)
|
||||
nextTaskIDs[namespace] = 1
|
||||
}
|
||||
|
||||
// 生成新任务ID
|
||||
taskID := nextTaskIDs[namespace]
|
||||
nextTaskIDs[namespace]++
|
||||
|
||||
// 创建任务
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
task := Task{
|
||||
ID: taskID,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Completed: false,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
// 存储任务
|
||||
tasks[namespace][taskID] = task
|
||||
|
||||
c.JSON(http.StatusCreated, task)
|
||||
}
|
||||
|
||||
// @Summary 更新任务
|
||||
// @Description 更新指定ID的任务
|
||||
// @Tags todo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param namespace header string false "命名空间"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Param id path int true "任务ID"
|
||||
// @Param task body TaskRequest true "任务信息"
|
||||
// @Success 200 {object} Task
|
||||
// @Router /todo/v1/tasks/{id} [put]
|
||||
func updateTask(c *gin.Context) {
|
||||
namespace := getNamespace(c)
|
||||
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var req TaskRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
taskMutex.Lock()
|
||||
defer taskMutex.Unlock()
|
||||
|
||||
// 检查命名空间是否存在
|
||||
if tasks[namespace] == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查任务是否存在
|
||||
task, exists := tasks[namespace][id]
|
||||
if !exists {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新任务
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
task.Title = req.Title
|
||||
task.Description = req.Description
|
||||
task.UpdatedAt = now
|
||||
|
||||
// 保存更新
|
||||
tasks[namespace][id] = task
|
||||
|
||||
c.JSON(http.StatusOK, task)
|
||||
}
|
||||
|
||||
// @Summary 删除任务
|
||||
// @Description 删除指定ID的任务
|
||||
// @Tags todo
|
||||
// @Produce json
|
||||
// @Param namespace header string false "命名空间"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Param id path int true "任务ID"
|
||||
// @Success 200 {object} string
|
||||
// @Router /todo/v1/tasks/{id} [delete]
|
||||
func deleteTask(c *gin.Context) {
|
||||
namespace := getNamespace(c)
|
||||
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
|
||||
return
|
||||
}
|
||||
|
||||
taskMutex.Lock()
|
||||
defer taskMutex.Unlock()
|
||||
|
||||
// 检查命名空间是否存在
|
||||
if tasks[namespace] == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查任务是否存在
|
||||
_, exists := tasks[namespace][id]
|
||||
if !exists {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// 删除任务
|
||||
delete(tasks[namespace], id)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Task deleted successfully"})
|
||||
}
|
||||
|
||||
// @Summary 完成任务
|
||||
// @Description 标记指定ID的任务为完成状态
|
||||
// @Tags todo
|
||||
// @Produce json
|
||||
// @Param namespace header string false "命名空间"
|
||||
// @Param namespace query string false "命名空间"
|
||||
// @Param id path int true "任务ID"
|
||||
// @Success 200 {object} Task
|
||||
// @Router /todo/v1/tasks/{id}/complete [patch]
|
||||
func completeTask(c *gin.Context) {
|
||||
namespace := getNamespace(c)
|
||||
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
|
||||
return
|
||||
}
|
||||
|
||||
taskMutex.Lock()
|
||||
defer taskMutex.Unlock()
|
||||
|
||||
// 检查命名空间是否存在
|
||||
if tasks[namespace] == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查任务是否存在
|
||||
task, exists := tasks[namespace][id]
|
||||
if !exists {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// 标记为完成
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
task.Completed = true
|
||||
task.UpdatedAt = now
|
||||
|
||||
// 保存更新
|
||||
tasks[namespace][id] = task
|
||||
|
||||
c.JSON(http.StatusOK, task)
|
||||
}
|
Reference in New Issue
Block a user