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:
zzy
2025-09-21 00:20:29 +08:00
commit 7ff8591be8
11 changed files with 2033 additions and 0 deletions

View 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)
}