课序

项目说明书 · 无需先看懂源码

课序开发文件结构与功能报告

顺着“浏览器 → 主 Worker → 数据库 → 提醒 Worker”理解项目。需要修改功能时,再查对应文件,不必从第一行开始阅读全部代码。

2 个 Worker18 个 API 路由9 张 D1 表12 个规则测试

第一部分

总体架构:每一层只做自己擅长的事

1

浏览器 PWA

显示页面、压缩图片、让用户校对、保存游客数据、导出 ICS 和申请通知权限。

frontend/ + public/
2

Python 主 Worker

提供 API、检查登录、调用 DeepSeek/Resend、校验课表并读写 D1。

src/
3

Cloudflare D1

保存用户确认后的校历、课程、发生时间、提醒设置和推送订阅。

migrations/

DeepSeek

只从图片提出课程候选,不负责计算真实日期。

服务适配层

隔离第三方格式,未来换模型时尽量不动业务代码。

src/services.py

Resend

只负责发送邮箱验证码。

最重要的边界:AI 只读图。单双周展开、冲突检查、实际日期和 ICS 都由代码计算;只有用户确认后才写入数据库。

第二部分

目录结构:先区分源码和生成文件

Learning-in-BJTU-assistant/
├── frontend/                 浏览器交互的 TypeScript 源码
│   ├── app.ts                页面全部交互逻辑
│   └── rules.ts              浏览器侧周次规则
├── public/                   浏览器直接访问的静态文件
│   ├── index.html            应用首页结构
│   ├── styles.css            页面视觉样式
│   ├── sw.js                 离线缓存和通知展示
│   ├── manifest.webmanifest  PWA 安装配置
│   └── app.js                自动生成,不要手改
├── src/                      Python 主 Worker
│   ├── main.py               API 总入口
│   ├── models.py             数据格式与字段校验
│   ├── services.py           DeepSeek 和 Resend 适配器
│   ├── rules.py              日期、单双周、冲突、ICS
│   ├── db.py                 D1 数据访问封装
│   └── security.py           验证码、哈希和随机 ID
├── migrations/              D1 建表脚本
├── reminder-worker/         独立提醒 Worker
├── tests/                   自动化规则测试
├── scripts/                 运维辅助脚本
├── wrangler.jsonc           主 Worker 部署配置
├── package.json             构建与测试命令
├── pyproject.toml           Python 依赖
└── README.md                启动和部署说明

前端与静态文件

frontend/app.ts

页面的大脑:保存用户、学期和课程状态;处理图片;调用 API;渲染课表;完成登录、导出和 Push 订阅。

frontend/rules.ts

把“1-16周(单)”等文字变成明确周次数组,让格式错误在浏览器里立刻出现。

public/index.html

页面骨架。按钮、输入框、课表区和登录弹窗在这里,但按钮点击后的行为不在这里。

public/styles.css

控制颜色、间距、手机适配、课程卡片、对话框和本报告的排版。

public/sw.js

负责离线缓存;收到 Push 后显示提醒;点击通知后打开网站。

public/app.js

自动生成。浏览器实际加载它,但开发时修改 app.ts,再运行 npm run build。

Python 后端

src/main.py

后端总入口:FastAPI 路由、中间件、Cookie 鉴权、课表保存事务、数据导出和静态页面返回。

src/models.py

数据合同:规定星期、节次、周次、置信度等字段允许什么值,非法数据先被拒绝。

src/services.py

外部服务隔离层:调用 DeepSeek/Resend、清理模型 JSON、转换错误,并提供开发模拟识别。

src/rules.py

正确性核心:后端周次规则、冲突检测、上海时区日期实例化和 ICS 生成。

src/db.py

统一 D1 的 first、all_rows、run、batch,并处理 Cloudflare JS/Python 类型转换。

src/security.py

生成验证码和随机 ID,计算 HMAC 哈希,并执行常量时间比较。

提醒、配置与测试

migrations/0001_initial.sql

创建九张 D1 表。上线后不要重写 0001,新改动应新增 0002、0003。

reminder-worker/src/index.ts

每分钟寻找需提醒课程、生成 VAPID 签名、发送 Push、记录结果并清理失效订阅。

wrangler.jsonc

声明主 Python Worker、静态资源、D1 和公开环境变量。

reminder-worker/wrangler.jsonc

声明每分钟 Cron,并绑定与主 Worker 相同的 D1。

tests/test_rules.py

验证后端单双周、冲突、上海时区和 ICS。

tests/frontend.test.mjs

验证浏览器侧周次规则,保证前后端理解一致。

第三部分

四条核心功能流程

A. 图片识别

  1. 浏览器读取、旋转、裁边并压缩图片。
  2. app.ts 把图片和学期发送到 POST /api/analyze。
  3. services.py 调用 DeepSeek,要求只输出 JSON。
  4. models.py 校验星期、节次、周次和置信度。
  5. 前端显示可编辑结果,等待人工确认。

