课程管理系统(Course Management System,简称 CMS)是一套面向学校内部使用的课程、课表、班级、学生和公告管理系统。项目由 Windows 桌面客户端、Android 客户端和 FastAPI 服务端组成。
- 用户登录、退出登录、令牌校验和修改密码
- 管理员维护课程、教师、班级、作息时间和公告
- 教师查看个人课表、班级课表、学生信息、请假信息和学生备注
- Windows 客户端导出课表数据
- Android 客户端离线缓存课表,并使用 Android Keystore 保护登录令牌
- SQLite 数据库存储业务数据,头像和运行日志存放在独立数据目录
.
├── src/
│ ├── client/ # Windows 客户端:CustomTkinter UI、网络请求和本地缓存
│ ├── server/ # FastAPI 服务端、路由、认证、权限和数据库初始化
│ ├── constants.py # Python 客户端与服务端共用的学期和年级常量
│ └── main.py # Windows 客户端启动入口
├── android_client/ # Android 客户端:Kotlin、Retrofit、Room
├── assets/ # Windows 客户端图标和其他静态资源
├── data/ # 运行时数据目录,仅保留 data/.gitkeep
├── docs/architecture.md # 系统边界和部署架构说明
├── requirements.txt # Python 依赖
├── .env.example # 服务端和 Windows 客户端配置模板
└── 课程管理系统.spec # PyInstaller 打包配置
运行时数据不应提交到 GitHub。生产环境建议将 CMS_DATA_DIR 设置为源代码目录之外的专用目录,并单独进行访问控制和备份。
Windows 客户端 ─┐
├─ HTTPS + Authorization: Bearer <token> ─> FastAPI ─> SQLite/头像/日志
Android 客户端 ─┘
服务端是唯一的权限判断边界。客户端中的角色判断只用于控制界面显示,不能替代服务端鉴权。生产环境应由 Nginx、Caddy 或云负载均衡器终止 TLS,再将请求转发给仅监听本机的 Uvicorn。
- Windows、Linux 或 macOS
- Python 3.10 或更高版本
- 生产环境建议使用独立虚拟环境
- 服务端需要可写的数据目录
- Android Studio 或 JDK 17 兼容的 Android 构建环境
- Android SDK 34
- Android 8.0(API 26)或更高版本的设备或模拟器
- 项目自带 Gradle Wrapper,可使用
gradlew或gradlew.bat
git clone <repository-url>
Set-Location CourseManagementSystem
py -3 -m venv .venv
.\.venv\Scripts\Activate.ps1
py -m pip install --upgrade pip
py -m pip install -r requirements.txtLinux/macOS:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt复制 .env.example 为 .env,或在服务管理器中直接设置环境变量:
Copy-Item .env.example .env配置项说明:
| 变量 | 必填 | 说明 |
|---|---|---|
CMS_HOST |
否 | Uvicorn 监听地址,默认 127.0.0.1 |
CMS_PORT |
否 | Uvicorn 监听端口,默认 5000 |
CMS_DEBUG |
否 | 是否启用 FastAPI 调试文档,生产环境必须为 false |
CMS_LOG_LEVEL |
否 | 日志级别,默认 INFO |
CMS_DATA_DIR |
否 | 数据库、日志和头像目录,生产环境建议放在项目目录外 |
CMS_SECRET_KEY |
是 | 部署实例的随机长字符串,不能使用示例值 |
CMS_SEED_PASSWORD |
首次初始化必填 | 空数据库首次初始化时的演示账号初始密码,至少 12 个字符 |
CMS_TOKEN_TTL_DAYS |
否 | 登录令牌有效天数,默认 30 |
CMS_SERVER_URL |
客户端使用 | Windows 客户端连接的服务端基地址 |
生成随机值的示例:
py -c "import secrets; print(secrets.token_urlsafe(48))"生产环境至少需要设置:
CMS_HOST=127.0.0.1
CMS_PORT=5000
CMS_DEBUG=false
CMS_DATA_DIR=/var/lib/course-management-system
CMS_SECRET_KEY=<随机生成的长字符串>
CMS_SEED_PASSWORD=<仅用于首次初始化的随机密码>
CMS_TOKEN_TTL_DAYS=30
不要把真实 .env 文件提交到仓库,也不要把密码写入启动脚本、截图、Issue 或日志。
服务端启动时会自动创建 SQLite 表。第一次启动空数据目录时,CMS_SEED_PASSWORD 会用于生成 models.py 中的示例账号:
$env:CMS_SECRET_KEY = "replace-with-a-private-random-secret"
$env:CMS_SEED_PASSWORD = "replace-with-a-private-seed-password"
$env:CMS_DATA_DIR = "$((Get-Location).Path)\runtime-data"
py -m src.server.serverLinux/macOS:
export CMS_SECRET_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')"
export CMS_SEED_PASSWORD="$(python -c 'import secrets; print(secrets.token_urlsafe(18))')"
export CMS_DATA_DIR=/var/lib/course-management-system
python -m src.server.server启动成功后,服务端默认监听 127.0.0.1:5000。健康检查:
GET http://127.0.0.1:5000/
返回 status: running 仅表示进程正常运行,不代表服务端已经配置好公网 TLS。
- 使用部署时设置的
CMS_SEED_PASSWORD登录每个示例账号。 - 立即为每个账号设置不同的强密码。
- 删除不需要的示例账号,或在管理员接口中替换为实际账号。
- 检查管理员、班主任、课程教师和班级的对应关系。
- 确认数据库备份目录权限仅允许服务账号和备份账号访问。
- 初始化完成后可以保留
CMS_SEED_PASSWORD,但它不应再被视为生产账号密码;若重新初始化新数据库,仍需提供一个新的随机值。
开发和小规模内部部署可以直接运行:
.\.venv\Scripts\Activate.ps1
py -m src.server.server长期运行时应使用 Windows Service、任务计划程序或其他进程管理器,并设置:
- 工作目录为项目根目录;
- Python 可执行文件为虚拟环境中的 Python;
CMS_DATA_DIR指向专用数据目录;- 自动重启和日志轮转;
- 服务账号只拥有项目和数据目录所需的最小权限。
以下示例假设项目位于 /opt/course-management-system,运行用户为 cms:
[Unit]
Description=Course Management System API
After=network.target
[Service]
User=cms
Group=cms
WorkingDirectory=/opt/course-management-system
EnvironmentFile=/etc/course-management-system/cms.env
ExecStart=/opt/course-management-system/.venv/bin/python -m src.server.server
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target将配置文件保存为 /etc/systemd/system/course-management-system.service,再执行:
sudo systemctl daemon-reload
sudo systemctl enable --now course-management-system
sudo systemctl status course-management-system
sudo journalctl -u course-management-system -f客户端传输登录密码、Bearer Token、学生信息和头像数据。生产环境必须使用 HTTPS,不能让客户端直接连接公网 HTTP Uvicorn 端口。
以下配置将 https://cms.example.com 转发到本机 127.0.0.1:5000。证书路径应替换为实际证书路径,证书可以使用受信任的 ACME/Let's Encrypt 证书:
server {
listen 80;
listen [::]:80;
server_name cms.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name cms.example.com;
ssl_certificate /etc/letsencrypt/live/cms.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cms.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
client_max_body_size 5m;
proxy_read_timeout 30s;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}校验证书和重载配置:
sudo nginx -t
sudo systemctl reload nginx
curl -fsS https://cms.example.com/防火墙只应开放 80/tcp 和 443/tcp。不要开放公网 5000/tcp,除非部署环境有明确的网络隔离和访问控制。
cms.example.com {
reverse_proxy 127.0.0.1:5000
request_body {
max_size 5MB
}
}Caddy 会自动申请和续期受信任的 TLS 证书。服务端进程仍建议只监听 127.0.0.1。
SQLite 使用 WAL 模式运行。备份时应先暂停写入,或使用 SQLite 的在线备份机制;不要只复制正在运行中的 cms.db 而忽略 -wal 文件。
停机复制示例:
sudo systemctl stop course-management-system
cp /var/lib/course-management-system/cms.db /backup/cms-$(date +%F).db
sudo systemctl start course-management-system备份内容还包括头像文件和必要的运行配置,但不应把备份目录放进 Git 仓库。备份文件应加密保存,并定期验证可以恢复。
src/server/models.py 的 _seed_data() 只会在 users 表为空时写入初始示例数据。因此,修改代码后已经存在的数据库不会自动更新。修改示例数据时必须明确区分“修改种子代码”和“修改现有数据库”两种操作。
在 users 列表中修改以下字段:
users = [
(1, "示例管理员一", pw(seed_password), "admin", "示例管理员一"),
(2, "示例管理员二", pw(seed_password), "admin", "示例管理员二"),
(3, "示例管理员三", pw(seed_password), "admin", "示例管理员三"),
]每一项的顺序是:
(用户 ID, 登录用户名, 密码哈希, 角色, 显示名称)
规则:
- 登录用户名必须唯一;
admin为管理员,teacher为教师,member为普通用户;- 初始密码由
CMS_SEED_PASSWORD统一提供,代码中不能写入明文密码; - 修改用户名或显示名称后,必须同步修改下方课表中的教师名称引用;
- 生产环境不要把真实人员姓名直接写入公开仓库的种子数据,可在部署后通过管理流程录入。
grade_advisor 定义班级与班主任的关系:
grade_advisor = {
"一年级": "示例教师一",
"二年级": "示例教师二",
}字典的键必须与 grade_schedules 的年级键一致,值必须存在于 users 列表中,并且通常应对应 teacher 角色。班级教室名称由以下代码生成:
f"{grade}教室"如果要使用自定义班级名称或地点,应同时修改班级插入逻辑、课表中的 class_name 和 location 字段,避免学生、课程和班级无法关联。
grade_schedules 的每一项格式如下:
("数学", "示例教师一")外层结构是:
年级 -> 星期几(0 表示星期一) -> 一天中的 7 个上课时段
periods 决定 7 个课程槽位使用的作息表 ID 和时间。修改课程时应注意:
- 每天课程数量应与
periods数量一致; - 教师名称必须能被
nid[teacher_name]找到; - 课程所属年级必须同时存在于
grade_advisor; - 课程颜色键应与
scol中的科目名称一致,否则使用默认颜色; - 修改课表后应重新初始化测试数据库并登录验证每个角色。
公告位于 _seed_data() 中的 announcements 列表:
announcements = [
("公告标题", "公告正文"),
]公告正文中的换行使用 \n。不要在公开仓库中写入真实电话号码、邮箱、地址或内部系统信息。
示例学生由以下逻辑生成:
for grade in grade_advisor.keys():
for i in range(1, 6):
...学生姓名、联系方式、性别和走读状态都是演示数据。修改时必须保证:
class_name已存在于classes.name;gender、is_boarding使用客户端和服务端约定的值;- 联系方式只能使用虚构号码;
- 不要把真实未成年人信息放入公开仓库;
- 生产数据应通过受控的管理流程导入,而不是提交 SQLite 数据库。
仅修改 models.py 不会覆盖已有数据库。开发环境可以删除本地数据库后重新启动:
Remove-Item .\data\cms.db, .\data\cms.db-shm, .\data\cms.db-wal -Force -ErrorAction SilentlyContinue
$env:CMS_SEED_PASSWORD = "a-new-private-random-password"
py -m src.server.server生产环境不要直接删除数据库。应先完成备份,在维护窗口执行迁移或通过管理接口逐项更新,并保留回滚方案。
在项目根目录创建并激活 Python 虚拟环境后运行:
.\.venv\Scripts\Activate.ps1
$env:CMS_SERVER_URL = "https://cms.example.com"
py -m src.main开发环境连接本机服务端:
$env:CMS_SERVER_URL = "http://127.0.0.1:5000"
py -m src.main生产环境必须使用 HTTPS 域名。客户端不会通过请求参数或自定义用户 ID 进行身份认证,登录后只使用服务端返回的 Bearer Token。
.\.venv\Scripts\Activate.ps1
py -m pip install pyinstaller
pyinstaller --clean --noconfirm "课程管理系统.spec"构建结果在 dist/。发布前应在干净的 Windows 环境中测试启动、登录、课表、公告、三种角色权限、修改密码、退出登录、头像上传和课表导出,并确认客户端配置的是正式 HTTPS 地址。
Android 客户端位于 android_client/。服务端地址通过 Gradle 属性 CMS_BASE_URL 注入,不应硬编码到 Kotlin 源代码。
Android 模拟器访问开发机本机服务端时,使用 10.0.2.2:
Set-Location android_client
.\gradlew.bat assembleDebug -PCMS_BASE_URL=http://10.0.2.2:5000/真机调试时,应使用开发机在局域网中的地址,并确保防火墙允许局域网访问。只允许调试构建使用明文 HTTP;发布构建默认禁止明文流量。
- 用 Android Studio 打开
android_client/。 - 等待 Gradle 同步完成。
- 在 Gradle properties 或命令行中设置
CMS_BASE_URL。 - 选择模拟器或 USB 调试设备。
- 执行
assembleDebug或直接运行 Debug 配置。
认证请求使用:
Authorization: Bearer <token>主要接口:
| 路径 | 方法 | 用途 |
|---|---|---|
/api/login |
POST |
登录并获取 Bearer Token |
/api/logout |
POST |
撤销当前令牌 |
/api/verify |
GET |
验证当前令牌 |
/api/schedule/full |
GET |
获取课表和当前用户课程 |
/api/timetable |
GET |
获取作息时间 |
/api/announcements |
GET |
获取公告 |
/api/user/... |
多种 | 获取资料、修改密码和头像 |
/api/classes/... |
GET |
获取班级和教师信息 |
/api/students/... |
多种 | 获取学生、备注和请假信息 |
/api/admin/... |
多种 | 管理员课表和基础数据维护 |
/api/sync/status |
GET |
获取数据同步版本 |
生产环境默认关闭 FastAPI /docs、/redoc 和 OpenAPI 文档。调试时可临时设置 CMS_DEBUG=true,调试结束后必须恢复为 false。
数据目录为空时必须设置至少 12 个字符的 CMS_SEED_PASSWORD。如果数据库已经初始化,可以继续保留该环境变量,也可以在不重建数据库的情况下修改它;它不会自动修改现有用户密码。
依次检查服务端进程、客户端地址、Android 模拟器的 10.0.2.2、反向代理到 127.0.0.1:5000 的连通性、TLS 证书信任链、防火墙和云安全组的 443/tcp 放行情况。
Token 可能已过期、被注销、服务端数据库已恢复到旧备份,或客户端缓存了旧 Token。退出登录后重新登录;如果仍失败,检查服务端时间、CMS_DATA_DIR 和数据库文件是否正确。
种子函数只在空数据库上运行。开发环境删除数据库后重启,生产环境使用数据库迁移或受控管理接口,不要直接删除生产数据库。
确认四个 CMS_RELEASE_* 属性均已提供,并且 CMS_RELEASE_STORE_FILE 指向存在且可读的 keystore。调试包可以不配置发布签名,但发布包必须使用受保护的签名凭据。
本项目遵循 MIT 许可证 开源,允许学习、交流与二次开发。许可证正文以根目录 LICENSE 文件为准。
- 仅供学习交流:本项目主要用于个人学习与课程设计参考,不对代码在生产环境中的可用性或安全性作任何保证。
- 学术诚信提示:严禁直接抄袭、打包本项目用于课程作业、毕业设计或答辩提交。使用者须自行承担因违背学术诚信要求而产生的任何后果。
上述学术诚信提示是项目使用提醒,不改变 MIT 许可证授予的开源权利。使用者仍应遵守所在学校、机构或平台的学术规范。