wangwangit
|
0bb7e7670d
|
refactor(scheduler): v3 重写 - TZ 感知 + 多规则 + 结构化日志(修复 #91/#52/#166)
src/services/notify/reminder-engine.js(新):
- 纯函数 shouldFire(rule, ctx)
- 三种 rule type 显式分支(before_expiry / on_expiry / after_expiry)
- 24 条表驱动单测覆盖各种边界
src/services/scheduler.js(重写):
1. 时区基准统一:通过 getNowInTimezone(config.TIMEZONE) 取用户 TZ 下
hourString,与 NOTIFICATION_HOURS 比对(修复 #91 / #52 根因——
v2 把 UTC 小时当作"用户本地小时")
2. 多提醒规则:从 reminders.repo 加载每订阅的规则数组,逐条调
reminder-engine.shouldFire;老订阅没有规则时现场用
legacyFieldToRule 转一条
3. 去重粒度:dedup key = notify_dedupe:{subId}:{ruleId}:{ymdh-local}
不再让一条订阅的多规则相互打架
4. 结构化日志:
- 每次执行写 sched_log:{iso}(含命中/去重/发送/续订统计 + 候选明细)
- 每条通知发送(成功/失败)写 notify_log(dispatch.js 自动落库)
5. 用户日历"剩余天数"基于 getDaysBetween(now, expiry, tz),
修复 #166 跨日界场景
测试 tests/services/scheduler.test.js 7 条覆盖:
- 场景 1:UTC 0点 + 北京 8 点 + 配置 [08] → 发送
- 场景 2:同上配 [00] → 跳过 inWindow=false
- 场景 3:4 条规则订阅(7/3/1/0),距 3 天 → 仅命中 value=3
- 场景 4:同 sub+rule+ymdh 重复调用 → 去重 dedupedCount=1
- 自动续订:cycle 模式补齐 + auto 支付记录
- 写 sched_log
- 成功发送时写 notify_log
总计 150 条测试全绿;wrangler dry-run 415 KiB / gzip 86 KiB。
Refs Task 6 of refactor/v3-product-grade plan.
|
2026-05-24 18:11:47 +08:00 |
|
wangwangit
|
8ca9b07ae1
|
refactor(notify): 9 渠道统一适配器接口 + 修复 #81
新增 src/services/notify/channel.js:定义 Channel 接口
{ name, validateConfig, send, test }
+ ok/fail/escapeMarkdownV2/stripMarkdown/errorMessage 工具函数
每个渠道(telegram/notifyx/webhook/wechat/email/bark/gotify/serverchan/pushplus)
重写为:
- 暴露统一的 xxxChannel 对象(4 个方法)
- 保留 sendXxxNotification(...) 函数作为 v2 兼容 wrapper
修复 #81:Telegram MarkdownV2 转义现在覆盖 \(\)~`\>#+-=|{}.! 等 13 个字符;
失败时仍然降级纯文本兜底。
新增 src/services/notify/dispatch.js:
- dispatch(payload, config, options) Promise.allSettled 并发分发
- 部分渠道失败不影响整体,每条渠道结果独立返回
- 带 env+subId 时同步把成功/失败结果写 notify_log
- testChannel(name, config) 给配置页"测试发送"按钮用
services/notify/index.js 改为 dispatch 的薄壳,保留旧签名。
测试 tests/services/notify/channels.test.js 共 34 条:
- escapeMarkdownV2 14 字符表驱动
- Telegram 含下划线名 / 降级兜底 / API 拒绝 / 网络异常
- 8 个其它渠道各 happy + 配置缺失
- Bark 自定义 URL 与标准 URL 分支
- Webhook 模板替换 + 模板格式错误退回默认
- dispatch 部分失败 + 写日志
- testChannel 未知/已知渠道
- 9 渠道注册表完整性
总计 119 条测试全绿;lint 干净;wrangler dry-run 413 KiB。
Refs Task 5 of refactor/v3-product-grade plan.
|
2026-05-24 18:08:14 +08:00 |
|
wangwangit
|
b9de410f6f
|
feat(observability): 三类可观测性仓库 + 迁移扩展
新增三个 KV 仓库(v3 通知/调度可观测性数据底座):
1. src/data/reminders.repo.js
- reminder_rules:{subId} 数组,支持 CRUD + 智能预设(4 条:到期前 7/3/1/当天)
- legacyFieldToRule 把 v2 的 reminderUnit/reminderValue 单点提醒
转成 1 条等价 before_expiry 规则
- normalizeRule 修复非法字段,repeatInterval 仅 after_expiry 类型保留
2. src/data/notification-logs.repo.js
- notify_log:{ymdh}:{subId}:{ruleId}:{channel}:{rand}, TTL 30 天
- writeLog / query(subId/channel/status/since/until/limit 过滤)
- 时间倒序,KV.list 拉全后排序
3. src/data/scheduler-logs.repo.js
- sched_log:{isoUtc}, TTL 30 天
- writeLog / getRecent(按时间倒序)
迁移扩展:MIGRATION_STEPS 追加两个 step
- reminder_rules_v3:为现有订阅生成默认提醒规则
- scheduler_logs_v3:v2 的 scheduler_status_history 转换到新表
调试页新增链接:/debug?export=sched_logs&limit=50 直接下载 JSON
测试:
- tests/data/reminders.test.js (13)
- tests/data/notification-logs.test.js (8)
- tests/data/scheduler-logs.test.js (4)
- 共 85 条测试全绿;lint 干净
Refs Task 4 of refactor/v3-product-grade plan.
|
2026-05-24 18:03:01 +08:00 |
|
wangwangit
|
dac8e7dc1d
|
refactor(data): 订阅仓库改为 KV 多 Key + 自动迁移
把 v2 的单 Key subscriptions JSON 数组改造为:
sub_index = JSON 数组 [id1, id2, ...]
sub:{id} = 单订阅完整数据
schema_version = 'v3'
migrate:{step} = 'done' 标记
migration_lock = 60s TTL 锁
subscriptions_v2_backup = 旧数据 7 天 TTL 备份
新增:
- src/data/subscriptions.repo.js:低层 KV 仓库(listIds/listAll/getById/save/saveMany/deleteById/replaceAll)
- src/data/migrate.js:迁移编排器,可累加 step;带内存缓存避免重复检查;
幂等 + 锁保护
- 在 src/index.js 入口(fetch + scheduled)开头调用 ensureMigrations
- src/data/subscriptions.js 改造为调用新 repo,单条 CRUD 不再触碰整数组
- src/services/scheduler.js 自动续订写入改用 subRepo.saveMany
测试:
- tests/data/migrate.test.js 16 条用例覆盖 repo CRUD、迁移幂等、锁、损坏 JSON 兜底
- 共 58 条测试全绿;wrangler dry-run 401 KiB
老用户升级:第一次 fetch / scheduled 触发后透明完成迁移;旧数据 7 天可回滚。
Refs Task 3 of refactor/v3-product-grade plan.
|
2026-05-24 17:59:06 +08:00 |
|
wangwangit
|
f6bce4b7b3
|
refactor(time): 重写时区核心模块为单一真相源
修复 #91 / #52 / #166 类时区相关问题的根因:旧 getCurrentTimeInTimezone
只返回 new Date(),被调用方误以为是"用户本地时间对象"。
变化点:
- 新增 getNowInTimezone(tz, now?) 返回 {utc, parts, hourString, isoLocal}
强制业务代码显式选择需要 UTC 时刻还是用户 TZ 字段
- 新增 getTimezoneHourString(date, tz) 调度器通知时段判断专用
- 新增 getDaysBetween(from, to, tz) 基于用户 TZ 各自零点的整天数差,
修复"凌晨 0–8 点创建订阅默认日期变前一天"的 #166
- formatLocalDate 替代 formatTimeInTimezone(保留旧名作 alias)
- 旧 API(getCurrentTimeInTimezone / convertUTCToTimezone)保留为兼容
wrapper,加显眼注释说明其语义陷阱
- 全文 JSDoc 标注 + 中文用途说明,启用 // @ts-check
- 40 条单测覆盖:UTC/北京/纽约 DST、跨日界、#166 边界、非法时区兜底
附带:/debug 页新增"时区诊断"区块,直观展示 UTC vs 用户 TZ 当前小时、
通知时段是否命中——这是用户自助排查"为什么没收到通知"的入口
Refs Task 2 of refactor/v3-product-grade plan.
|
2026-05-24 17:53:29 +08:00 |
|
wangwangit
|
e2fc130f99
|
fix: XSS 风险修复
- showToast 改用 textContent 设置消息内容,防止 HTML 注入
- createHoverText 对所有用户输入进行 escapeHtml 转义
- 续订/支付历史/编辑支付模态框中的 subscription.name 和
payment.note 使用 escapeHtml 转义
- debug 页面 adminUsername 转义
- 添加全局 escapeHtml 工具函数
|
2026-05-19 12:06:21 +08:00 |
|
wangwangit
|
b76e047050
|
feat: 搜索筛选体验优化
- 搜索框添加一键清空按钮(X 图标,有内容时显示)
- 筛选结果为空时显示'清除筛选条件'重置链接
- 重置链接一键清空搜索关键词和所有筛选器
|
2026-05-19 12:03:49 +08:00 |
|
wangwangit
|
4f91427881
|
feat: Dashboard 刷新优化
- 添加手动刷新按钮(带 loading 状态)
- visibilitychange 暂停/恢复自动轮询,节省后台资源
- 页面重新可见时立即刷新数据
- 加载失败时显示重试按钮
|
2026-05-19 12:02:56 +08:00 |
|
wangwangit
|
7454d78be0
|
feat: 模态框交互优化 - ESC关闭、背景锁定滚动、动画
- ESC 键关闭所有模态框(主模态框 + 动态模态框)
- 所有动态模态框(续订、支付历史、编辑支付)打开时锁定背景滚动
- 关闭时恢复背景滚动
- 主模态框添加淡入/缩放动画
|
2026-05-19 12:01:48 +08:00 |
|
wangwangit
|
edc7be9e90
|
feat: 操作反馈优化 - 按钮 loading 状态、停用确认
- 编辑按钮点击后显示 spinner,加载完成/失败后恢复
- 停用订阅前添加确认提示,避免误操作
|
2026-05-19 11:59:08 +08:00 |
|
wangwangit
|
d36f8b94fe
|
feat: 登录暴力破解防护 + Cookie Secure 标志
- 基于 IP 的登录失败计数(KV TTL 自动过期)
- 连续 5 次失败后锁定 5 分钟
- 失败时提示剩余尝试次数
- 登录成功后清除失败计数
- 添加 request.json() 的 try-catch 防止解析错误
- Cookie 添加 Secure 标志(login + logout)
|
2026-05-19 11:57:53 +08:00 |
|
wangwangit
|
bfa4d52772
|
feat: 配置页保存体验优化
- 添加悬浮保存栏:表单有变更时底部显示保存按钮
- 添加 beforeunload 提示:有未保存更改时离开页面会提示
- 加载失败时禁用整个表单,防止误提交覆盖已有配置
- 保存成功后自动隐藏悬浮栏
- 支持 dark mode
|
2026-05-19 11:57:13 +08:00 |
|
wangwangit
|
384f3aaab2
|
feat: Toast 优化 - 错误延长显示、可关闭、堆叠限制
- 错误类 toast 延长到 6 秒,警告 4.5 秒,成功 3 秒
- 添加关闭按钮,用户可手动 dismiss
- 最多同时显示 5 个 toast,超出自动移除最早的
- toast-container 改为 flex 布局支持正确堆叠
- 同步更新 adminPage 和 configPage
|
2026-05-19 11:55:43 +08:00 |
|
wangwangit
|
008df6e5c5
|
feat: 统一 apiFetch wrapper,401 自动跳转登录页
- adminPage/configPage/dashboardPage 添加 apiFetch 封装
- 所有 API 调用替换为 apiFetch,401 时提示并跳转登录页
- theme-resources.js 中兼容性检测 apiFetch 可用性
解决认证过期后用户看到'未知错误'而非跳转登录的问题
|
2026-05-19 11:54:08 +08:00 |
|
wangwangit
|
61af3b332f
|
fix(#169): 兼容 Bark 自定义服务器 URL 格式
支持 bark-worker 等自定义服务器的完整 URL 格式
(如 https://user:pass@server/deviceKey/)。
当 BARK_SERVER 路径不为空时,直接 POST 到该 URL;
否则走标准 Bark API (serverUrl + /push + device_key)。
|
2026-05-19 11:19:34 +08:00 |
|
wangwangit
|
e1112bb114
|
fix(#166): 修复订阅列表到期时间显示的时区一致性
- diffMs/diffHours 改为基于用户时区计算,而非直接用 UTC 时间戳差值
- 状态判断增加 diffMs < 0 条件,到期日当天过午夜也显示'已过期'
原因:expiryDate 存储为 UTC 午夜,直接用 getTime() 差值会导致
UTC+8 用户在到期日当天凌晨看到'约7小时后到期'而非'已过期'
|
2026-05-19 11:18:40 +08:00 |
|
wangwangit
|
55ff9cd067
|
fix: 修复 deletePaymentRecord 中排序比较变量引用错误
sortedByPeriodEnd 排序中 dateB 错误引用了 a.periodEnd,
应为 b.periodEnd。此 bug 导致删除支付记录后到期日期回退不正确。
|
2026-05-19 11:16:25 +08:00 |
|
wangwangit
|
d5452336b6
|
fix(#166): 调度器使用用户配置时区计算到期天数和通知时段
- todayMidnight 改为基于 config.TIMEZONE 计算
- currentHour 改为使用 getTimezoneDateParts 获取时区对应小时
- NOTIFICATION_HOURS 现在按用户配置时区解释
原因:之前硬编码 UTC,导致 UTC+8 用户设置'到期当天提醒'时,
在北京时间 0:00(UTC 16:00)触发时 daysDiff 仍为 1 而非 0
|
2026-05-19 11:15:42 +08:00 |
|
wangwangit
|
06d952b492
|
fix(#166): 修复前端默认日期使用UTC而非本地时间的问题
- 添加订阅时默认开始日期改用本地日期
- 续订表单默认付款日期改用本地日期
- 编辑支付记录时日期显示改用本地日期
- 自动计算到期日期改用本地日期格式化
原因:toISOString() 返回 UTC 时间,UTC+8 用户在 0:00-8:00
之间会得到前一天的日期
|
2026-05-19 11:14:03 +08:00 |
|
wangwangit001
|
a2e0d1c28d
|
fix: redirect authenticated users from home
|
2026-04-24 11:31:05 +08:00 |
|
wangwangit001
|
5214f76c55
|
fix: preserve zero amount and payment currency
|
2026-04-24 11:14:32 +08:00 |
|
wangwangit001
|
cc97cc97c9
|
feat: add serverchan and pushplus notifiers
|
2026-04-23 12:04:44 +08:00 |
|
wangwangit
|
805a47e417
|
fix(security/ui): do not expose secrets; show configured flags + explicit clear
|
2026-03-05 19:40:47 +08:00 |
|
wangwangit
|
4f4fc5af1f
|
feat(notify): add gotify notifier + config UI
|
2026-03-05 15:29:53 +08:00 |
|
wangwangit
|
a095213162
|
fix(security): mask config secrets, protect /debug, use crypto RNG
|
2026-03-05 15:05:34 +08:00 |
|
Squarelan
|
0c92c1d99a
|
修复adminPage.html时区文案生成逻辑
|
2026-03-02 21:01:59 +08:00 |
|
wangwangit
|
a1638dedc0
|
fix: harden telegram notifications with markdown escaping and plaintext fallback
|
2026-02-27 18:59:46 +08:00 |
|
wangwangit
|
bd5b5227c4
|
release: v2 modular refactor and docs cleanup
|
2026-02-27 15:29:43 +08:00 |
|