Commit Graph

28 Commits

Author SHA1 Message Date
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