Refactor/v3 product grade#172
Merged
Merged
Conversation
added 30 commits
May 19, 2026 11:14
- 添加订阅时默认开始日期改用本地日期 - 续订表单默认付款日期改用本地日期 - 编辑支付记录时日期显示改用本地日期 - 自动计算到期日期改用本地日期格式化 原因:toISOString() 返回 UTC 时间,UTC+8 用户在 0:00-8:00 之间会得到前一天的日期
- todayMidnight 改为基于 config.TIMEZONE 计算 - currentHour 改为使用 getTimezoneDateParts 获取时区对应小时 - NOTIFICATION_HOURS 现在按用户配置时区解释 原因:之前硬编码 UTC,导致 UTC+8 用户设置'到期当天提醒'时, 在北京时间 0:00(UTC 16:00)触发时 daysDiff 仍为 1 而非 0
sortedByPeriodEnd 排序中 dateB 错误引用了 a.periodEnd, 应为 b.periodEnd。此 bug 导致删除支付记录后到期日期回退不正确。
- diffMs/diffHours 改为基于用户时区计算,而非直接用 UTC 时间戳差值 - 状态判断增加 diffMs < 0 条件,到期日当天过午夜也显示'已过期' 原因:expiryDate 存储为 UTC 午夜,直接用 getTime() 差值会导致 UTC+8 用户在到期日当天凌晨看到'约7小时后到期'而非'已过期'
支持 bark-worker 等自定义服务器的完整 URL 格式 (如 https://user:pass@server/deviceKey/)。 当 BARK_SERVER 路径不为空时,直接 POST 到该 URL; 否则走标准 Bark API (serverUrl + /push + device_key)。
- adminPage/configPage/dashboardPage 添加 apiFetch 封装 - 所有 API 调用替换为 apiFetch,401 时提示并跳转登录页 - theme-resources.js 中兼容性检测 apiFetch 可用性 解决认证过期后用户看到'未知错误'而非跳转登录的问题
- 错误类 toast 延长到 6 秒,警告 4.5 秒,成功 3 秒 - 添加关闭按钮,用户可手动 dismiss - 最多同时显示 5 个 toast,超出自动移除最早的 - toast-container 改为 flex 布局支持正确堆叠 - 同步更新 adminPage 和 configPage
- 添加悬浮保存栏:表单有变更时底部显示保存按钮 - 添加 beforeunload 提示:有未保存更改时离开页面会提示 - 加载失败时禁用整个表单,防止误提交覆盖已有配置 - 保存成功后自动隐藏悬浮栏 - 支持 dark mode
- 基于 IP 的登录失败计数(KV TTL 自动过期) - 连续 5 次失败后锁定 5 分钟 - 失败时提示剩余尝试次数 - 登录成功后清除失败计数 - 添加 request.json() 的 try-catch 防止解析错误 - Cookie 添加 Secure 标志(login + logout)
- 编辑按钮点击后显示 spinner,加载完成/失败后恢复 - 停用订阅前添加确认提示,避免误操作
- ESC 键关闭所有模态框(主模态框 + 动态模态框) - 所有动态模态框(续订、支付历史、编辑支付)打开时锁定背景滚动 - 关闭时恢复背景滚动 - 主模态框添加淡入/缩放动画
- 添加手动刷新按钮(带 loading 状态) - visibilitychange 暂停/恢复自动轮询,节省后台资源 - 页面重新可见时立即刷新数据 - 加载失败时显示重试按钮
- 搜索框添加一键清空按钮(X 图标,有内容时显示) - 筛选结果为空时显示'清除筛选条件'重置链接 - 重置链接一键清空搜索关键词和所有筛选器
- showToast 改用 textContent 设置消息内容,防止 HTML 注入 - createHoverText 对所有用户输入进行 escapeHtml 转义 - 续订/支付历史/编辑支付模态框中的 subscription.name 和 payment.note 使用 escapeHtml 转义 - debug 页面 adminUsername 转义 - 添加全局 escapeHtml 工具函数
- package.json 升 3.0.0-alpha.1,新增 hono、vitest、@cloudflare/workers-types、wrangler 依赖 - jsconfig.json 配置 TypeScript 检查 JS(checkJs=false 默认关闭,文件级 // @ts-check 按需开启) - vitest.config.js 用 @cloudflare/vitest-pool-workers 跑真实 workerd 运行时 - tests/smoke.test.js 验证测试基础设施 - .gitignore 屏蔽 node_modules、调研文档、CHANGELOG 草稿 - 现有运行行为零变化,wrangler dry-run 通过 (390 KiB / 79 gzip) Refs Task 1 of refactor/v3-product-grade plan.
修复 #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.
把 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.
新增三个 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.
新增 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.
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.
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.
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.
策略说明: - 不强行拆分 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.
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.
新增 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.
修复 #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.
文档: - 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.
本地 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 环境实测中。
我之前的版本用了一个简化的 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)完全一致。
时区默认值: - 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。
added 14 commits
May 26, 2026 23:24
- KV 存储分类列表(去重 + 排序) - 新增 getCategories / addCategory 函数 - Issue #171
- 改用 getNowInTimezone(config.TIMEZONE) 替代 getCurrentTimeInTimezone('UTC')
- 用 getTimezoneMidnightTimestamp 做跨日判断
- 修复 #166: 北京用户凌晨创建订阅默认日期变前一天
- 创建/更新订阅时自动保存分类到分类列表
- 从 URL 提取 Basic Auth 凭证并添加 Authorization header - 清理 URL 中的凭证信息后再发请求 - 支持 URL 编码的特殊字符(如 p%40ss) - Issue #169
- 支持 before_expiry / on_expiry / after_expiry 三种规则类型 - 纯函数设计,便于单元测试 - Issue #170
- Bark Basic Auth 测试 - 分类管理 API 测试 - 版本号端点测试 - 下次提醒时间 API 测试 - 全部 177 个测试通过
- 完整 API 测试用例 - 时区功能验证方法 - 前端手动测试清单 - 回归测试检查项
- 打开模态框时从 GET /api/categories 加载已保存分类 - 合并硬编码默认分类(去重,保存的排前面) - 输入时实时过滤下拉选项 - Issue #171 前端部分
- 从 GET /api/version 获取版本号 - 显示在页面底部 - Issue #168
- time.js: 新增 parseDateInputInTimezone、addCalendarPeriodInTimezone 等 时区感知日期工具函数 - subscriptions.js: createSubscription/updateSubscription/manualRenewSubscription 使用 config.TIMEZONE 解析前端传入的日期字符串,修复北京时间凌晨 0-8 点创建订阅日期偏移一天的问题
- updateSubscription: 当订阅有金额但缺少 initial 支付记录时自动补建 - testSingleSubscriptionNotification: 传递 env/subId 使通知日志正常写入 - .gitignore: 排除 AI 工具目录和环境变量文件
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.