feat: 新增定时账单导入提醒与通知直达导入页

- 新增通知配置、渠道、发送日志三张表(MySQL/SQLite 双建表脚本)及实体、Mapper、Service、Controller
- 支持邮箱(SMTP)与 Server 酱两种推送渠道,抽象 NotificationSender 工厂统一发送
- 新增 NotificationScheduler 定时任务,按每天/每周/每月周期检查并推送账单导入提醒
- 新增全局通知链接地址配置(/api/app/notification-url),随提醒一并下发
- 前端系统设置页新增「定时通知」面板,支持周期配置、渠道增删改测、立即发送与发送日志
- 新增 /bill-import 直达页,供提醒链接一键打开账单上传弹窗
- 登录支持 redirect 回跳,仅允许站内路径以防开放重定向
This commit is contained in:
2026-09-18 16:47:09 +08:00
parent c1f5d02564
commit e49b6b7b81
45 changed files with 3469 additions and 513 deletions
+40
View File
@@ -96,3 +96,43 @@ CREATE TABLE IF NOT EXISTS t_ai_usage (
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_created (user_id, created_at) COMMENT '用户ID+创建时间联合索引'
);
-- 通知配置表:存储每个用户的账单导入提醒设置
CREATE TABLE IF NOT EXISTS t_notification_config (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID,主键自增',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用定时提醒:1-启用,0-关闭',
frequency VARCHAR(10) NOT NULL DEFAULT 'MONTHLY' COMMENT '提醒周期:DAILY-每天,WEEKLY-每周,MONTHLY-每月',
day_of_month INT NOT NULL DEFAULT 1 COMMENT '每月提醒日(1-28),frequency=MONTHLY 时生效',
day_of_week INT NOT NULL DEFAULT 1 COMMENT '每周提醒日(1-7,1为周一),frequency=WEEKLY 时生效',
send_time VARCHAR(5) NOT NULL DEFAULT '09:00' COMMENT '提醒时间(HH:mm)',
last_sent_at DATETIME COMMENT '最近一次发送时间,用于避免重复提醒',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE INDEX uk_notification_config_user_id (user_id) COMMENT '用户ID唯一索引,每个用户一条配置'
);
-- 通知渠道表:存储用户配置的邮箱、Server 酱等推送渠道
CREATE TABLE IF NOT EXISTS t_notification_channel (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '渠道ID,主键自增',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
type VARCHAR(20) NOT NULL COMMENT '渠道类型:EMAIL-邮箱,SERVERCHAN-Server 酱',
name VARCHAR(50) COMMENT '渠道名称,便于用户识别',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用:1-启用,0-关闭',
config TEXT COMMENT '渠道配置(JSON),如 webhook 地址、SMTP 账号等',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_notification_channel_user_id (user_id) COMMENT '用户ID索引'
);
-- 通知发送日志表:记录每次提醒的发送结果,便于排查问题
CREATE TABLE IF NOT EXISTS t_notification_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '日志ID,主键自增',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
channel_id BIGINT COMMENT '渠道ID,测试或渠道已删除时可能为空',
channel_type VARCHAR(20) COMMENT '渠道类型',
success TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否成功:1-成功,0-失败',
message VARCHAR(500) COMMENT '结果描述或失败原因',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_notification_log_user_created (user_id, created_at) COMMENT '用户ID+创建时间联合索引'
);
+53
View File
@@ -123,3 +123,56 @@ WHEN NEW.updated_at = OLD.updated_at
BEGIN
UPDATE t_ai_config SET updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime') WHERE id = OLD.id;
END;;
CREATE TABLE IF NOT EXISTS t_notification_config (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
enabled INTEGER NOT NULL DEFAULT 0,
frequency VARCHAR(10) NOT NULL DEFAULT 'MONTHLY',
day_of_month INTEGER NOT NULL DEFAULT 1,
day_of_week INTEGER NOT NULL DEFAULT 1,
send_time VARCHAR(5) NOT NULL DEFAULT '09:00',
last_sent_at DATETIME,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
);;
CREATE UNIQUE INDEX IF NOT EXISTS uk_notification_config_user_id ON t_notification_config (user_id);;
CREATE TABLE IF NOT EXISTS t_notification_channel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
type VARCHAR(20) NOT NULL,
name VARCHAR(50),
enabled INTEGER NOT NULL DEFAULT 1,
config TEXT,
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime')),
updated_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
);;
CREATE INDEX IF NOT EXISTS idx_notification_channel_user_id ON t_notification_channel (user_id);;
CREATE TABLE IF NOT EXISTS t_notification_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
channel_id INTEGER,
channel_type VARCHAR(20),
success INTEGER NOT NULL DEFAULT 1,
message VARCHAR(500),
created_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime'))
);;
CREATE INDEX IF NOT EXISTS idx_notification_log_user_created ON t_notification_log (user_id, created_at);;
CREATE TRIGGER IF NOT EXISTS trg_notification_config_updated_at
AFTER UPDATE ON t_notification_config
FOR EACH ROW
WHEN NEW.updated_at = OLD.updated_at
BEGIN
UPDATE t_notification_config SET updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime') WHERE id = OLD.id;
END;;
CREATE TRIGGER IF NOT EXISTS trg_notification_channel_updated_at
AFTER UPDATE ON t_notification_channel
FOR EACH ROW
WHEN NEW.updated_at = OLD.updated_at
BEGIN
UPDATE t_notification_channel SET updated_at = strftime('%Y-%m-%dT%H:%M:%S', 'now', 'localtime') WHERE id = OLD.id;
END;;