Files
study-tool/docs/architecture.md
T

6.4 KiB
Raw Blame History

架构说明

目录

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"] 分发:

# engine.run_one_course 末尾
pf = registry.get(d.get("platform"))
pf.study(key, s, d, workers=..., cap=..., enable_quiz=..., ...)

Platform 契约(见 platforms/base.py):

@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:

    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()。