This repository has been archived on 2026-08-12. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Cyrene/backend/gateway/internal/store/reminder_store.go
T
AskaEth 27a497e397 feat: 群聊提醒 — 提醒关联平台/频道/适配器,到期发回原群聊
- reminder_create 新增 platform/channel_type/channel_id/adapter_name 参数
- Gateway Reminder 模型 + DB 迁移 + CRUD 全部支持新字段
- 提醒到期时:群聊提醒 → platform-bridge 发回原群聊
- 非群聊提醒 → 走 Web 端推送
- Thinker 新增 PushPlatformMessage 方法

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-23 20:57:58 +08:00

207 lines
6.9 KiB
Go

package store
import (
"database/sql"
"fmt"
"git.yeij.top/AskaEth/Cyrene/pkg/logger"
"time"
)
// Reminder 提醒模型
type Reminder struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Title string `json:"title"`
Description string `json:"description"`
RemindAt time.Time `json:"remind_at"`
Status string `json:"status"` // pending, completed, cancelled
CreatedAt time.Time `json:"created_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
RepeatType string `json:"repeat_type"` // none, daily, weekly, monthly
SessionID string `json:"session_id"`
Platform string `json:"platform,omitempty"` // 来源平台
ChannelType string `json:"channel_type,omitempty"` // 频道类型
ChannelID string `json:"channel_id,omitempty"` // 频道ID
AdapterName string `json:"adapter_name,omitempty"` // 适配器名
Notified bool `json:"notified"`
}
// ReminderStore 提醒持久化存储
type ReminderStore struct {
db *sql.DB
}
// NewReminderStore 使用已有数据库连接初始化提醒存储并自动建表
func NewReminderStore(db *sql.DB) (*ReminderStore, error) {
store := &ReminderStore{db: db}
if err := store.migrate(); err != nil {
return nil, fmt.Errorf("提醒表迁移失败: %w", err)
}
logger.Println("[ReminderStore] 提醒持久化存储已初始化")
return store, nil
}
// migrate 自动创建提醒表结构
func (s *ReminderStore) migrate() error {
queries := []string{
`CREATE TABLE IF NOT EXISTS reminders (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
title VARCHAR(500) NOT NULL,
description TEXT DEFAULT '',
remind_at TIMESTAMPTZ NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ,
repeat_type VARCHAR(20) DEFAULT '',
session_id VARCHAR(36) DEFAULT '',
notified BOOLEAN DEFAULT FALSE
)`,
`CREATE INDEX IF NOT EXISTS idx_reminders_user_id ON reminders(user_id)`,
`CREATE INDEX IF NOT EXISTS idx_reminders_remind_at ON reminders(remind_at)`,
`CREATE INDEX IF NOT EXISTS idx_reminders_status ON reminders(status)`,
`CREATE INDEX IF NOT EXISTS idx_reminders_due ON reminders(remind_at, status, notified)`,
`ALTER TABLE reminders ADD COLUMN IF NOT EXISTS platform VARCHAR(32) DEFAULT ''`,
`ALTER TABLE reminders ADD COLUMN IF NOT EXISTS channel_type VARCHAR(16) DEFAULT ''`,
`ALTER TABLE reminders ADD COLUMN IF NOT EXISTS channel_id VARCHAR(64) DEFAULT ''`,
`ALTER TABLE reminders ADD COLUMN IF NOT EXISTS adapter_name VARCHAR(64) DEFAULT ''`,
}
for _, q := range queries {
if _, err := s.db.Exec(q); err != nil {
return fmt.Errorf("迁移SQL执行失败: %w\nSQL: %s", err, q)
}
}
return nil
}
// CreateReminder 创建新提醒
func (s *ReminderStore) CreateReminder(r *Reminder) error {
if r.CreatedAt.IsZero() {
r.CreatedAt = time.Now()
}
_, err := s.db.Exec(
`INSERT INTO reminders (id, user_id, title, description, remind_at, status, created_at, repeat_type, session_id, platform, channel_type, channel_id, adapter_name)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
r.ID, r.UserID, r.Title, r.Description, r.RemindAt, r.Status, r.CreatedAt, r.RepeatType, r.SessionID,
)
if err != nil {
return fmt.Errorf("创建提醒失败: %w", err)
}
return nil
}
// GetRemindersByUser 获取用户的提醒列表(可按状态筛选,按 remind_at 升序)
func (s *ReminderStore) GetRemindersByUser(userID, status string, limit, offset int) ([]Reminder, error) {
if limit <= 0 {
limit = 50
}
if offset < 0 {
offset = 0
}
var rows *sql.Rows
var err error
if status != "" {
rows, err = s.db.Query(
`SELECT id, user_id, title, description, remind_at, status, created_at, completed_at, repeat_type, session_id, notified, platform, channel_type, channel_id, adapter_name
FROM reminders WHERE user_id = $1 AND status = $2
ORDER BY remind_at ASC LIMIT $3 OFFSET $4`,
userID, status, limit, offset,
)
} else {
rows, err = s.db.Query(
`SELECT id, user_id, title, description, remind_at, status, created_at, completed_at, repeat_type, session_id, notified, platform, channel_type, channel_id, adapter_name
FROM reminders WHERE user_id = $1
ORDER BY remind_at ASC LIMIT $2 OFFSET $3`,
userID, limit, offset,
)
}
if err != nil {
return nil, fmt.Errorf("查询提醒列表失败: %w", err)
}
defer rows.Close()
var reminders []Reminder
for rows.Next() {
var r Reminder
if err := rows.Scan(&r.ID, &r.UserID, &r.Title, &r.Description, &r.RemindAt,
&r.Status, &r.CreatedAt, &r.CompletedAt, &r.RepeatType, &r.SessionID, &r.Notified, &r.Platform, &r.ChannelType, &r.ChannelID, &r.AdapterName); err != nil {
return nil, fmt.Errorf("扫描提醒行失败: %w", err)
}
reminders = append(reminders, r)
}
if reminders == nil {
reminders = []Reminder{}
}
return reminders, rows.Err()
}
// GetDueReminders 获取所有到期且未通知的提醒
func (s *ReminderStore) GetDueReminders() ([]Reminder, error) {
rows, err := s.db.Query(
`SELECT id, user_id, title, description, remind_at, status, created_at, completed_at, repeat_type, session_id, notified, platform, channel_type, channel_id, adapter_name
FROM reminders
WHERE remind_at <= NOW() AND status = 'pending' AND notified = FALSE
ORDER BY remind_at ASC`,
)
if err != nil {
return nil, fmt.Errorf("查询到期提醒失败: %w", err)
}
defer rows.Close()
var reminders []Reminder
for rows.Next() {
var r Reminder
if err := rows.Scan(&r.ID, &r.UserID, &r.Title, &r.Description, &r.RemindAt,
&r.Status, &r.CreatedAt, &r.CompletedAt, &r.RepeatType, &r.SessionID, &r.Notified, &r.Platform, &r.ChannelType, &r.ChannelID, &r.AdapterName); err != nil {
return nil, fmt.Errorf("扫描到期提醒行失败: %w", err)
}
reminders = append(reminders, r)
}
if reminders == nil {
reminders = []Reminder{}
}
return reminders, rows.Err()
}
// MarkNotified 标记提醒为已通知
func (s *ReminderStore) MarkNotified(id string) error {
_, err := s.db.Exec(
`UPDATE reminders SET notified = TRUE WHERE id = $1`,
id,
)
if err != nil {
return fmt.Errorf("标记提醒已通知失败: %w", err)
}
return nil
}
// UpdateReminder 更新提醒字段
func (s *ReminderStore) UpdateReminder(id string, r *Reminder) error {
_, err := s.db.Exec(
`UPDATE reminders SET title = $1, description = $2, remind_at = $3, status = $4,
completed_at = $5, repeat_type = $6, session_id = $7, notified = $8
WHERE id = $9`,
r.Title, r.Description, r.RemindAt, r.Status, r.CompletedAt, r.RepeatType, r.SessionID, r.Notified, id,
)
if err != nil {
return fmt.Errorf("更新提醒失败: %w", err)
}
return nil
}
// DeleteReminder 删除提醒
func (s *ReminderStore) DeleteReminder(id string) error {
_, err := s.db.Exec(`DELETE FROM reminders WHERE id = $1`, id)
if err != nil {
return fmt.Errorf("删除提醒失败: %w", err)
}
return nil
}