- 新增「简记memo」一体化小程序产品原型设计文档 - 新增简记memo完整版UI视觉设计规范和界面细节 - 添加IDEA项目配置文件.gitignore - 创建404页面HTML文件,包含响应式布局和错误提示 - 添加关于页面HTML文件,展示品牌介绍和团队信息 - 实现AES加解密工具函数,支持请求体加密 - 添加用户协议页面基础框架
437 lines
11 KiB
Go
437 lines
11 KiB
Go
// 待办接口
|
|
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,
|
|
})
|
|
}
|