package controllers import ( "crypto/rand" "encoding/json" "fmt" "io" "server/middleware" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" beego "github.com/beego/beego/v2/server/web" ) type AppReminderController struct { beego.Controller } func (c *AppReminderController) appClaims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") } parts := strings.SplitN(auth, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { return nil, fmt.Errorf("认证信息格式错误") } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { return nil, fmt.Errorf("无效的token") } if claims.UserType != "backend" && claims.UserType != "app" && claims.UserType != "platform" { return nil, fmt.Errorf("无权访问") } return claims, nil } func (c *AppReminderController) jsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *AppReminderController) ok(data interface{}) { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} _ = c.ServeJSON() } func appGenerateToken() string { b := make([]byte, 16) _, _ = rand.Read(b) b[6] = (b[6] & 0x0f) | 0x40 b[8] = (b[8] & 0x3f) | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) } type appSchedulePayload struct { Content string `json:"content"` ScheduleTime string `json:"schedule_time"` RemindChannels []string `json:"remind_channels"` AdvanceMinutes int `json:"advance_minutes"` RepeatIntervalMinutes int `json:"repeat_interval_minutes"` MaxSendCount int `json:"max_send_count"` } // GetList GET /app/schedule/list func (c *AppReminderController) GetList() { claims, err := c.appClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } page, _ := c.GetInt("page", 1) pageSize, _ := c.GetInt("pageSize", 20) keyword := c.GetString("keyword", "") status := c.GetString("status", "") if page < 1 { page = 1 } if pageSize < 1 || pageSize > 100 { pageSize = 20 } qs := models.Orm.QueryTable(new(models.PlatformSchedule)). Filter("user_id", claims.UserID) if keyword != "" { qs = qs.Filter("content__contains", keyword) } total, _ := qs.Count() var schedules []models.PlatformSchedule _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&schedules) if err != nil { c.jsonErr(500, 500, "查询失败: "+err.Error()) return } list := make([]map[string]interface{}, 0, len(schedules)) for _, s := range schedules { var reminders []models.PlatformScheduleReminder _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). Filter("schedule_id", s.ID). Filter("is_deleted", 0). All(&reminders) channels := make([]string, 0, len(reminders)) isFinished := true if len(reminders) == 0 { isFinished = false } else { for _, r := range reminders { channels = append(channels, r.RemindChannel) if r.RemindStatus != 2 { isFinished = false } } } // status filter if status == "pending" && isFinished { continue } if status == "done" && !isFinished { continue } item := map[string]interface{}{ "id": s.ID, "content": s.Content, "schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"), "remind_channels": channels, "is_finished": isFinished, "created_at": s.ScheduleTime.Format("2006-01-02 15:04:05"), "updated_at": s.ScheduleTime.Format("2006-01-02 15:04:05"), } if len(reminders) > 0 { first := reminders[0] item["advance_minutes"] = first.AdvanceMinutes if !first.CreateTime.IsZero() { item["created_at"] = first.CreateTime.Format("2006-01-02 15:04:05") } if !first.UpdateTime.IsZero() { item["updated_at"] = first.UpdateTime.Format("2006-01-02 15:04:05") } } list = append(list, item) } c.ok(map[string]interface{}{ "list": list, "total": total, "page": page, "pageSize": pageSize, }) } // GetDetail GET /app/schedule/:id func (c *AppReminderController) GetDetail() { claims, err := c.appClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseUint(idStr, 10, 64) if id == 0 { c.jsonErr(400, 400, "无效的ID") return } var schedule models.PlatformSchedule err = models.Orm.QueryTable(new(models.PlatformSchedule)). Filter("id", id). Filter("user_id", claims.UserID). One(&schedule) if err != nil { c.jsonErr(404, 404, "日程未找到") return } var reminders []models.PlatformScheduleReminder _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). Filter("schedule_id", schedule.ID). Filter("is_deleted", 0). All(&reminders) channels := make([]string, 0, len(reminders)) var first models.PlatformScheduleReminder for _, r := range reminders { channels = append(channels, r.RemindChannel) first = r } isFinished := true if len(reminders) == 0 { isFinished = false } else { for _, r := range reminders { if r.RemindStatus != 2 { isFinished = false break } } } data := map[string]interface{}{ "id": schedule.ID, "content": schedule.Content, "schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"), "remind_channels": channels, "is_finished": isFinished, } if first.ID > 0 { data["advance_minutes"] = first.AdvanceMinutes if !first.CreateTime.IsZero() { data["created_at"] = first.CreateTime.Format("2006-01-02 15:04:05") } if !first.UpdateTime.IsZero() { data["updated_at"] = first.UpdateTime.Format("2006-01-02 15:04:05") } } c.ok(data) } // Create POST /app/schedule func (c *AppReminderController) Create() { claims, err := c.appClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var p appSchedulePayload if err := json.Unmarshal(raw, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } if strings.TrimSpace(p.Content) == "" { c.jsonErr(400, 400, "日程内容不能为空") return } if strings.TrimSpace(p.ScheduleTime) == "" { c.jsonErr(400, 400, "日程发生时间不能为空") return } schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local) if err != nil { c.jsonErr(400, 400, "日程时间格式不合法,支持 YYYY-MM-DD HH:mm:ss") return } content := strings.TrimSpace(p.Content) title := strings.SplitN(content, "\n", 2)[0] if len(title) > 60 { title = title[:60] } if title == "" { title = "日程提醒" } schedule := models.PlatformSchedule{ Title: title, Content: content, ScheduleTime: schedTime, UserID: uint64(claims.UserID), } schedID, err := models.Orm.Insert(&schedule) if err != nil { c.jsonErr(500, 500, "保存日程失败: "+err.Error()) return } for _, ch := range p.RemindChannels { ch = strings.ToUpper(strings.TrimSpace(ch)) if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" && ch != "APP" { continue } if ch == "APP" { ch = "SITE_MSG" } firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute) reminder := models.PlatformScheduleReminder{ ScheduleID: uint64(schedID), RemindChannel: ch, AdvanceMinutes: p.AdvanceMinutes, NextRemindTime: firstSendTime, ReceiverUserID: uint64(claims.UserID), RemindStatus: 0, CreateTime: time.Now(), UpdateTime: time.Now(), } if ch == "EMAIL" || ch == "BARK" { token := appGenerateToken() reminder.AckToken = &token reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes reminder.MaxSendCount = p.MaxSendCount if reminder.MaxSendCount <= 0 { reminder.MaxSendCount = 1 } } else { reminder.RepeatIntervalMinutes = 0 reminder.MaxSendCount = 1 } _, err = models.Orm.Insert(&reminder) if err != nil { c.jsonErr(500, 500, "创建提醒失败: "+err.Error()) return } } c.ok(map[string]interface{}{"id": schedID}) } // Update PUT /app/schedule/:id func (c *AppReminderController) Update() { claims, err := c.appClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseUint(idStr, 10, 64) if id == 0 { c.jsonErr(400, 400, "无效的ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.jsonErr(400, 400, "参数错误") return } var p appSchedulePayload if err := json.Unmarshal(raw, &p); err != nil { c.jsonErr(400, 400, "参数错误") return } schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local) if err != nil { c.jsonErr(400, 400, "日程时间格式不合法") return } var schedule models.PlatformSchedule err = models.Orm.QueryTable(new(models.PlatformSchedule)). Filter("id", id). Filter("user_id", claims.UserID). One(&schedule) if err != nil { c.jsonErr(404, 404, "日程未找到") return } var reminders []models.PlatformScheduleReminder _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). Filter("schedule_id", id). Filter("is_deleted", 0). All(&reminders) isFinished := true if len(reminders) == 0 { isFinished = false } else { for _, r := range reminders { if r.RemindStatus != 2 { isFinished = false break } } } if isFinished { c.jsonErr(400, 400, "该日程提醒已全部结束,无法编辑") return } content := strings.TrimSpace(p.Content) if content == "" { content = schedule.Content } title := strings.SplitN(content, "\n", 2)[0] if len(title) > 60 { title = title[:60] } schedule.Title = title schedule.Content = content schedule.ScheduleTime = schedTime _, err = models.Orm.Update(&schedule, "Title", "Content", "ScheduleTime") if err != nil { c.jsonErr(500, 500, "更新失败") return } // 软删除旧提醒,重建 _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). Filter("schedule_id", id). Update(map[string]interface{}{ "IsDeleted": 1, "UpdateTime": time.Now(), }) for _, ch := range p.RemindChannels { ch = strings.ToUpper(strings.TrimSpace(ch)) if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" && ch != "APP" { continue } if ch == "APP" { ch = "SITE_MSG" } firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute) reminder := models.PlatformScheduleReminder{ ScheduleID: id, RemindChannel: ch, AdvanceMinutes: p.AdvanceMinutes, NextRemindTime: firstSendTime, ReceiverUserID: schedule.UserID, RemindStatus: 0, CreateTime: time.Now(), UpdateTime: time.Now(), } if ch == "EMAIL" || ch == "BARK" { token := appGenerateToken() reminder.AckToken = &token reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes reminder.MaxSendCount = p.MaxSendCount if reminder.MaxSendCount <= 0 { reminder.MaxSendCount = 1 } } else { reminder.RepeatIntervalMinutes = 0 reminder.MaxSendCount = 1 } _, err = models.Orm.Insert(&reminder) if err != nil { c.jsonErr(500, 500, "重新创建提醒失败") return } } c.ok(nil) } // Delete DELETE /app/schedule/:id func (c *AppReminderController) Delete() { claims, err := c.appClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseUint(idStr, 10, 64) if id == 0 { c.jsonErr(400, 400, "无效的ID") return } var schedule models.PlatformSchedule err = models.Orm.QueryTable(new(models.PlatformSchedule)). Filter("id", id). Filter("user_id", claims.UserID). One(&schedule) if err != nil { c.jsonErr(404, 404, "日程未找到") return } var reminders []models.PlatformScheduleReminder _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). Filter("schedule_id", id). Filter("is_deleted", 0). All(&reminders) isFinished := true if len(reminders) == 0 { isFinished = false } else { for _, r := range reminders { if r.RemindStatus != 2 { isFinished = false break } } } if isFinished { c.jsonErr(400, 400, "该日程提醒已全部结束,无法删除") return } _, err = models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).Delete() if err == nil { _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). Filter("schedule_id", id). Update(map[string]interface{}{ "IsDeleted": 1, "UpdateTime": time.Now(), }) } middleware.WriteDeleteLog(uint64(claims.UserID), claims.TenantId, "schedule", schedule.Content) c.ok(nil) } // ToggleComplete POST /app/schedule/:id/toggle func (c *AppReminderController) ToggleComplete() { claims, err := c.appClaims() if err != nil { c.jsonErr(401, 401, err.Error()) return } idStr := c.Ctx.Input.Param(":id") id, _ := strconv.ParseUint(idStr, 10, 64) if id == 0 { c.jsonErr(400, 400, "无效的ID") return } var schedule models.PlatformSchedule err = models.Orm.QueryTable(new(models.PlatformSchedule)). Filter("id", id). Filter("user_id", claims.UserID). One(&schedule) if err != nil { c.jsonErr(404, 404, "日程未找到") return } var reminders []models.PlatformScheduleReminder _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). Filter("schedule_id", id). Filter("is_deleted", 0). All(&reminders) // Toggle: if all finished -> set to pending; else -> set to finished allFinished := len(reminders) > 0 for _, r := range reminders { if r.RemindStatus != 2 { allFinished = false break } } var newStatus int8 if allFinished { newStatus = 0 // reopen } else { newStatus = 2 // mark done } now := time.Now() for _, r := range reminders { r.RemindStatus = newStatus r.UpdateTime = now models.Orm.Update(&r, "RemindStatus", "UpdateTime") } c.ok(map[string]interface{}{ "is_finished": newStatus == 2, }) }