Files
tennis-book/AGENTS.md
2026-07-15 20:09:02 +08:00

58 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tennis Book 开发约定
## 产品边界
- 本项目是桌面端网球场聚合预约工具,不在渲染层直接调用第三方微信小程序接口。
- 每个球场必须通过独立适配器接入;公共层只定义球场、日期、场地和时段等稳定概念。
- 不在公共模型中硬塞某个球场的专属字段。专属请求参数、签名、Cookie 和流程状态留在对应适配器内。
## 目录结构
- `src/main/`Electron 主进程、第三方网络请求、凭证和适配器注册。
- `src/main/settings/`:按球场保存的本机配置与原子写入逻辑,存储位置使用 Electron `userData` 目录。
- `src/main/bookings/`:待支付锁场订单摘要与恢复逻辑;不得持久化支付签名。
- `src/main/monitors/`空场监控任务、10 秒轮询调度、整点立即检查、变化去重和系统通知;只允许调用无凭证列表接口。整点检查必须绕过常规轮询冷却,但不得与已有检查并发或在同一整点重复触发。
- 带报名活动(如双打活动)的时段统一视为已订场地;不得计入可订数量,也不得触发空场监控提醒。
- 空场监控日期可选;未指定日期的任务持续滚动检查主界面支持的未来 7 天数据,不能在跨日时自动失效。
- 自动预定是监控任务级、用户显式开启的一次性写操作。开启前必须明确告知“只锁场不支付”并校验球场访客凭证;成功后立即转为待支付且禁止再次下单。服务端明确失败只允许重试一次,第二次失败后自动熔断;结果不确定时禁止重试并立即熔断。
- 监控时间段表示“同一天、同一个物理场地完整连续覆盖整个区间”,不是区间内任意单个小时。任一小时缺失、不可订、属于报名活动或来自不同场地时均不命中;提醒、可订数量和自动预定统一以完整连续组合为单位。
- 连续组合自动预定必须作为一笔请求提交全部组成时段,下单前逐项重新校验可用性、场地身份、连续性和总价;不得只锁其中一个小时后宣称任务成功。
- 同一球场存在 `submitting-uncertain``pending-payment``release-uncertain` 记录时,所有监控任务只能继续读列表,不得发起新的下单请求。自动预定只能消费本轮新出现的可订时段,每个任务每轮最多选择一个时段。
- 每个监控任务的累计统计、近 30 天每日汇总和最近 240 条运行日志随任务持久化;删除任务时一并删除。日志禁止记录访客凭证、预约人信息、完整响应或完整请求头。
- 10 秒轮询遥测先更新主进程内存,每 5 分钟按所有脏任务批量写入;提醒、暂停、恢复、到期等关键节点立即持久化,正常退出必须等待最终批量刷新,禁止每任务每轮重写整个 JSON。
- `src/main/adapters/<court-id>/`:单个球场的接口与预约流程实现。
- `src/preload/`:仅暴露经过白名单审核的类型安全 IPC。
- `src/renderer/`React UI不可使用 Node.js API不可持有第三方密钥。
- `src/shared/`:主进程、预加载和渲染层共享的纯类型与消息契约。
- `docs/`:架构决策和球场接入说明。
## 命名与扩展规则
- 桌面应用对外名称统一为 `Tennis Book`;开发模式和打包产物都必须通过 Electron 运行时名称与 `productName` 保持一致。
- Electron `userData` 目录固定使用稳定存储 ID `tennis-book`,不得从显示名称推导;修改品牌名称不能导致配置、待支付订单或监控任务切换存储目录。
- 球场 ID 使用稳定的 `kebab-case`,适配器目录名必须与 ID 一致。
- IPC channel 使用 `domain:action`,新增 channel 时必须同时更新共享类型和 preload 白名单。
- 新球场先实现 `CourtAdapter`,再注册到适配器注册表;禁止在 React 组件里按球场 ID 写业务分支。
- 第三方接口字段 `userId` 在本项目语义中是“场地 ID”固定场地 ID 属于球场适配器私有常量,不得通过 IPC 或配置界面暴露。
- 场地 ID 只允许在对应适配器请求边界映射到第三方 `userId` 字段和 `STOREID` 请求头。
- 下单、取消等写操作必须由对应球场适配器独立实现;公共层只编排确认、状态和恢复,不拼装球场专属请求。
- 模拟数据只允许存在于 `demo` 适配器或明确命名的 fixture 中,接入真实 API 后不得静默回退到模拟成功。
## 安全规则
- Electron 必须保持 `contextIsolation: true``nodeIntegration: false``sandbox: true`
- 第三方令牌、Cookie、签名密钥不得进入渲染进程、日志或版本库。
- 第三方请求诊断日志只允许在 dev 模式输出;请求头和响应中的访客凭证、支付签名、预支付标识及个人信息必须递归脱敏。
- `PSPLVISITORID` 持久化在 Electron 主进程管理的本机配置中;设置弹窗可通过白名单 IPC 读取原值用于回填,但关闭弹窗后必须清除渲染层状态,其他业务接口不得返回原值。
- 可订列表等只读接口禁止读取或携带 `PSPLVISITORID`;只有下单、释放等明确需要访客身份的写接口可以从持久化配置加载并注入该请求头。
- 每球场配置以 `courtId` 隔离;标识符在共享模型和持久化文件中使用字符串,实际请求前再按接口契约安全转换。
- 预约人昵称和手机号仅可按产品明确要求存在于当次时段查询的内存结果中;不得写日志、落盘、缓存或用于预约以外的用途。
- 外部链接只能通过主进程白名单打开;页面不得任意导航或创建窗口。
- 写操作前必须在主进程重新验证实时可用性和价格;手动下单必须 UI 二次确认,自动预定必须在任务开关处获得一次明确授权。不得用真实接口做自动化下单或取消测试。
## 完成标准
- 每次修改至少通过 `npm run typecheck``npm run build``git diff --check`
- 影响交互或布局时必须做一次实际渲染检查。
- 接入真实球场时需要覆盖:正常数据、无可用时段、登录失效、限流、接口格式变化和网络失败。