// 待办接口 package app import ( "fmt" "math/rand" "os" "simple-memo/global" "simple-memo/models" "simple-memo/utils" "time" "gorm.io/gorm" "github.com/gin-gonic/gin" ) // -------------------------- 1. 定义 Handler 接口 -------------------------- // TodoHandler 待办模块接口定义 type TodoHandler interface { AddTodo(c *gin.Context) // 添加待办 TodoList(c *gin.Context) // 获取待办列表 DeleteTodo(c *gin.Context) // 删除待办 DoneTodo(c *gin.Context) // 完成/取消完成待办 EditTodo(c *gin.Context) // 编辑待办 TodoStat(c *gin.Context) // 待办统计 } // -------------------------- 2. 实现结构体 -------------------------- // todoHandler 接口实现结构体 type todoHandler struct { db *gorm.DB // 数据库依赖 } // NewTodoHandler 创建待办处理器 func NewTodoHandler() TodoHandler { return &todoHandler{ db: global.DB, } } // -------------------------- 3. 请求结构体 -------------------------- // TodoListReq 列表请求参数 type TodoListReq struct { Search string `json:"search"` Status string `json:"status"` Date string `json:"date"` StartDate string `json:"start_date"` EndDate string `json:"end_date"` } // DeleteTodoReq 删除待办请求 type DeleteTodoReq struct { ID uint `json:"id"` } // DoneTodoReq 完成待办请求 type DoneTodoReq struct { ID uint `json:"id"` Status uint `json:"status"` // 0-取消完成 1-完成 } // EditTodoReq 编辑待办请求(与model对齐) type EditTodoReq struct { ID uint `json:"id"` Title string `json:"title"` Category string `json:"category"` Priority int `json:"priority"` Remark string `json:"remark"` Date string `json:"date"` StartTime string `json:"start_time"` EndTime string `json:"end_time"` } // TodoStatReq 待办统计请求 type TodoStatReq struct { StartDate string `json:"start_date" binding:"required"` // 开始日期 EndDate string `json:"end_date" binding:"required"` // 结束日期 } // -------------------------- 4. 接口实现 -------------------------- // GetTodoListForCalendar 获取日历月份的待办列表(内部方法) func (h *todoHandler) GetTodoListForCalendar(c *gin.Context, year, month int) []interface{} { userID := c.GetUint("userID") // 计算月份范围 startDate := fmt.Sprintf("%d-%02d-01", year, month) // 计算月末 _, lastDay := utils.GetMonthLastDay(year, month) endDate := fmt.Sprintf("%d-%02d-%02d", year, month, lastDay) var list []models.Todo h.db.Model(&models.Todo{}). Where("user_id = ? AND date BETWEEN ? AND ?", userID, startDate, endDate). Order("date ASC, id ASC"). Find(&list) // 转换结果 result := make([]interface{}, len(list)) for i, v := range list { if v.Done == 0 && v.Date < time.Now().Format("2006-01-02") { v.IsOverdue = 1 } else { v.IsOverdue = 0 } result[i] = v } return result } // AddTodo 添加待办 func (h *todoHandler) AddTodo(c *gin.Context) { userID := c.GetUint("userID") var todo models.Todo if err := c.ShouldBindJSON(&todo); err != nil { utils.Fail(c, "参数解析失败") return } // 敏感词过滤 todo.Title = utils.FilterSensitive(todo.Title) todo.Remark = utils.FilterSensitive(todo.Remark) // 检查每日限制(最多100个待办/天) today := time.Now().Format("2006-01-02") var todayCount int64 if err := h.db.Model(&models.Todo{}).Where("user_id = ? AND date = ? AND deleted_at IS NULL", userID, today).Count(&todayCount).Error; err != nil { utils.Fail(c, "系统错误") return } if todayCount >= 100 { utils.Fail(c, "今日待办数量已达上限(100个)") return } todo.UserID = userID if err := h.db.Create(&todo).Error; err != nil { utils.Fail(c, "添加失败") return } utils.Ok(c, nil) } // TodoList 获取待办列表 func (h *todoHandler) TodoList(c *gin.Context) { userID := c.GetUint("userID") var req TodoListReq if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, "参数解析失败") return } // 构建查询 db := h.db.Model(&models.Todo{}).Where("user_id = ?", userID) // 搜索 if req.Search != "" { db = db.Where("title LIKE ? OR remark LIKE ?", "%"+req.Search+"%", "%"+req.Search+"%") } // 状态筛选 switch req.Status { case "undone": db = db.Where("done = 0") case "done": db = db.Where("done = 1") } // 日期 now := time.Now() today := now.Format("2006-01-02") startDate := req.StartDate endDate := req.EndDate if startDate != "" && endDate != "" { db = db.Where("date BETWEEN ? AND ?", startDate, endDate) } else if startDate != "" { db = db.Where("date >= ?", startDate) } else if endDate != "" { db = db.Where("date <= ?", endDate) } else if req.Date != "" { db = db.Where("date = ?", req.Date) } else { db = db.Where("date = ?", today) } // 排序 db = db.Order("date ASC, id ASC") // 查询 var list []models.Todo if err := db.Find(&list).Error; err != nil { utils.Fail(c, "获取失败") return } // 逾期状态 for i := range list { if list[i].Done == 0 && list[i].Date < today { list[i].IsOverdue = 1 } else { list[i].IsOverdue = 0 } } utils.Ok(c, list) } // DeleteTodo 删除待办 func (h *todoHandler) DeleteTodo(c *gin.Context) { userID := c.GetUint("userID") var req DeleteTodoReq if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, "参数解析失败") return } // 查询待办信息(用于扣除积分) var todo models.Todo err := h.db.Where("id = ? AND user_id = ?", req.ID, userID).First(&todo).Error if err != nil { utils.Fail(c, "待办不存在") return } // 删除待办 err = h.db.Where("id = ? AND user_id = ?", req.ID, userID).Delete(&models.Todo{}).Error if err != nil { utils.Fail(c, "删除失败") return } // 扣除今天添加时获得的积分 growthHandler := NewGrowthHandler() today := time.Now().Format("2006-01-02") deducted := growthHandler.DeductExpInternal(userID, "task", fmt.Sprintf("%d", req.ID), today) utils.Ok(c, gin.H{ "deducted_exp": deducted, }) } // DoneTodo 完成/取消完成待办 func (h *todoHandler) DoneTodo(c *gin.Context) { userID := c.GetUint("userID") var req DoneTodoReq if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, "参数解析失败") return } if req.Status != 0 && req.Status != 1 { utils.Fail(c, "参数错误") return } // 先查询当前状态 var todo models.Todo err := h.db.Where("id = ? AND user_id = ?", req.ID, userID).First(&todo).Error if err != nil { utils.Fail(c, "待办不存在") return } oldDone := todo.Done // 更新状态 err = h.db.Model(&models.Todo{}). Where("id = ? AND user_id = ?", req.ID, userID). Update("done", req.Status).Error if err != nil { utils.Fail(c, "操作失败") return } var result any debugMode := os.Getenv("DEBUG_MODE") // 只有从未完成变成完成(0→1)的时候才加积分,并且这个待办之前没有加过积分 if oldDone == 0 && req.Status == 1 { // 查询是否已经给这个待办加过积分 var count int64 h.db.Model(&models.ExpChangeLog{}). Where("user_id = ? AND source = ? AND source_id = ?", userID, "task", fmt.Sprintf("%d", req.ID)). Count(&count) // 只有之前没加过积分才加 if count == 0 { // ==================== 调试模式 ==================== if debugMode == "true" { rand.Seed(time.Now().UnixNano()) levelUp := rand.Intn(10) < 3 // 30%概率升级 hasAchievement := rand.Intn(10) < 4 // 40%概率获得成就 result = MockGrowthRewards(levelUp, hasAchievement) } else { // 生产模式:真实调用 growthHandler := NewGrowthHandler() result = growthHandler.AddExpInternal(userID, "task", fmt.Sprintf("%d", req.ID), nil) } // ================================================== } } utils.Ok(c, gin.H{ "success": true, "growth": result, "debugMode": debugMode, "isDebug": debugMode == "true", }) } // EditTodo 编辑待办 func (h *todoHandler) EditTodo(c *gin.Context) { userID := c.GetUint("userID") var req EditTodoReq if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, "参数解析失败") return } // 敏感词过滤 filteredTitle := utils.FilterSensitive(req.Title) filteredRemark := utils.FilterSensitive(req.Remark) data := map[string]any{ "title": filteredTitle, "remark": filteredRemark, "category": req.Category, "priority": req.Priority, "date": req.Date, "start_time": req.StartTime, "end_time": req.EndTime, } err := h.db.Model(&models.Todo{}). Where("id = ? AND user_id = ?", req.ID, userID). Updates(data).Error if err != nil { utils.Fail(c, "编辑失败") return } utils.Ok(c, nil) } // TodoStat 待办统计 func (h *todoHandler) TodoStat(c *gin.Context) { userID := c.GetUint("userID") var req TodoStatReq if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, "参数解析失败") return } // 总体统计 var total, done, undone int64 h.db.Model(&models.Todo{}). Where("user_id = ? AND date BETWEEN ? AND ?", userID, req.StartDate, req.EndDate). Count(&total) h.db.Model(&models.Todo{}). Where("user_id = ? AND date BETWEEN ? AND ? AND done = 1", userID, req.StartDate, req.EndDate). Count(&done) undone = total - done // 完成率 completionRate := 0.0 if total > 0 { completionRate = float64(done) / float64(total) * 100 } // 按分类统计 type CategoryStat struct { Total int64 `json:"total"` Done int64 `json:"done"` } byCategory := make(map[string]CategoryStat) var categoryResults []struct { Category string Total int64 Done int64 } h.db.Model(&models.Todo{}). Select("category, COUNT(*) as total, SUM(CASE WHEN done = 1 THEN 1 ELSE 0 END) as done"). Where("user_id = ? AND date BETWEEN ? AND ?", userID, req.StartDate, req.EndDate). Group("category"). Scan(&categoryResults) for _, r := range categoryResults { byCategory[r.Category] = CategoryStat{ Total: r.Total, Done: r.Done, } } // 按优先级统计 type PriorityStat struct { Total int64 `json:"total"` Done int64 `json:"done"` } byPriority := make(map[string]PriorityStat) var priorityResults []struct { Priority int Total int64 Done int64 } h.db.Model(&models.Todo{}). Select("priority, COUNT(*) as total, SUM(CASE WHEN done = 1 THEN 1 ELSE 0 END) as done"). Where("user_id = ? AND date BETWEEN ? AND ?", userID, req.StartDate, req.EndDate). Group("priority"). Scan(&priorityResults) for _, r := range priorityResults { key := fmt.Sprintf("%d", r.Priority) byPriority[key] = PriorityStat{ Total: r.Total, Done: r.Done, } } utils.Ok(c, gin.H{ "total": total, "done": done, "undone": undone, "completion_rate": completionRate, "by_category": byCategory, "by_priority": byPriority, }) }