完善uniapp

This commit is contained in:
2026-07-16 00:35:50 +08:00
parent 61a937f7a3
commit 834c088aa2
29 changed files with 4444 additions and 305 deletions
+178
View File
@@ -0,0 +1,178 @@
package controllers
import (
"encoding/json"
"fmt"
"server/models"
"server/pkg/jwtutil"
"strings"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
type AppActivityController struct {
beego.Controller
}
func (c *AppActivityController) activityClaims() (*jwtutil.Claims, error) {
auth := c.Ctx.Request.Header.Get("Authorization")
if auth == "" {
return nil, orm.ErrNoRows
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, orm.ErrNoRows
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
return nil, err
}
if claims.UserType != "backend" && claims.UserType != "app" && claims.UserType != "platform" {
return nil, orm.ErrNoRows
}
return claims, nil
}
// GetList GET /app/activity/list
func (c *AppActivityController) GetList() {
claims, err := c.activityClaims()
if err != nil {
c.Ctx.Output.SetStatus(401)
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录"}
_ = c.ServeJSON()
return
}
limit, _ := c.GetInt("limit", 10)
if limit < 1 || limit > 50 {
limit = 10
}
var logs []models.SystemOperationLog
qs := models.Orm.QueryTable(new(models.SystemOperationLog)).
Filter("user_id", claims.UserID).
Exclude("action__in", "登录", "退出", "查询").
OrderBy("-create_time").
Limit(limit)
_, err = qs.All(&logs)
if err != nil && err != orm.ErrNoRows {
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"}
_ = c.ServeJSON()
return
}
type activityItem struct {
ID uint64 `json:"id"`
Action string `json:"action"`
TargetType string `json:"target_type"`
Title string `json:"title"`
URL string `json:"url"`
CreatedAt string `json:"created_at"`
}
list := make([]activityItem, 0, len(logs))
for _, l := range logs {
module := l.Module
action := l.Action
// 从请求体中提取标题
title := extractTitle(l.Module, l.Action, l.RequestData)
if title == "" {
title = fmt.Sprintf("您%s了:%s", actionLabel(action), moduleLabel(module))
}
list = append(list, activityItem{
ID: l.ID,
Action: action,
TargetType: module,
Title: title,
URL: l.URL,
CreatedAt: l.CreateTime.Format("2006-01-02 15:04:05"),
})
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list}
_ = c.ServeJSON()
}
// extractTitle 从请求数据中提取可读标题
func extractTitle(module, action string, reqData *string) string {
if reqData == nil {
return ""
}
data := strings.TrimSpace(*reqData)
if data == "" {
return ""
}
// 解析 JSON 请求体
var body map[string]interface{}
if err := json.Unmarshal([]byte(data), &body); err != nil {
return ""
}
moduleCN := moduleLabel(module)
actionCN := actionLabel(action)
// 笔记本:取 title
if module == "notebook" {
if t, ok := body["title"].(string); ok && t != "" {
return fmt.Sprintf("您%s了%s:【%s】", actionCN, moduleCN, truncateStr(t, 20))
}
}
// 日程:取 content 第一行
if module == "schedule" {
if c, ok := body["content"].(string); ok && c != "" {
firstLine := strings.SplitN(c, "\n", 2)[0]
return fmt.Sprintf("您%s了%s:【%s】", actionCN, moduleCN, truncateStr(firstLine, 20))
}
}
return fmt.Sprintf("您%s了:%s", actionCN, moduleCN)
}
func truncateStr(s string, maxLen int) string {
s = strings.TrimSpace(s)
if len([]rune(s)) > maxLen {
return string([]rune(s)[:maxLen]) + "..."
}
return s
}
func moduleLabel(m string) string {
switch m {
case "notebook":
return "记事本"
case "schedule":
return "日程提醒"
case "erp":
return "ERP"
case "file":
return "文件"
case "article":
return "文章"
default:
if m == "" {
return "系统"
}
return m
}
}
func actionLabel(a string) string {
switch a {
case "新增":
return "新增"
case "编辑":
return "编辑"
case "删除":
return "删除"
case "提交":
return "操作"
default:
return a
}
}
+376
View File
@@ -0,0 +1,376 @@
package controllers
import (
"encoding/json"
"io"
"server/middleware"
"server/models"
"server/pkg/jwtutil"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
type AppNotebookController struct {
beego.Controller
}
func (c *AppNotebookController) appNotebookClaims() (*jwtutil.Claims, error) {
auth := c.Ctx.Request.Header.Get("Authorization")
if auth == "" {
return nil, orm.ErrNoRows
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, orm.ErrNoRows
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
return nil, err
}
if claims.UserType != "backend" && claims.UserType != "app" && claims.UserType != "platform" {
return nil, orm.ErrNoRows
}
return claims, nil
}
func (c *AppNotebookController) nbJsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func (c *AppNotebookController) nbOk(data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
// GetList GET /app/notebook/list
func (c *AppNotebookController) GetList() {
claims, err := c.appNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
keyword := strings.TrimSpace(c.GetString("keyword"))
qs := models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID)
if keyword != "" {
qs = qs.Filter("title__icontains", keyword)
}
var list []models.BackendNotebook
_, err = qs.OrderBy("-pinned", "-update_time").All(&list)
if err != nil && err != orm.ErrNoRows {
c.nbJsonErr(500, 500, "查询失败")
return
}
if list == nil {
list = []models.BackendNotebook{}
}
type noteItem struct {
ID uint64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Pinned bool `json:"pinned"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
result := make([]noteItem, 0, len(list))
for _, n := range list {
item := noteItem{
ID: n.ID,
Title: n.Title,
Content: n.Content,
Pinned: n.Pinned == 1,
}
item.CreatedAt = n.CreateTime.Format("2006-01-02 15:04:05")
if n.UpdateTime != nil {
item.UpdatedAt = n.UpdateTime.Format("2006-01-02 15:04:05")
} else {
item.UpdatedAt = item.CreatedAt
}
result = append(result, item)
}
c.nbOk(map[string]interface{}{
"list": result,
"total": len(result),
})
}
// GetDetail GET /app/notebook/:id
func (c *AppNotebookController) GetDetail() {
claims, err := c.appNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.nbJsonErr(400, 400, "无效ID")
return
}
var note models.BackendNotebook
err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
c.nbJsonErr(404, 404, "笔记不存在")
return
}
createdAt := note.CreateTime.Format("2006-01-02 15:04:05")
updatedAt := createdAt
if note.UpdateTime != nil {
updatedAt = note.UpdateTime.Format("2006-01-02 15:04:05")
}
c.nbOk(map[string]interface{}{
"id": note.ID,
"title": note.Title,
"content": note.Content,
"pinned": note.Pinned == 1,
"created_at": createdAt,
"updated_at": updatedAt,
})
}
// Create POST /app/notebook
func (c *AppNotebookController) Create() {
claims, err := c.appNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.nbJsonErr(400, 400, "参数错误")
return
}
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
Pinned bool `json:"pinned"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
c.nbJsonErr(400, 400, "参数错误")
return
}
title := strings.TrimSpace(payload.Title)
if title == "" {
title = "无标题"
}
userID := uint64(claims.UserID)
pinned := int8(0)
if payload.Pinned {
pinned = 1
}
note := &models.BackendNotebook{
Tid: claims.TenantId,
Title: title,
Content: payload.Content,
Pinned: pinned,
UserID: &userID,
UserName: &claims.Username,
IsDeleted: 0,
}
id, err := models.Orm.Insert(note)
if err != nil {
c.nbJsonErr(500, 500, "创建失败")
return
}
note.ID = uint64(id)
c.nbOk(map[string]interface{}{
"id": note.ID,
"title": note.Title,
"content": note.Content,
"pinned": note.Pinned == 1,
})
}
// Update PUT /app/notebook/:id
func (c *AppNotebookController) Update() {
claims, err := c.appNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.nbJsonErr(400, 400, "无效ID")
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.nbJsonErr(400, 400, "参数错误")
return
}
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
Pinned *bool `json:"pinned"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
c.nbJsonErr(400, 400, "参数错误")
return
}
var note models.BackendNotebook
err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
c.nbJsonErr(404, 404, "笔记不存在")
return
}
updateFields := map[string]interface{}{
"update_time": time.Now(),
}
if payload.Title != "" {
updateFields["title"] = strings.TrimSpace(payload.Title)
}
updateFields["content"] = payload.Content
if payload.Pinned != nil {
if *payload.Pinned {
updateFields["pinned"] = int8(1)
} else {
updateFields["pinned"] = int8(0)
}
}
_, err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Update(updateFields)
if err != nil {
c.nbJsonErr(500, 500, "更新失败")
return
}
now := time.Now()
createdAt := note.CreateTime.Format("2006-01-02 15:04:05")
c.nbOk(map[string]interface{}{
"id": note.ID,
"title": updateFields["title"],
"content": payload.Content,
"pinned": payload.Pinned != nil && *payload.Pinned,
"created_at": createdAt,
"updated_at": now.Format("2006-01-02 15:04:05"),
})
}
// Delete DELETE /app/notebook/:id
func (c *AppNotebookController) Delete() {
claims, err := c.appNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.nbJsonErr(400, 400, "无效ID")
return
}
var note models.BackendNotebook
err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
c.nbJsonErr(404, 404, "笔记不存在")
return
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Update(map[string]interface{}{
"is_deleted": 1,
"delete_time": now,
})
if err != nil {
c.nbJsonErr(500, 500, "删除失败")
return
}
middleware.WriteDeleteLog(uint64(claims.UserID), claims.TenantId, "notebook", note.Title)
c.nbOk(nil)
}
// TogglePin POST /app/notebook/:id/togglePin
func (c *AppNotebookController) TogglePin() {
claims, err := c.appNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.nbJsonErr(400, 400, "无效ID")
return
}
var note models.BackendNotebook
err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
c.nbJsonErr(404, 404, "笔记不存在")
return
}
var newPinned int8
if note.Pinned == 1 {
newPinned = 0
} else {
newPinned = 1
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Update(map[string]interface{}{
"pinned": newPinned,
"update_time": now,
})
if err != nil {
c.nbJsonErr(500, 500, "操作失败")
return
}
c.nbOk(map[string]interface{}{
"pinned": newPinned == 1,
})
}
+586
View File
@@ -0,0 +1,586 @@
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,
})
}
+274
View File
@@ -0,0 +1,274 @@
package controllers
import (
"encoding/json"
"io"
"server/models"
"server/pkg/jwtutil"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
type BackendNotebookController struct {
beego.Controller
}
func (c *BackendNotebookController) backendNotebookClaims() (*jwtutil.Claims, error) {
auth := c.Ctx.Request.Header.Get("Authorization")
if auth == "" {
return nil, orm.ErrNoRows
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, orm.ErrNoRows
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
return nil, err
}
if claims.UserType != "backend" {
return nil, orm.ErrNoRows
}
return claims, nil
}
func (c *BackendNotebookController) nbJsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func (c *BackendNotebookController) nbOk(data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
// List GET /backend/notebook/list
func (c *BackendNotebookController) List() {
claims, err := c.backendNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
keyword := strings.TrimSpace(c.GetString("keyword"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
qs := models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID)
if keyword != "" {
qs = qs.Filter("title__icontains", keyword)
}
total, _ := qs.Count()
var list []models.BackendNotebook
_, err = qs.OrderBy("-update_time", "-create_time").
Limit(pageSize).Offset((page - 1) * pageSize).
All(&list)
if err != nil && err != orm.ErrNoRows {
c.nbJsonErr(500, 500, "查询失败")
return
}
if list == nil {
list = []models.BackendNotebook{}
}
c.nbOk(map[string]interface{}{"list": list, "total": total})
}
// Detail GET /backend/notebook/detail/:id
func (c *BackendNotebookController) Detail() {
claims, err := c.backendNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.nbJsonErr(400, 400, "无效ID")
return
}
var note models.BackendNotebook
err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
c.nbJsonErr(404, 404, "笔记不存在")
return
}
c.nbOk(note)
}
// Create POST /backend/notebook/create
func (c *BackendNotebookController) Create() {
claims, err := c.backendNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.nbJsonErr(400, 400, "参数错误")
return
}
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
c.nbJsonErr(400, 400, "参数错误")
return
}
payload.Title = strings.TrimSpace(payload.Title)
if payload.Title == "" {
payload.Title = "无标题"
}
userID := uint64(claims.UserID)
note := &models.BackendNotebook{
Tid: claims.TenantId,
Title: payload.Title,
Content: payload.Content,
UserID: &userID,
UserName: &claims.Username,
IsDeleted: 0,
}
id, err := models.Orm.Insert(note)
if err != nil {
c.nbJsonErr(500, 500, "创建失败")
return
}
note.ID = uint64(id)
c.nbOk(note)
}
// Update POST /backend/notebook/update/:id
func (c *BackendNotebookController) Update() {
claims, err := c.backendNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.nbJsonErr(400, 400, "无效ID")
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.nbJsonErr(400, 400, "参数错误")
return
}
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
c.nbJsonErr(400, 400, "参数错误")
return
}
payload.Title = strings.TrimSpace(payload.Title)
if payload.Title == "" {
payload.Title = "无标题"
}
var note models.BackendNotebook
err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
c.nbJsonErr(404, 404, "笔记不存在")
return
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Update(map[string]interface{}{
"title": payload.Title,
"content": payload.Content,
"update_time": now,
})
if err != nil {
c.nbJsonErr(500, 500, "更新失败")
return
}
note.Title = payload.Title
note.Content = payload.Content
note.UpdateTime = &now
c.nbOk(note)
}
// Delete DELETE /backend/notebook/delete/:id
func (c *BackendNotebookController) Delete() {
claims, err := c.backendNotebookClaims()
if err != nil {
c.nbJsonErr(401, 401, "未登录或无权限")
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.nbJsonErr(400, 400, "无效ID")
return
}
var note models.BackendNotebook
err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
c.nbJsonErr(404, 404, "笔记不存在")
return
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.BackendNotebook)).
Filter("id", id).
Update(map[string]interface{}{
"is_deleted": 1,
"delete_time": now,
})
if err != nil {
c.nbJsonErr(500, 500, "删除失败")
return
}
c.nbOk(nil)
}
+724
View File
@@ -0,0 +1,724 @@
package controllers
import (
"context"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
type BackendReminderController struct {
beego.Controller
}
func (c *BackendReminderController) backendReminderClaims() (*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" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
func (c *BackendReminderController) brJsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func (c *BackendReminderController) brOk(data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
func backendGenerateToken() 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 backendReminderFormPayload struct {
Title string `json:"title"`
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"`
ReceiverUserID uint64 `json:"receiver_user_id"`
ReceiverTargets map[string]string `json:"receiver_targets"`
}
// GetReminderList GET /backend/reminder/list
func (c *BackendReminderController) GetReminderList() {
claims, err := c.backendReminderClaims()
if err != nil {
c.brJsonErr(401, 401, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
keyword := strings.TrimSpace(c.GetString("keyword"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
qs := models.Orm.QueryTable(new(models.BackendSchedule)).
Filter("tid", claims.TenantId)
if keyword != "" {
qs = qs.Filter("content__contains", keyword)
}
total, _ := qs.Count()
var schedules []models.BackendSchedule
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&schedules)
if err != nil {
c.brJsonErr(500, 500, "查询失败: "+err.Error())
return
}
list := make([]map[string]interface{}, 0, len(schedules))
for _, s := range schedules {
var reminders []models.BackendScheduleReminder
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
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
}
}
}
item := map[string]interface{}{
"id": s.ID,
"title": s.Title,
"content": s.Content,
"schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"),
"remind_channels": channels,
"user_id": s.UserID,
"is_finished": isFinished,
}
if len(reminders) > 0 {
first := reminders[0]
item["advance_minutes"] = first.AdvanceMinutes
item["repeat_interval_minutes"] = first.RepeatIntervalMinutes
item["max_send_count"] = first.MaxSendCount
item["receiver_user_id"] = first.ReceiverUserID
}
list = append(list, item)
}
c.brOk(map[string]interface{}{
"list": list,
"total": total,
"page": page,
"pageSize": pageSize,
})
}
// GetReminderDetail GET /backend/reminder/:id
func (c *BackendReminderController) GetReminderDetail() {
claims, err := c.backendReminderClaims()
if err != nil {
c.brJsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
c.brJsonErr(400, 400, "无效的ID")
return
}
var schedule models.BackendSchedule
err = models.Orm.QueryTable(new(models.BackendSchedule)).
Filter("id", id).
Filter("tid", claims.TenantId).
One(&schedule)
if err != nil {
c.brJsonErr(404, 404, "日程未找到")
return
}
var reminders []models.BackendScheduleReminder
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
Filter("schedule_id", schedule.ID).
Filter("is_deleted", 0).
All(&reminders)
channels := make([]string, 0, len(reminders))
targets := make(map[string]string)
var first models.BackendScheduleReminder
for _, r := range reminders {
channels = append(channels, r.RemindChannel)
if r.ReceiverTarget != nil {
targets[r.RemindChannel] = *r.ReceiverTarget
}
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,
"title": schedule.Title,
"content": schedule.Content,
"schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"),
"remind_channels": channels,
"receiver_targets": targets,
"is_finished": isFinished,
}
if first.ID > 0 {
data["advance_minutes"] = first.AdvanceMinutes
data["repeat_interval_minutes"] = first.RepeatIntervalMinutes
data["max_send_count"] = first.MaxSendCount
data["receiver_user_id"] = first.ReceiverUserID
}
c.brOk(data)
}
// CreateReminder POST /backend/reminder
func (c *BackendReminderController) CreateReminder() {
claims, err := c.backendReminderClaims()
if err != nil {
c.brJsonErr(401, 401, err.Error())
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.brJsonErr(400, 400, "参数错误")
return
}
var p backendReminderFormPayload
if err := json.Unmarshal(raw, &p); err != nil {
c.brJsonErr(400, 400, "参数错误")
return
}
if strings.TrimSpace(p.ScheduleTime) == "" {
c.brJsonErr(400, 400, "日程发生时间不能为空")
return
}
schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local)
if err != nil {
c.brJsonErr(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.BackendSchedule{
Tid: claims.TenantId,
Title: title,
Content: content,
ScheduleTime: schedTime,
UserID: uint64(claims.UserID),
}
schedID, err := models.Orm.Insert(&schedule)
if err != nil {
c.brJsonErr(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" {
continue
}
targetVal := p.ReceiverTargets[ch]
var target *string
if targetVal != "" {
target = &targetVal
}
firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute)
reminder := models.BackendScheduleReminder{
Tid: claims.TenantId,
ScheduleID: uint64(schedID),
RemindChannel: ch,
AdvanceMinutes: p.AdvanceMinutes,
NextRemindTime: firstSendTime,
ReceiverUserID: uint64(claims.UserID),
ReceiverTarget: target,
RemindStatus: 0,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if ch == "EMAIL" || ch == "BARK" {
token := backendGenerateToken()
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.brJsonErr(500, 500, "创建提醒失败: "+err.Error())
return
}
}
c.brOk(map[string]interface{}{"schedule_id": schedID})
}
// UpdateReminder PUT /backend/reminder/:id
func (c *BackendReminderController) UpdateReminder() {
claims, err := c.backendReminderClaims()
if err != nil {
c.brJsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
c.brJsonErr(400, 400, "无效的ID")
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.brJsonErr(400, 400, "参数错误")
return
}
var p backendReminderFormPayload
if err := json.Unmarshal(raw, &p); err != nil {
c.brJsonErr(400, 400, "参数错误")
return
}
schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local)
if err != nil {
c.brJsonErr(400, 400, "日程时间格式不合法")
return
}
var schedule models.BackendSchedule
err = models.Orm.QueryTable(new(models.BackendSchedule)).
Filter("id", id).
Filter("tid", claims.TenantId).
One(&schedule)
if err != nil {
c.brJsonErr(404, 404, "日程未找到")
return
}
var reminders []models.BackendScheduleReminder
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
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.brJsonErr(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.brJsonErr(500, 500, "更新失败")
return
}
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
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" {
continue
}
targetVal := p.ReceiverTargets[ch]
var target *string
if targetVal != "" {
target = &targetVal
}
firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute)
reminder := models.BackendScheduleReminder{
Tid: claims.TenantId,
ScheduleID: id,
RemindChannel: ch,
AdvanceMinutes: p.AdvanceMinutes,
NextRemindTime: firstSendTime,
ReceiverUserID: schedule.UserID,
ReceiverTarget: target,
RemindStatus: 0,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if ch == "EMAIL" || ch == "BARK" {
token := backendGenerateToken()
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.brJsonErr(500, 500, "重新创建提醒失败")
return
}
}
c.brOk(nil)
}
// DeleteReminder DELETE /backend/reminder/:id
func (c *BackendReminderController) DeleteReminder() {
claims, err := c.backendReminderClaims()
if err != nil {
c.brJsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
c.brJsonErr(400, 400, "无效的ID")
return
}
var schedule models.BackendSchedule
err = models.Orm.QueryTable(new(models.BackendSchedule)).
Filter("id", id).
Filter("tid", claims.TenantId).
One(&schedule)
if err != nil {
c.brJsonErr(404, 404, "日程未找到")
return
}
var reminders []models.BackendScheduleReminder
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
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.brJsonErr(400, 400, "该日程提醒已全部结束,无法删除")
return
}
_, err = models.Orm.QueryTable(new(models.BackendSchedule)).Filter("id", id).Delete()
if err == nil {
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
Filter("schedule_id", id).
Update(map[string]interface{}{
"IsDeleted": 1,
"UpdateTime": time.Now(),
})
}
c.brOk(nil)
}
type backendReminderBatchDeletePayload struct {
Ids []uint64 `json:"ids"`
}
// BatchDeleteReminder POST /backend/reminder/batchDelete
func (c *BackendReminderController) BatchDeleteReminder() {
claims, err := c.backendReminderClaims()
if err != nil {
c.brJsonErr(401, 401, err.Error())
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.brJsonErr(400, 400, "参数错误")
return
}
var p backendReminderBatchDeletePayload
if err := json.Unmarshal(raw, &p); err != nil {
c.brJsonErr(400, 400, "参数错误")
return
}
if len(p.Ids) == 0 {
c.brOk(nil)
return
}
for _, scheduleID := range p.Ids {
var reminders []models.BackendScheduleReminder
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
Filter("schedule_id", scheduleID).
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.brJsonErr(400, 400, fmt.Sprintf("日程ID %d 的提醒已全部结束,无法删除", scheduleID))
return
}
}
_, _ = models.Orm.QueryTable(new(models.BackendSchedule)).
Filter("id__in", p.Ids).
Filter("tid", claims.TenantId).
Delete()
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
Filter("schedule_id__in", p.Ids).
Update(map[string]interface{}{
"IsDeleted": 1,
"UpdateTime": time.Now(),
})
c.brOk(nil)
}
// FinishReminder POST /backend/reminder/finish/:id
func (c *BackendReminderController) FinishReminder() {
claims, err := c.backendReminderClaims()
if err != nil {
c.brJsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
c.brJsonErr(400, 400, "无效的ID")
return
}
var schedule models.BackendSchedule
err = models.Orm.QueryTable(new(models.BackendSchedule)).
Filter("id", id).
Filter("tid", claims.TenantId).
One(&schedule)
if err != nil {
c.brJsonErr(404, 404, "日程未找到")
return
}
var reminders []models.BackendScheduleReminder
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
Filter("schedule_id", id).
Filter("is_deleted", 0).
All(&reminders)
allFinished := len(reminders) > 0
for _, r := range reminders {
if r.RemindStatus != 2 {
allFinished = false
break
}
}
var newStatus int8
if allFinished {
newStatus = 0
} else {
newStatus = 2
}
now := time.Now()
for _, r := range reminders {
r.RemindStatus = newStatus
r.UpdateTime = now
models.Orm.Update(&r, "RemindStatus", "UpdateTime")
}
c.brOk(map[string]interface{}{"is_finished": newStatus == 2})
}
type backendReminderTestPayload struct {
Title string `json:"title"`
Content string `json:"content"`
RemindChannels []string `json:"remind_channels"`
}
// TestReminder POST /backend/reminder/test
func (c *BackendReminderController) TestReminder() {
claims, err := c.backendReminderClaims()
if err != nil {
c.brJsonErr(401, 401, err.Error())
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.brJsonErr(400, 400, "参数错误")
return
}
var p backendReminderTestPayload
if err := json.Unmarshal(raw, &p); err != nil {
c.brJsonErr(400, 400, "参数错误")
return
}
if strings.TrimSpace(p.Title) == "" {
p.Title = "测试提醒"
}
if strings.TrimSpace(p.Content) == "" {
p.Content = "这是一条验证日程提醒配置的测试通知。"
}
senders := map[string]services.ReminderSender{
"SMS": &services.SMSSender{},
"EMAIL": &services.EmailSender{},
"BARK": &services.BarkSender{},
"SITE_MSG": &services.SiteMsgSender{},
}
type TestResult struct {
Channel string `json:"channel"`
Success bool `json:"success"`
Msg string `json:"msg"`
}
results := make([]TestResult, 0)
for _, ch := range p.RemindChannels {
ch = strings.ToUpper(strings.TrimSpace(ch))
sender, ok := senders[ch]
if !ok {
results = append(results, TestResult{Channel: ch, Success: false, Msg: "不支持的提醒渠道"})
continue
}
dummyToken := "test-token-for-verification"
reminder := &models.PlatformScheduleReminder{
RemindChannel: ch,
ReceiverUserID: uint64(claims.UserID),
AckToken: &dummyToken,
}
success, sendErr := sender.Send(context.Background(), reminder, "[测试]"+p.Title, p.Content)
msg := "发送成功"
if !success {
msg = "发送失败"
if sendErr != nil {
msg = sendErr.Error()
}
}
results = append(results, TestResult{Channel: ch, Success: success, Msg: msg})
}
c.brOk(results)
}