B. 邮箱登录

  1. 申请验证码时按邮箱和 IP 限流。
  2. D1 只保存验证码 HMAC 哈希。
  3. 生产由 Resend 发信,开发环境直接显示验证码。
  4. 验证成功后创建 30 天会话。
  5. 浏览器持有 HttpOnly Cookie,D1 保存 Token 哈希。

C. 保存课表

  1. 前端把周次文字展开成数组。
  2. 后端再次校验,并拒绝真实时间冲突。
  3. 已有课表时先返回新增、删除和不变数量。
  4. 用户确认后用 D1 batch 整批写入。
  5. 每个周次生成 UTC occurrence,供提醒和 ICS 使用。

D. 浏览器提醒

  1. 浏览器申请权限并创建 Push Subscription。
  2. Cron 每分钟唤醒 Reminder Worker。
  3. SQL 找到即将开始课程,用唯一键防重复。
  4. Worker 使用 VAPID 私钥发送空负载 Push。
  5. sw.js 本地显示通知;ICS 作为可靠降级。

第四部分

D1 数据库保存了什么

作用核心数据关键约束
users账号主体邮箱、创建/删除状态连接会话、学期和课程
otp_challenges一次性验证码哈希、IP、尝试次数、过期时间10 分钟、最多 5 次
sessions登录会话Token 哈希、过期时间不保存明文 Token
terms校历版本首周周一、周数、时区、作息课程日期计算的根
courses确认后的课程规则星期、节次、周次、地点不保存原始图片
course_occurrences实际课程实例UTC 开始/结束、第几周提醒和 ICS 使用
reminders提醒设置提前分钟数、是否启用每门课程一条
push_subscriptions设备订阅endpoint、公钥、auth失效后自动停用
reminder_deliveries投递账本计划时间、状态、错误唯一约束防重复

公开配置与私密 Secret

可写进 wrangler.jsonc

  • APP_ORIGIN:正式网站地址
  • APP_NAME:产品名称
  • DEEPSEEK_MODEL:模型名
  • DEV_MODE:开发模式开关
  • RESEND_FROM:发件人
  • VAPID_PUBLIC_KEY:Push 公钥

只能放 Cloudflare Secrets

  • DEEPSEEK_API_KEY
  • RESEND_API_KEY
  • OTP_SECRET
  • SESSION_SECRET
  • VAPID_JWK 私钥
  • VAPID_SUBJECT 联系邮箱

第五部分

想修改某项功能时,打开哪个文件

目标主要文件注意事项
改首页文案或模块顺序public/index.html按钮行为不在这里
改颜色、手机布局、弹窗public/styles.css同时测试手机宽度
改按钮行为或图片处理frontend/app.ts之后运行 npm run build
新增 APIsrc/main.py + models.py用户资源必须带 user_id
更换 AI 或邮件服务src/services.py保持业务层接口稳定
改单双周或日期frontend/rules.ts + src/rules.py前后端同时改并补测试
增加数据库字段migrations/0002_*.sql不要重写已执行的 0001
改提醒调度reminder-worker/src/index.ts保留幂等和订阅清理
改通知显示public/sw.js修改后升级 CACHE 名
改域名或 D1 绑定两份 wrangler.jsoncD1 ID 必须一致

不要直接编辑

node_modules/.venv/.venv-workers/python_modules/.wrangler/.tmp/ 是依赖、缓存或本地数据;public/app.js 是构建产物。

日常验证顺序

  1. npm run typecheck:类型检查。
  2. npm test:前端规则测试。
  3. python -m unittest discover -s tests:后端规则测试。
  4. npm run build:生成 app.js。
  5. npm run db:local:迁移本地 D1。
  6. npm run dev:启动完整本地网站。

第六部分

第一次接触时容易混淆的术语

Worker
Cloudflare 托管的后端程序,收到请求时运行,不需要 VPS。
PWA
可以安装到桌面或手机主屏幕的网站。
D1
Cloudflare 托管的 SQL 数据库,语法接近 SQLite。
FastAPI
定义 /api/... 路由的 Python Web 框架。
Pydantic
验证 API 数据格式和字段范围的工具。
Service Worker
浏览器后台脚本,负责缓存和 Push;不是 Cloudflare Worker。
Cron Trigger
Cloudflare 定时唤醒功能,本项目每分钟运行一次提醒检查。
VAPID
Web Push 身份签名;公钥给浏览器,私钥只放 Secret。
ICS
通用日历文件,是 Push 不可靠时的降级方案。
Secret
平台加密保存的私密变量,不应写进代码。

推荐阅读方式

先看“总体架构”和“四条功能流程”。需要修改功能时,回到“修改指南”查对应文件;不需要先学完 Python 或 TypeScript。