Commit Graph

183 Commits

Author SHA1 Message Date
wangwangit
b4fd96c63d docs: 重写 README,补齐上手路径与高频 FAQ
面向使用者补充:5 分钟上手、首次必配、通知小时/精确日语义、
克隆与备份、通知排障步骤,降低社区常见误解。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:47:53 +08:00
wangwangit
fe6055051c fix: 为克隆/历史按钮补充主题 CSS 渐变样式
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:43:17 +08:00
wangwangit
fac63c498c fix: 克隆/历史按钮改用主题 btn 样式,修复无背景色
Tailwind 的 bg-teal/bg-purple 在页面中未稳定生效;与编辑/测试等一致使用 theme-resources 渐变按钮类。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:42:46 +08:00
wangwangit
4ac3cc80da fix: 操作列按钮可换行,确保克隆按钮可见
桌面端 table-layout fixed + overflow hidden 易裁切操作按钮;
统一 flex-wrap 并放宽操作列最小宽度。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:40:19 +08:00
wangwangit
d400fe732d fix: 理清通知小时预览文案,加载配置后刷新状态
- 去掉难懂的 TZ 字样,改用「北京时间」等说明
- 预览与调度器一致:仅当当前整点在允许列表才显示可发送
- loadConfig 写入 08 后再刷新,避免误显示「空=全天」
- 任务历史跳过原因改为通俗中文

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:16:57 +08:00
wangwangit
d127d8dea6 fix: 修复列表页 unit 重复声明导致脚本中断布局异常
getReminderSettings 中 var unit 与 let unit 同函数作用域冲突,
触发 Identifier has already been declared,整页 JS 停摆。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:28:12 +08:00
wangwangit
fa161dd8a3 fix: 落实审计剩余 P0/P1,清理无用文件并补回归测试
- 调度:日规则按本地日去重;成功后才写 dedupe;after_expiry 持久化 lastFire
- autoRenew 统一时区日历加法(含 endOfMonth/农历)
- 备份:订阅字段校验、空覆盖拒绝、先数据后配置
- 订阅 PUT 写入 reminderRules;POST/PUT 非法 JSON 400;缺资源 404
- 前端去掉 fetch 劫持,表单显式提交规则;登录/配置默认密码提示
- ApiClient 401 跳转登录;删除 theme-resources.js 与死 updateConfig
- 测试:日去重/失败重试/lastFire、PUT rules、GET 404

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 17:33:42 +08:00
wangwangit
460e3e5728 docs: 新增全面审计报告 FULL_AUDIT_REPORT
涵盖代码审计、P0-P2 风险清单、测试补全说明、产品评估与可清理文件建议。
(docs/ 已被 gitignore,报告放在仓库根目录)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:56:49 +08:00
wangwangit
12cdc351b5 fix: 加固 JWT 过期校验、删除清理规则、第三方 notify 与 XSS
- verifyJWT 校验 exp,过期 cookie 失效
- 删除订阅时清理 reminder_rules
- /api/notify/* 在 JWT 门禁前放行(独立 token)
- 列表备注/分类 escapeHtml 防 XSS
- 补全 auth/CRUD/config/notify/e2e/currency/time/scheduler 测试
- 新增 docs/FULL_AUDIT_REPORT.md 全面审计报告

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:55:54 +08:00
wangwangit
c439e6b7a7 feat: 新增 ntfy 通知渠道
支持公共/自建 ntfy 服务器,配置 Topic 与可选 Token,并接入测试发送与渠道注册表(#191)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:33:40 +08:00
wangwangit
2ec421cd9e feat: 克隆订阅、状态筛选、停用局部刷新、季/半年与月末选项
- 列表支持克隆(预填表单并复制提醒规则)与状态筛选
- 停用/启用改为本地缓存更新,避免整表重载跳动
- 周期快捷:季度/半年/一年;公历可选「每月最后一天」
- 表单补充循环 vs 到期重置说明(#181 #180 #179 #178 #176)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:32:06 +08:00
wangwangit
336b54fcd9 feat: Telegram 支持可选 Topic ID(message_thread_id)
配置项 TG_TOPIC_ID 写入 Forum 群组话题,发送与测试通知一并携带(#190)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:29:18 +08:00
wangwangit
f0194ae687 feat: 支持配置与订阅的备份导出/导入恢复
新增 GET /api/backup 与 POST /api/restore,配置页可导出脱敏或含密钥 JSON,并按合并/覆盖模式恢复订阅与提醒规则(#187)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:18:53 +08:00
wangwangit
90bfeed0e8 docs: 澄清提醒规则为精确日触发,避免误解为窗口内每天通知
表单说明与 README 写明「到期前 N 天」仅在剩余天数等于 N 时触发一次,连发需多条规则(#182)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:17:32 +08:00
wangwangit
1a8fee9bf7 fix: 通知农历日期按用户时区计算,修复与列表不一致
定时通知与手动测试通知改为 getTimezoneDateParts 后再转农历,避免 Date 本地/UTC 分量导致农历差一天(#178)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:16:43 +08:00
wangwangit
b32119a5be fix: 列表提醒显示与多规则同步,修复始终显示提前7天
订阅列表改为展示 reminder_rules 摘要,规则变更时同步 legacy 字段,避免列表与真实提醒策略脱节(#183 / #178)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:16:19 +08:00
wangwangit
d4eee049e9 fix: 精确匹配 Wrangler KV 命名空间 2026-05-28 23:28:44 +08:00
wangwangit
e73a1501b6 fix: KV 命名空间查找兼容 Wrangler v3 前缀
Wrangler v3 会自动在 KV 命名空间名称前加项目名前缀,
导致 setup-kv.cjs 精确匹配失败。

修改为同时匹配精确名称和带项目名前缀的命名空间。

Fixes #173
2026-05-28 22:31:48 +08:00
wangwangit
e62b118262 docs: 优化 README,移除一键部署按钮,补充 GitHub Actions 部署说明 2026-05-27 21:05:18 +08:00
wangwangit
122ddf2b2d fix(ci): 移除 deploy 脚本中无效的 --env 参数 2026-05-27 20:59:32 +08:00
wangwangit
7c6d57fdf3 fix(ci): 修复 setup-kv.js ESM 兼容问题,重命名为 .cjs 2026-05-27 20:58:01 +08:00
一只会飞的旺旺
3504c285ed Merge pull request #172 from wangwangit/refactor/v3-product-grade
Refactor/v3 product grade
2026-05-27 20:40:58 +08:00
wangwangit
ac07e1079e fix(ci): 提交 package-lock.json 修复 CI 缓存报错 2026-05-27 20:39:37 +08:00
wangwangit
7c92119d45 chore: 排除 docs/ 目录 2026-05-27 20:36:41 +08:00
wangwangit
df5ee5b241 fix: 修复编辑订阅不生成支付记录及测试通知不写入历史的问题
- updateSubscription: 当订阅有金额但缺少 initial 支付记录时自动补建
- testSingleSubscriptionNotification: 传递 env/subId 使通知日志正常写入
- .gitignore: 排除 AI 工具目录和环境变量文件
2026-05-27 20:35:18 +08:00
wangwangit
549dabdcf1 fix(#171,#166): 分类下拉框从 API 加载 + 前端日期时区修复
- #171: initCustomDropdown 支持输入过滤,打开模态框时从
  GET /api/categories 加载已保存分类并与默认选项合并
- #166: 前端日期处理改用时区感知函数(createLocalCalendarDate、
  getTodayDateStringInTimezone),修复凌晨创建订阅日期偏移
2026-05-27 10:39:14 +08:00
wangwangit
77ff034be5 fix(#166): 订阅创建/更新/续订使用用户配置时区解析日期
- time.js: 新增 parseDateInputInTimezone、addCalendarPeriodInTimezone 等
  时区感知日期工具函数
- subscriptions.js: createSubscription/updateSubscription/manualRenewSubscription
  使用 config.TIMEZONE 解析前端传入的日期字符串,修复北京时间凌晨
  0-8 点创建订阅日期偏移一天的问题
2026-05-27 10:38:06 +08:00
wangwangit
1cb8c9ed00 feat(ui): 管理页底部显示版本号
- 从 GET /api/version 获取版本号
- 显示在页面底部
- Issue #168
2026-05-26 23:41:31 +08:00
wangwangit
5c1a02b9fd feat(ui): 分类下拉框支持从 API 加载 + 输入过滤
- 打开模态框时从 GET /api/categories 加载已保存分类
- 合并硬编码默认分类(去重,保存的排前面)
- 输入时实时过滤下拉选项
- Issue #171 前端部分
2026-05-26 23:40:29 +08:00
wangwangit
377561fa73 docs: 新增功能测试文档
- 完整 API 测试用例
- 时区功能验证方法
- 前端手动测试清单
- 回归测试检查项
2026-05-26 23:34:04 +08:00
wangwangit
99557f853e test: 新增 6 个测试用例覆盖新功能
- Bark Basic Auth 测试
- 分类管理 API 测试
- 版本号端点测试
- 下次提醒时间 API 测试
- 全部 177 个测试通过
2026-05-26 23:25:42 +08:00
wangwangit
a25c817c8f feat(api): 新增版本号端点 + 分类管理 + 下次提醒时间 API
- GET /api/version 返回当前版本号 (3.0.0)
- GET/POST /api/categories 管理分类列表
- GET /api/subscriptions/:id/next-reminder 返回下次提醒时间
- Issue #168 #170 #171
2026-05-26 23:25:34 +08:00
wangwangit
83c40d7e43 feat(reminder): 新增 getNextFireTime 计算规则下次触发时间
- 支持 before_expiry / on_expiry / after_expiry 三种规则类型
- 纯函数设计,便于单元测试
- Issue #170
2026-05-26 23:25:22 +08:00
wangwangit
31c44f7325 fix(bark): 修复自定义 Bark 服务器 URL 认证失败
- 从 URL 提取 Basic Auth 凭证并添加 Authorization header
- 清理 URL 中的凭证信息后再发请求
- 支持 URL 编码的特殊字符(如 p%40ss)
- Issue #169
2026-05-26 23:25:12 +08:00
wangwangit
823bcc6f00 fix(timezone): 修复创建/更新/续订订阅时忽略用户时区的 bug
- 改用 getNowInTimezone(config.TIMEZONE) 替代 getCurrentTimeInTimezone('UTC')
- 用 getTimezoneMidnightTimestamp 做跨日判断
- 修复 #166: 北京用户凌晨创建订阅默认日期变前一天
- 创建/更新订阅时自动保存分类到分类列表
2026-05-26 23:25:07 +08:00
wangwangit
d9833eca3c feat(categories): 新增分类标签持久化存储模块
- KV 存储分类列表(去重 + 排序)
- 新增 getCategories / addCategory 函数
- Issue #171
2026-05-26 23:24:57 +08:00
wangwangit
6e1b506c8a polish: 默认时区 Asia/Shanghai + 清理代码与文档中的版本号字眼
时区默认值:
- DEFAULT_CONFIG.TIMEZONE: 'UTC' → 'Asia/Shanghai'
- 配置页时区下拉用 <optgroup> 把中国标准时间钉到"🇨🇳 推荐"分组
- 未知时区 fallback 也改 Asia/Shanghai

去除版本号字眼(45 文件):
- 全部 "维护人:v3 重构 (2026-05)" 文件头注释删除
- 文件描述里的 "(v3 重写)" "(v3 新增)" 等去掉
- @deprecated v2 兼容 → @deprecated 旧版兼容函数
- 注释里的 "v2/v3" 替换为中性表述("早期版本""旧调度器""既有客户端"等)
- src/api/handlers/v3-routes.js → src/api/handlers/extras.js
- tests/api/v3-routes.test.js → tests/api/extras-routes.test.js
- 函数名 handleV3Routes → handleExtraRoutes
- README / MIGRATION / ARCHITECTURE 整体重写:
  - README 去掉 "v3 关键改进里程碑" 段,改写为标准产品功能介绍
  - MIGRATION 重写为通用"升级指南",用"旧版本"代替 v2
  - ARCHITECTURE 模块图与流程描述去掉所有 v3 标签
- package.json description 去掉 v3
- wrangler.toml / wrangler.dev.toml 注释里 v3 去掉

故意保留的(持久化的 KV 数据兼容性标识):
- schema_version 字符串值 'v3'
- migrate:subscriptions_v3 / reminder_rules_v3 / scheduler_logs_v3 step ID
- subscriptions_v2_backup KV key
- 文档对这些标识有显式说明:"是 KV 内部数据兼容性标记,与产品版本号无关"

171 测试全绿;lint 干净;wrangler dry-run 571 KiB / gzip 119 KiB。
2026-05-24 20:04:16 +08:00
wangwangit
bbcad369af fix(ui): 通知历史页布局与其他后台页面对齐
我之前的版本用了一个简化的 nav,跟 admin/config/dashboard 三个页面完全不
匹配——没 logo 没汉堡菜单没实时时钟,body 也没用统一的 `bg-gray-100
min-h-screen` 容器,导致用户从订阅列表点"通知历史"就跳到一个长得不像
同一个产品的页面。

重写 src/views/notifyLogsPage.html:
- 复用与 admin/config/dashboard 完全相同的导航结构:
  - 左侧:fa-calendar-check 图标 + "订阅管理系统" 标题 + 实时时钟
  - 右侧:5 个导航链接(仪表盘/订阅列表/通知历史/系统配置/退出)
  - 移动端:汉堡按钮 + 同一份折叠菜单
  - "通知历史" 链接 active 高亮 (indigo-600 + border-b-2)
- 复用 `bg-gray-100 min-h-screen` body 与 `max-w-7xl mx-auto px-4` 主容器
- 复用 stat-card / btn-primary 风格的统一组件类
- 复用 ${themeResources} 注入暗色主题
- 增加:移动端菜单 JS / 实时时钟 JS / formatTimezoneDisplay
       (与其他页面一致的实现)
- 筛选条改为下拉选择订阅(subId 选项从 /api/subscriptions 动态填充,
   不再让用户手敲 ID)

171 测试全绿;wrangler dry-run 570 KiB。

dev 环境实测确认:/admin/notify-logs 与 /admin/* 其它三页 nav 标记数
(logo×1 hamburger×4 clock×3)完全一致。
2026-05-24 19:24:25 +08:00
wangwangit
a1fe2dcbf4 fix(scheduler): NOTIFICATION_HOURS 通配符 "*" 被 padStart 误处理为 "0*"
本地 wrangler dev 测试时发现:用户配置 "*" 后,inWindow 始终判定 false,
反而比 v2 不能用。

根因:v3 scheduler 对配置数组无脑 padStart(2,'0'),把 '*' 变成 '0*'。

修复 src/services/scheduler.js:normalize 时仅对纯数字 padStart;
'*' / 'ALL' 保持原样。

测试 tests/services/scheduler.test.js:新增 "场景5:通配符不应被 padStart"
确保回归。

171 测试全绿。

发现自我们 @ 2026-05-24 19:11 在本地 dev 环境实测中。
2026-05-24 19:13:06 +08:00
wangwangit
d3066dfc5e docs+ci: README/MIGRATION/ARCHITECTURE 文档 + GitHub Actions(Task 13)
文档:
- README.md 整体重写(v3 关键改进、部署、升级、FAQ)
- docs/MIGRATION.md v2 → v3 详细迁移指南,含回滚 3 种方案与升级检查清单
- docs/ARCHITECTURE.md 完整架构图、目录结构、KV Key 布局、
  调度器流程、提醒规则模型、9 渠道适配器、关键测试索引、
  常见维护场景指南

CI:
- .github/workflows/test.yml:push/PR 触发 lint + 170 单测
- .github/workflows/deploy.yml:main 分支自动部署,需配置 secrets
  CLOUDFLARE_API_TOKEN

package.json version: 3.0.0-alpha.1 → 3.0.0

170 测试全绿;wrangler dry-run 560 KiB / gzip 118 KiB。

Refs Task 13 of refactor/v3-product-grade plan.
2026-05-24 18:34:42 +08:00
wangwangit
868263a1c7 fix: 货币显示一致性 + 仪表盘读新调度日志(Task 12)
修复 #160:v2 在 4 个文件里重复定义 currencySymbols;JPY 与 CNY 同
符号 ¥ 易混淆。
- 新建 src/core/currency-format.js:CURRENCY_SYMBOLS 单一来源 +
  formatAmount(amount, currency) helper,JPY 改前缀为 'JP¥' 区分
- src/services/notify/reminder.js 与 src/api/handlers/subscriptions.js
  改用 formatAmount,删除两份重复定义

仪表盘 (#149):
- src/api/handlers/dashboard.js 重写,scheduler_status / status_history
  改从 scheduler-logs.repo.getRecent(10) 取
- 兼容老前端字段(lastRunAt / shouldNotifyThisHour 等)做扁平转换
- 时区从 config.TIMEZONE 读取(不再硬编码 UTC)
- 响应增加顶层 timezone 字段

170 测试全绿;wrangler dry-run 560 KiB / gzip 118 KiB。

Refs Task 12 of refactor/v3-product-grade plan.
2026-05-24 18:31:05 +08:00
wangwangit
8518045408 feat(ui): 通知历史页 + 时区感知配置页(Task 11)
新增 src/views/notifyLogsPage.html:
- /admin/notify-logs 路由(admin.js 登记)
- 顶部筛选:subId / channel / status / 最近 N 小时
- 概览卡片:总记录 / 成功 / 失败 / 最近发送
- 调度执行预览(最近 10 次 sched_log,可折叠)
- 主表格:时间(用户 TZ)/ 订阅名 / 规则 ID / 渠道 / 状态徽章 / 详情展开
- 失败行红底,成功行绿底;详情含 raw 响应与通知正文展开

src/views/configPage.html:
- 通知时段标签从"(UTC)"改为"(按你的时区)",动态显示具体 TZ 名
- 改原灰色提示为黄色"v3 时区语义变更"提示,明确升级行为
- 新增实时预览 div:显示当前用户 TZ 的小时/分钟、是否在通知时段
- TZ select / hours input 的 change/input 事件即时刷新,30s 兜底重算

src/views/{adminPage,dashboardPage,configPage}.html:
顶部导航与移动菜单加 "通知历史" 链接(fa-history 图标)

src/views/pages.js + src/api/admin.js:
注册 notifyLogsPage 模板 + 路由 /admin/notify-logs

170 测试全绿;wrangler dry-run 559 KiB / gzip 117 KiB。

Refs Task 11 of refactor/v3-product-grade plan.
2026-05-24 18:28:48 +08:00
wangwangit
3397d34c15 feat(ui): 订阅表单加入多提醒规则编辑器(Task 10)
adminPage.html:
- 替换原"提醒提前量"单行输入为"提醒规则"区块
  - 顶部按钮:[应用预设(7/3/1/0)] [添加规则]
  - 每行:启用复选框、类型(到期前/到期当天/到期后)、数值、单位(天/小时)
  - after_expiry 类型展开:每 N 小时重复,直到 [续费/手动确认/不停止]
- 隐藏原 reminderUnit/reminderValue 作为 v2 兜底字段保留
- validateForm 不再强制 reminderValue(已隐藏)

JS 模块(位于 </script> 之前):
- ReminderRulesEditor (setRules / setPresets / getRules / clear)
- 新增订阅 → 自动渲染 4 条预设
- 编辑订阅 → 轮询监听 subscriptionId 字段变化,拉取 /api/.../reminders 渲染
- 全局 fetch 劫持:POST/PUT 订阅时自动附加 reminderRules 到 body
- 编辑模式 (PUT) 完成后调用 PUT /api/subscriptions/:id/reminders 同步替换规则

后端:
- v3-routes.js 新增 PUT /api/subscriptions/:id/reminders 整体替换接口

170 条测试全绿;wrangler dry-run 540 KiB / gzip 113 KiB。

Refs Task 10 of refactor/v3-product-grade plan.
2026-05-24 18:24:17 +08:00
wangwangit
8b9e157456 feat(assets): 启用 Workers Assets 托管 public/ + ApiClient 公共库
策略说明:
- 不强行拆分 v2 的 3000+1500 行内联 HTML(独立清理 sprint)
- 仅建 public/ 给 v3 新增的客户端 JS / 独立页面用
- compatibility_date 升至 2024-09-23(assets binding 所需)

新增:
- wrangler.toml 加 [assets] directory=./public binding=ASSETS
- public/README.md 说明用途
- public/js/lib/api-client.js 统一 fetch + JSON 错误处理

后续 Task 10/11 的新 UI 通过 <script src="/js/..."> 引入,避免污染既有 HTML。

170 条测试全绿;wrangler dry-run 526 KiB / gzip 109 KiB。

Refs Task 9 of refactor/v3-product-grade plan.
2026-05-24 18:21:04 +08:00
wangwangit
b6ab5ed944 feat(api): 提醒规则 / 通知日志 / 调度日志新 API + 创建订阅联动
src/api/handlers/v3-routes.js(新):
  GET    /api/subscriptions/:id/reminders            列表
  POST   /api/subscriptions/:id/reminders            新增(body 传 preset:true 一键应用预设)
  PUT    /api/subscriptions/:id/reminders/:ruleId    更新
  DELETE /api/subscriptions/:id/reminders/:ruleId    删除

  GET    /api/notification-logs?subId=&channel=&status=&since=&until=&limit=
  GET    /api/scheduler-logs?limit=N

挂载点:在 router.js 鉴权之后、subscriptions handler 之前匹配。
按 v2 错误结构返回 { success, message };405/404/400 状态码与 v2 一致。

POST /api/subscriptions 扩展:
- 不传 reminderRules → 自动应用 4 条智能预设(7/3/1/0 天)
- 传入数组 → 按数组替换(每条经 normalizeRule 校验)
- 写入失败不影响订阅本体创建,错误打 console.error

测试 tests/api/v3-routes.test.js 12 条覆盖:
- 4 类 reminder CRUD(含 preset=true / 不存在 → 404)
- notification-logs 过滤 subId / status
- scheduler-logs 列表
- 创建订阅自动写预设 vs 自定义规则
- 鉴权失败路径

总计 170 条测试全绿;lint 干净;wrangler dry-run 484 KiB。

Refs Task 8 of refactor/v3-product-grade plan.
2026-05-24 18:16:22 +08:00
wangwangit
a10c212989 refactor(api): 引入 Hono 应用作为路由外壳
src/app.js 新建 Hono 应用:
- 全局中间件:ensureMigrations(首次访问透明迁移)、onError 兜底
- 路由:GET /、GET /admin、ALL /admin/*、ALL /api/*、/debug、兜底
- path/method/response shape 与 v2 严格 1:1 兼容(前端零修改)

src/index.js 简化为 { fetch: app.fetch, scheduled }

策略说明:
- Hono 现阶段当"路由外壳"用,请求转发给现有 handler
- 后续 Task 可逐 handler 改成 Hono 原生写法(c.json/c.req.json 等)
- 这样既拿到中间件 / 错误兜底好处,又不破坏既有行为

vitest.config.js 增加 assetsInclude: ['**/*.html']
让 vite 在测试中能解析 src/views/*.html 文本 import(与生产 wrangler text loader 行为一致)

测试 tests/api/routes-compat.test.js 8 条覆盖:
- GET / 未登录 → 登录页 HTML
- GET /admin 未登录 → 重定向
- GET /api/subscriptions 未登录 → 401 标准错误体
- POST /api/login 错误 body / 正确凭据
- GET /debug 未登录 → 401
- GET /api/未知 → 404 标准错误体
- 兜底路径 → 登录页

总计 158 条测试全绿;wrangler dry-run 476 KiB / gzip 101 KiB。

Refs Task 7 of refactor/v3-product-grade plan.
2026-05-24 18:14:07 +08:00
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