智慧教室管理系统 - Django 后端¶
Author: Zhenyu Yang yangzhenyu@sust.edu.cn
Last updated: Mar 20, 2026
本文档基于 backend/ 当前实现,描述 Classroom Manager Django 后端的实际架构、模块职责和开发注意事项。
架构与技术栈¶
框架:Django + Django REST Framework
数据库:MySQL
认证:SimpleJWT,自定义
CookieJWTAuthentication账号安全:TOTP、WebAuthn、SM2 密码传输、微信小程序登录
文件处理:本地上传目录;代码中预留 S3 兼容对象存储配置
Excel 导入:
openpyxl中间件扩展:操作日志上下文、活动角色提取、请求时区切换
项目结构¶
backend/
├── apps/
│ ├── accounts/ # 用户、登录、角色、TOTP、WebAuthn、微信绑定
│ ├── classrooms/ # 教室信息、公开查询、课表与空闲教室
│ ├── courses/ # 课程、节次、导入、调课、时令作息
│ ├── checkins/ # 课堂签到开关、签到场次、签到记录
│ ├── borrowings/ # 教室借用申请、多级审批、冲突校验
│ ├── repairs/ # 报修工单
│ ├── abuse/ # 违规举报
│ ├── logs/ # 操作日志查询与导出
│ └── signage/ # 门牌屏教室信息与课程日程
├── classroom_manager/
│ ├── settings/ # base.py / dev.py / prod.py
│ └── urls.py # 全局路由
├── common/ # 通用响应、分页、系统配置、公共视图/中间件
├── services/ # 微信、TOTP、WebAuthn、SM2、文件存储服务
└── manage.py
配置、路由与认证¶
配置¶
默认配置模块是
classroom_manager.settings.base;manage.py、wsgi.py、asgi.py都直接指向它。base.py负责大部分真实配置:INSTALLED_APPS注册了账户、教室、课程、签到、借用、报修、违规、日志、门牌和common。默认数据库是 MySQL,连接参数来自
MYSQL_*环境变量。默认语言是
zh-hans,时区是Asia/Shanghai。REST framework 默认要求认证,默认分页类是
common.pagination.DefaultPagination,默认PAGE_SIZE=200。登录接口使用节流,速率由
LOGIN_THROTTLE_RATE控制,默认5/minute。CORS 允许携带 Cookie,来源由
CORS_ALLOWED_ORIGINS控制。
dev.py仅覆盖DEBUG=True和本地ALLOWED_HOSTS。prod.py追加静态文件目录、媒体目录和CSRF_TRUSTED_ORIGINS。
路由¶
Django 管理后台路径是
/django-admin/。API 统一前缀是
/api/v1/,主路由注册了以下资源:usersclassroomsborrowrepairsabuselogscoursescheckin-sessionscheckin-recordscheckin-configs
另外还定义了若干独立接口:
认证相关:邮箱登录/注册、微信登录、登出、刷新 token、
/auth/meTOTP:
/auth/totp/setup|enable|disableWebAuthn:注册参数、注册提交、登录参数、登录提交
微信绑定:
/auth/wechat/bind|unbind用户管理补充接口:邀请外教、修改角色、修改状态、Excel 导入
通用接口:
/upload、/config、/config/season教室补充接口:公开列表、导入、日程、空闲教室
门牌接口:
/signage/classrooms/<id>及其/schedule
apps.accounts.urls还暴露了/api/v1/accounts/sm2/public-key,用于前端获取 SM2 公钥。开发和简化部署场景下,Django 会直接通过
/media/uploads/<path>提供上传文件访问。
认证与角色体系¶
默认认证类是:
apps.accounts.authentication.CookieJWTAuthenticationrest_framework.authentication.SessionAuthentication
Web 端邮箱登录和 WebAuthn 登录成功后,会同时返回 token 数据并设置这些 Cookie:
tokenrefresh_tokenlogged_incsrftoken
小程序微信登录不使用 Cookie,直接在响应体中返回 access/refresh token。
CookieJWTAuthentication优先读取Authorization头,其次读取tokenCookie;因此浏览器请求和脚本调用都兼容。用户模型使用
rolesJSON 列表保存全部角色,当前活动角色current_role不落库,而是放在 JWT claim 里,并可由请求头X-Current-Role覆盖。登录 token 会携带
roles、current_role和auth_version。密码、角色或状态发生变化时,后端会递增auth_version,旧 token 自动失效。用户状态为
inactive时会被认证层拒绝。
响应格式现状¶
common.responses.success/error提供了{code, message, data}风格的统一返回。但当前代码并不是所有接口都使用这套封装:
许多自定义动作和函数视图使用统一封装。
多个
ModelViewSet的默认list/retrieve/update仍直接返回 DRF 序列化结果或分页结果。
因此后端当前处于“统一封装与 DRF 原生返回并存”的状态,开发新接口时需要显式确认返回风格。
主要业务模块¶
账户与认证 apps.accounts¶
User基于AbstractUser扩展,核心字段包括:user_codefull_namewxidrolesstatusstudent_classpreferred_localetotp_secretis_totp_enabledauth_version
支持的角色包括:
superadmin、secretary、assistant、counselor、chinese_teacher、foreign_teacher、student。邮箱登录接口支持“邮箱或工号/学号 + 密码”,并会尝试先做 SM2 解密,失败后回退到明文兼容逻辑。
支持:
邮箱注册外教账号
微信小程序登录
微信绑定与解绑
TOTP 双因素认证
WebAuthn 安全密钥注册与登录
管理员邀请外教
Excel 批量导入用户
用户语言偏好会在首次访问或登录时按
Accept-Language规范化到zh-CN或en-US。
教室与门牌 apps.classrooms / apps.signage¶
Classroom保存教学楼、教室号、容量、设备、状态、维护时间和备注,building + room_number唯一。教室列表支持按
building、capacity_min、status过滤。权限规则:
创建教室:仅
superadmin更新教室:
superadmin或secretary删除教室:仅
superadminExcel 导入教室:
superadmin、secretary、assistant
free接口会同时排除两类冲突:手动借用申请(
pending/approved且source_type=manual)课程表展开后的课程占用
schedule接口会把“手动借用 + 课程排课”合并为时间块;未登录访问时不返回占用原因。PublicClassroomViewSet允许匿名查看公开教室,但只暴露id/building/room_number/status。门牌侧使用
SignageDevice与Classroom一对一关联。门牌日程接口只返回课程系统生成的日程,不合并手动借用。
课程与时令作息 apps.courses¶
课程数据拆成两层:
Course:课程基础信息、教师、班级、起止日期CourseSession:具体星期、教室、节次列表、周次列表
节次时间不是固定写死在模型里,而是通过
SeasonConfigService按SystemConfig.season动态解析:winter和summer两套时间表使用 Django cache 缓存当前季节和时段配置
CourseViewSet是只读接口,支持按teacher_id和class_name查询。课程导入支持 Excel 文件,且要求传入
semester_start_date。调课入口在两个层面:
CourseViewSet.reschedule_session直接调整CourseSession的周次/教室/节次BorrowApplicationViewSet.reschedule处理source_type=course的借用改期
课程冲突检测由
get_conflicting_classrooms()/has_course_conflict()完成,借用和空闲教室查询都会复用这套逻辑。
课堂签到 apps.checkins¶
签到能力由三张表支撑:
CourseCheckinSetting:某门课程是否开启签到CheckinSession:某节课在某一天的签到场次CheckinRecord:学生签到记录
开启签到后,后端会按课程节次和日期范围批量生成
CheckinSession,避免重复创建。CheckinSessionViewSet在读取列表时会根据当前时间自动刷新场次状态:开课前 30 分钟到下课前:
open下课后:
closed
已提供的业务动作包括:
开启/关闭课程签到
查询每日课程签到
手动开启签到场次
获取点名册
切换单个学生签到状态
学生查看自己的当日签到课表
教师/管理员手动签到
签到管理权限属于课程教师或
superadmin/secretary/assistant。
借用审批 apps.borrowings¶
BorrowApplication除了基础借用字段,还包含:source_typeactivity_typereview_levelreviewer_roleis_urgentauto_generated
列表权限不是简单按“是否管理员”区分,而是按当前活动角色分流:
教师默认只看自己的人工借用
辅导员默认看一审学生活动
教学秘书默认看一审教学活动
助理默认看二审申请
学生只能看自己的申请
超管默认可看全部
创建借用申请时,后端会校验:
开始结束时间是否合法
申请时间是否符合角色限制
学生活动/教学活动是否和申请人角色匹配
申请人数是否超过教室容量
是否与已有手动借用或课程排课冲突
审批是两级流程:
一审由
counselor或secretary负责,取决于activity_type一审通过后自动流转到二审
assistantassistant和superadmin可以审批自己的申请superadmin可以直接最终通过
申请人可取消自己的申请,但距离开始时间不足 6 小时时禁止取消。
报修 apps.repairs¶
RepairTicket保存报修人、指派人、教室、描述、图片、状态和备注。普通报修创建时会检查同教室是否已有
open/processing工单,存在则拒绝新建。紧急报修接口不会做这层重复校验,响应里会附带
duty_phone。assistant、superadmin、counselor可以更新工单状态并自动成为assignee。报修人本人可通过
/confirm将工单标记为confirmed。
违规举报 apps.abuse¶
AbuseReport记录举报人、教室、描述、图片、状态和备注。非
superadmin只能查看自己的举报记录。只有
superadmin可以更新举报状态和处理备注。
操作日志 apps.logs¶
OperationLogContextMiddleware使用ContextVar记录当前请求,便于服务层或装饰器写日志。log_operation()会记录用户、活动角色、动作、模块、目标对象、IP、UA 和附加 JSON 数据。目前借用、报修、签到等关键动作都已有显式日志写入。
日志接口支持按时间、用户、角色、模块、动作过滤,并支持导出 CSV。
需要注意:
IsLogAdmin仍保留对历史字段user.role的判断,但当前User模型已无该字段;因此实际可稳定访问日志接口的通常是 Djangois_superuser或is_staff账号。
通用能力 common / services¶
SystemConfig当前只管理季节作息season,并通过/api/v1/config与/api/v1/config/season暴露。RequestTimezoneMiddleware会读取请求头X-Timezone,动态激活当前请求时区。upload_file接口当前实现直接把文件写入LOCAL_UPLOAD_DIR,并返回LOCAL_UPLOAD_URL_PREFIX下的 URL。services/file_storage.py已实现本地/S3 双后端文件存储服务,但当前上传接口尚未接入这个服务类,实际运行仍以本地文件系统为准。微信登录依赖
WECHAT_APP_ID/WECHAT_APP_SECRET调用jscode2session。WebAuthn 依赖
WEBAUTHN_RP_ID、WEBAUTHN_RP_NAME、WEBAUTHN_ORIGIN。
开发与运维注意事项¶
默认 settings 不是 dev/prod 自动切换:直接执行
python manage.py ...默认使用classroom_manager.settings.base;如果需要显式切到其他配置,必须手动设置DJANGO_SETTINGS_MODULE。分页参数:分页接口支持
page_size,也兼容size,最大 200。登录限流:登录节流键会把客户端 IP 和账号标识一起哈希,避免单纯按 IP 限流过粗。
活动角色优先级:权限判断大量依赖
get_current_role(request),扩展角色时要同时检查 JWT claim、中间件和业务判断。时令缓存:修改季节时会同步清理课程时间表缓存;调试排课问题时要注意缓存影响。
上传目录:默认上传目录是
/data/uploads,而不是旧文档中的/var/www/...。响应风格不统一:编写前端调用或新接口时,不要假设所有 API 都返回
{code,message,data}。日志权限存在历史兼容代码:如果后续要把日志接口真正接入
roles/current_role体系,需要先修正apps.logs.permissions.IsLogAdmin。