refactor: React 前端重构,新旧平台合并为莞易学

This commit is contained in:
study-tool
2026-10-29 23:50:46 +08:00
parent 35853891c9
commit e8634a6317
62 changed files with 8923 additions and 4623 deletions
+130
View File
@@ -0,0 +1,130 @@
# 架构说明
## 目录
```
app.py 启动壳:把 src 加进 sys.path,调用 study_tool.app.main()
pyproject.toml 打包配置(src 布局,console script: study-tool)
src/study_tool/
__init__.py 包信息(版本)
__main__.py python -m study_tool 入口
app.py 组装:FastAPI、鉴权中间件、页面 / health、启动 main()
webapp.py FastAPI 实例(路由模块从这里注册,避免循环导入)
config.py 常量 / 环境变量 / 根目录定位(data、static、desenc.js)
runtime.py 运行状态 RUNS + 统一日志 log()
store.py SQLite:账号 / 设置 / 日志 / 状态 / 缓存 / 快照
models.py HTTP 请求模型(Pydantic)
settings_store.py 系统设置读写
llm.py 大模型调用(OpenAI 兼容)
quiz.py 测验解析 / 交卷 / AI 答题(含 /api/quiz/*)
engine.py 学习引擎:登录→发现→按平台分发→worker / 视频 / 提交
capture.py 抓包
platforms/ 平台插件(见下)
base.py Platform 契约(id / label / study / entries)
registry.py 注册表:register / get / detect / all
detect.py 页面代际识别(legacy 旧版 / spa 新版)
guanyixue.py 莞易学平台:合并 jspx 登录入口与 study 学习入口
jspx.py 莞易学旧版页面实现(会话 / 登录 / 课程抓取 / 旧版学习)
study.py 莞易学新版 SPA 实现(StudyClient / 接口学习)
routes/ HTTP 接口
accounts.py 账号 / 登录 / 登录记录
misc.py 课程 / 发现 / 学习 / 停止 / 抓包 / 快照 / 平台列表
logs.py /api/log、/api/runs
web/ 前端源码(React 19 + TypeScript + Vite + Tailwind CSS v4)
static/ 前端构建产物(index.html + assets/ + favicon.svg,提交进仓库)
des*.js 服务端 DES 加密脚本(Node 调用)
```
## 分层
- **入口层**:`app.py`(根)+ `study_tool/app.py`。前者只负责 `sys.path` 与启动,后者组装应用。
- **接口层**:`routes/*` 与 `quiz.py` 里的 `/api/quiz/*`,只做参数校验、调用引擎、返回结果。
- **领域层**:`engine.py`(学习流程编排)、`platforms/*`(平台实现)、`llm.py` / `quiz.py` 的解析逻辑。
- **基础设施**:`store.py`(SQLite)、`runtime.py`(状态 + 日志)、`config.py`。
依赖方向单向:接口 → 领域 → 基础设施。基础设施不反向 import 接口。
## 平台插件与识别
平台实现在 `platforms/`,通过注册表注入,`engine` 只按 `course["platform"]` 分发:
```python
# engine.run_one_course 末尾
pf = registry.get(d.get("platform"))
pf.study(key, s, d, workers=..., cap=..., enable_quiz=..., ...)
```
`Platform` 契约(见 `platforms/base.py`):
```python
@dataclass
class Platform:
id: str # course["platform"] 的取值
label: str # 日志 / 界面展示名,如「莞易学」
study: Callable # (key, s, d, **opts) -> None
entries: tuple[str, ...] # 登录入口 / 学习入口的 URL 片段
```
`registry.detect(url, html)` 按 `entries` 命中平台;课程发现时把平台 id 写进
`course["platform"]`,把页面代际写进 `course["generation"]`(`legacy` / `spa`)。
引擎按平台分发,平台内部再按代际选择学习实现。
`opts` 固定包含:`workers / cap / enable_quiz / enable_feedback / mode / parallel /
quiz_key / cookies / plan / project / name`。插件可以只取自己需要的。
### 内置:莞易学
`jspx.dgjy.net`(登录入口)和 `study.dgjy.net`(学习入口)是同一个平台,统一注册为
`id=guanyixue`、`label=莞易学`。两代课程页:
- `platforms/jspx.py`:旧版页面(`tier-tree-one` / `tier3`),负责会话、登录、课程抓取,
学习流程在 `engine._study_builtin`。
- `platforms/study.py`:新版 Vue SPA,`StudyClient` + `/train/course/front/*` 接口,
学习流程在 `engine.run_study_course`。
- `platforms/guanyixue.py`:注册平台并按 `course["generation"]` 分发;
`platforms/detect.py` 只判断页面代际,不判断平台归属。
### 新增一个平台
1. 新建 `src/study_tool/platforms/<名字>.py`:
```python
from study_tool.platforms import registry
from study_tool.platforms.base import Platform
def _study(key, s, d, **opts):
# 用 s(requests.Session,带该账号 cookie)把课程活动跑完
# 日志一律 runtime.log(key, ...)
...
PLATFORM = registry.register(Platform(
id="<平台 id>", label="展示名", study=_study,
entries=("<登录入口域名>", "<学习入口域名>"),
))
```
2. 在 `platforms/__init__.py` 的 `load_builtin()` 里加一行 `from study_tool.platforms import <名字>`。
3. 平台自己的登录 / 发现流程把 `course["platform"]` 写成该 id 后,引擎自动分发;
`registry.detect()` 用登录 / 学习入口识别平台,不需要改前端。
现阶段登录与课程发现仍由莞易学模块承担(SSO 只有一套);接入第二个平台时,
在账号登录 / 发现入口按 `Platform.entries` 命中结果选择对应实现即可。
## 前端
`web/` 是独立的前端工程:React 19 + TypeScript + Vite + Tailwind CSS v4。
- `web/src/App.tsx`:账号轮询、操作编排、弹窗状态;`components/` 放卡片 / 弹窗 / 基础 UI。
- `web/src/lib/api.ts`:统一 fetch 封装(401 / 离线判定)与 SSE 读取。
- `web/src/types.ts`:账号、课程、运行状态、测验等接口类型。
- 构建产物输出到仓库根的 `static/`(`index.html` + `assets/*` + `favicon.svg`),
FastAPI 直接托管:`/` 返回 `static/index.html`(禁止缓存),`/assets` 挂 `StaticFiles`。
- 开发:`cd web && pnpm install && pnpm dev`(5173,`/api` 代理到 `127.0.0.1:8848`)。
- 构建 / 类型检查:`pnpm build` / `pnpm typecheck`。
## 日志
统一入口 `runtime.log(key, msg, level="info")`,一次写三处:控制台、内存
(`/api/log` 给前端轮询)、SQLite(`/api/log/history`)。key 用账号 id 或 `system`。
规范见 `AGENTS.md` 的「日志规范」,不要自己 `print()`。
+7
View File
@@ -0,0 +1,7 @@
# 后续待办
- 接入第二个学习平台时,把该平台的登录、课程发现实现放进 `platforms/<平台>.py`,
登录 / 发现入口用 `Platform.entries` + `registry.detect()` 选择;目前登录与发现仍由莞易学模块承担。
- 前端补一条自动化回归(Playwright):空态、账号卡片、测验弹窗、设置保存。
- 构建检查进 CI:`cd web && pnpm install --frozen-lockfile && pnpm typecheck && pnpm build`,
并校验 `static/` 与源码同步。