SACC / README.md
cacode's picture
Update Space: schedule, MySQL persistence, registration codes, registration flow
a30f196 verified
---
title: Advanced SCU Course Catcher
emoji: 🐳
colorFrom: green
colorTo: yellow
sdk: docker
app_port: 7860
pinned: false
---
# Advanced SCU Course Catcher
这是一个面向 Hugging Face Space 的四川大学选课系统 Web 版抢课面板。当前版本已经从单机脚本改造成了可部署、可并行、可多用户管理的 Flask + Selenium 服务,支持普通用户端和管理员端分离运行。
## 当前能力
- 普通用户入口:`/login`
用户使用 `学号 + 密码` 登录,只能看到自己的课程、任务状态和实时日志。
- 管理员入口:`/admin`
管理员使用 `账号 + 密码` 登录。此入口不会在用户登录页展示。
- 多管理员体系
支持多位普通管理员,支持 1 位超级管理员。超级管理员账号和密码由环境变量 `ADMIN``PASSWORD` 提供。
- 多用户管理
管理员可以手动录入用户账号、重置密码、启用/禁用用户、查看所有课程目标。
- 课程录入
用户自己填写 `课程号``课序号`,管理员可见全部内容,也可以代为录入和修改。
- 实时日志
用户和管理员都可以实时看到程序日志,前端通过 SSE 持续推送。
- 并行调度
多用户任务由后台任务调度器并行执行,管理员可以在后台动态设置并行数。
- Hugging Face Space 适配
使用 Docker Space 运行,内置 Chromium、chromedriver、中文字体和无头浏览器配置。
- 登录验证码与提交验证码 OCR
登录阶段和提交选课阶段都支持验证码 OCR 自动识别。
- Selenium 自恢复
单个 Selenium 会话连续错误达到 5 次时,会自动重建浏览器;连续重建 5 个会话仍失败时,任务才会终止。
## 运行结构
当前实际运行入口和核心模块如下:
- `app.py`
Hugging Face / gunicorn 使用的 WSGI 入口。
- `space_app.py`
Flask 路由、页面渲染、登录鉴权、SSE 日志流。
- `core/config.py`
运行配置与内部密钥管理。
- `core/db.py`
SQLite 数据层。
- `core/task_manager.py`
并行调度、任务生命周期管理。
- `core/course_bot.py`
Selenium 抢课核心逻辑、验证码识别、重试与重建策略。
- `webdriver_utils.py`
Chromium / chromedriver 初始化。
- `templates/` + `static/`
用户端、管理员端和响应式前端页面。
## 环境变量
为了减少 Space 配置复杂度,当前只保留少量必要环境变量。
### 必填
- `ADMIN`
超级管理员账号。
- `PASSWORD`
超级管理员密码。
### 可选
- `DATA_DIR`
数据目录,默认会使用仓库下的 `data/`。在 Hugging Face Space 中建议保持为 `/data`
- `CHROME_BIN`
自定义 Chromium 路径。默认会自动尝试 `/usr/bin/chromium`
- `CHROMEDRIVER_PATH`
自定义 chromedriver 路径。默认会自动尝试 `/usr/bin/chromedriver`
### 不需要再手动配置的内容
以下内容已经改为自动生成或写入程序默认值,不需要再手工配置环境变量:
- Flask session secret
- 用户密码加密密钥
- 默认并行数
- 登录重试次数
- Selenium 会话错误阈值
- Selenium 会话重建阈值
- 提交验证码重试次数
- 页面超时时间
- 日志页容量
程序会在 `DATA_DIR/.app_secrets.json` 中自动持久化内部密钥。这样即使以后修改 `ADMIN``PASSWORD`,也不会导致历史用户密码全部失效。
## Hugging Face Space 部署
### 1. 创建 Space
在 Hugging Face 上创建一个新的 Space:
- SDK 选择 `Docker`
- 端口使用 `7860`
- 仓库内容直接推送本项目即可
本仓库根目录已经提供好 `README.md` 的 Space metadata 和 `Dockerfile`
### 2. 设置 Space Secrets
进入 `Settings -> Variables and secrets`,至少配置:
- `ADMIN=你的超级管理员账号`
- `PASSWORD=你的超级管理员密码`
通常不需要再设置其他环境变量。
如果你想显式指定数据目录,也可以增加:
- `DATA_DIR=/data`
### 3. 开启持久化存储
如果你希望以下数据在 Space 重启后仍然保留,建议在 Space 设置里开启 Persistent Storage:
- 用户账号
- 管理员账号
- 课程目标
- 任务历史
- 运行日志
- 自动生成的内部密钥文件
当前 Dockerfile 已按 `/data` 目录进行适配,并预先处理了目录权限,方便在 Space 中直接持久化。
### 4. 推送部署
如果你使用 git 推送到 Hugging Face Space,可以参考:
```bash
git remote add space https://huggingface.co/spaces/<your-name>/<your-space>
git push space main
```
### 5. 启动方式
容器启动命令已经写入 `Dockerfile`
```bash
gunicorn --bind 0.0.0.0:${PORT:-7860} --workers 1 --threads 8 --timeout 180 app:app
```
这里使用 `1` 个 gunicorn worker,是为了避免多进程情况下每个进程都各自启动一套本地任务调度器。并发能力由应用内部的线程调度和管理员可配置的任务并行数控制。
## Space 端建议配置
推荐的初始运行策略:
- 后台并行数先设置为 `1``2`
- 只有在 Space CPU / 内存足够稳定时,再逐步提升
- 在真实高峰前,先做一次小规模联调
对于 CPU 较小的 Space,同时拉起过多 Chromium 会明显增加失败率,因此管理员后台提供了并行数动态调整功能。
## 管理员联调清单
部署到 Space 后,建议按以下顺序联调:
1. 访问 `/admin`,使用 `ADMIN` / `PASSWORD` 登录超级管理员。
2. 在管理员后台创建 1 个普通管理员、2 个测试用户。
3. 为两个测试用户分别录入不同课程号和课序号。
4. 将并行数设置为 `2`
5. 分别触发两个用户任务,确认两条任务都能进入队列,并按并行数启动。
6. 在管理员日志面板确认日志持续刷新,且可以看到不同学号的执行记录。
7. 使用用户账号访问 `/login`,确认用户只能看到自己的课程和日志。
8. 在任务运行过程中测试停止任务,确认状态能变为“停止中”并最终收敛。
9. 如果学校页面出现提交验证码,确认日志中能看到提交阶段验证码识别提示。
## 自动化测试
仓库当前已补充以下自动化测试:
- `tests/test_config.py`
验证内部密钥在超级管理员账号变更后依然保持稳定。
- `tests/test_course_bot.py`
验证 Selenium 会话错误累计后会触发浏览器重建,并验证提交阶段验证码分支。
- `tests/test_task_manager.py`
验证任务调度器会严格遵守管理员设置的并行上限。
- `.github/workflows/linux-tests.yml`
`ubuntu-latest` 上自动运行上述测试,避免关键逻辑只在 Windows 本地验证。
本地运行测试:
```bash
python -m unittest discover -s tests -v
```
## 本地运行
### 直接运行
```bash
pip install -r requirements.txt
set ADMIN=admin
set PASSWORD=change-me
python main.py
```
### Docker 运行
```bash
docker build -t advanced-scu-course-catcher .
docker run --rm -p 7860:7860 \
-e ADMIN=admin \
-e PASSWORD=change-me \
advanced-scu-course-catcher
```
## 重要说明
- 本项目当前的任务模型是“持续轮询直到成功、手动停止或达到错误上限”。
- 用户密码会以应用内部密钥加密后保存在数据库中,用于后台自动登录教务系统。
- 管理员可以看到全部用户、课程和日志,请在实际使用前明确权限边界。
- 如果学校选课页面 DOM 结构变化较大,需要同步更新 `core/course_bot.py` 中的选择器以及 `javascript/` 下的辅助脚本。
- `course_catcher/` 目录是仓库中保留的旧实现,当前运行链路以 `space_app.py + core/*` 为准。