智慧教室管理系统 - 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.basemanage.pywsgi.pyasgi.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/,主路由注册了以下资源:

    • users

    • classrooms

    • borrow

    • repairs

    • abuse

    • logs

    • courses

    • checkin-sessions

    • checkin-records

    • checkin-configs

  • 另外还定义了若干独立接口:

    • 认证相关:邮箱登录/注册、微信登录、登出、刷新 token、/auth/me

    • TOTP:/auth/totp/setup|enable|disable

    • WebAuthn:注册参数、注册提交、登录参数、登录提交

    • 微信绑定:/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.CookieJWTAuthentication

    • rest_framework.authentication.SessionAuthentication

  • Web 端邮箱登录和 WebAuthn 登录成功后,会同时返回 token 数据并设置这些 Cookie:

    • token

    • refresh_token

    • logged_in

    • csrftoken

  • 小程序微信登录不使用 Cookie,直接在响应体中返回 access/refresh token。

  • CookieJWTAuthentication 优先读取 Authorization 头,其次读取 token Cookie;因此浏览器请求和脚本调用都兼容。

  • 用户模型使用 roles JSON 列表保存全部角色,当前活动角色 current_role 不落库,而是放在 JWT claim 里,并可由请求头 X-Current-Role 覆盖。

  • 登录 token 会携带 rolescurrent_roleauth_version。密码、角色或状态发生变化时,后端会递增 auth_version,旧 token 自动失效。

  • 用户状态为 inactive 时会被认证层拒绝。

响应格式现状

  • common.responses.success/error 提供了 {code, message, data} 风格的统一返回。

  • 但当前代码并不是所有接口都使用这套封装:

    • 许多自定义动作和函数视图使用统一封装。

    • 多个 ModelViewSet 的默认 list/retrieve/update 仍直接返回 DRF 序列化结果或分页结果。

  • 因此后端当前处于“统一封装与 DRF 原生返回并存”的状态,开发新接口时需要显式确认返回风格。

主要业务模块

账户与认证 apps.accounts

  • User 基于 AbstractUser 扩展,核心字段包括:

    • user_code

    • full_name

    • wxid

    • roles

    • status

    • student_class

    • preferred_locale

    • totp_secret

    • is_totp_enabled

    • auth_version

  • 支持的角色包括:superadminsecretaryassistantcounselorchinese_teacherforeign_teacherstudent

  • 邮箱登录接口支持“邮箱或工号/学号 + 密码”,并会尝试先做 SM2 解密,失败后回退到明文兼容逻辑。

  • 支持:

    • 邮箱注册外教账号

    • 微信小程序登录

    • 微信绑定与解绑

    • TOTP 双因素认证

    • WebAuthn 安全密钥注册与登录

    • 管理员邀请外教

    • Excel 批量导入用户

  • 用户语言偏好会在首次访问或登录时按 Accept-Language 规范化到 zh-CNen-US

教室与门牌 apps.classrooms / apps.signage

  • Classroom 保存教学楼、教室号、容量、设备、状态、维护时间和备注,building + room_number 唯一。

  • 教室列表支持按 buildingcapacity_minstatus 过滤。

  • 权限规则:

    • 创建教室:仅 superadmin

    • 更新教室:superadminsecretary

    • 删除教室:仅 superadmin

    • Excel 导入教室:superadminsecretaryassistant

  • free 接口会同时排除两类冲突:

    • 手动借用申请(pending/approvedsource_type=manual

    • 课程表展开后的课程占用

  • schedule 接口会把“手动借用 + 课程排课”合并为时间块;未登录访问时不返回占用原因。

  • PublicClassroomViewSet 允许匿名查看公开教室,但只暴露 id/building/room_number/status

  • 门牌侧使用 SignageDeviceClassroom 一对一关联。门牌日程接口只返回课程系统生成的日程,不合并手动借用。

课程与时令作息 apps.courses

  • 课程数据拆成两层:

    • Course:课程基础信息、教师、班级、起止日期

    • CourseSession:具体星期、教室、节次列表、周次列表

  • 节次时间不是固定写死在模型里,而是通过 SeasonConfigServiceSystemConfig.season 动态解析:

    • wintersummer 两套时间表

    • 使用 Django cache 缓存当前季节和时段配置

  • CourseViewSet 是只读接口,支持按 teacher_idclass_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_type

    • activity_type

    • review_level

    • reviewer_role

    • is_urgent

    • auto_generated

  • 列表权限不是简单按“是否管理员”区分,而是按当前活动角色分流:

    • 教师默认只看自己的人工借用

    • 辅导员默认看一审学生活动

    • 教学秘书默认看一审教学活动

    • 助理默认看二审申请

    • 学生只能看自己的申请

    • 超管默认可看全部

  • 创建借用申请时,后端会校验:

    • 开始结束时间是否合法

    • 申请时间是否符合角色限制

    • 学生活动/教学活动是否和申请人角色匹配

    • 申请人数是否超过教室容量

    • 是否与已有手动借用或课程排课冲突

  • 审批是两级流程:

    • 一审由 counselorsecretary 负责,取决于 activity_type

    • 一审通过后自动流转到二审 assistant

    • assistantsuperadmin 可以审批自己的申请

    • superadmin 可以直接最终通过

  • 申请人可取消自己的申请,但距离开始时间不足 6 小时时禁止取消。

报修 apps.repairs

  • RepairTicket 保存报修人、指派人、教室、描述、图片、状态和备注。

  • 普通报修创建时会检查同教室是否已有 open/processing 工单,存在则拒绝新建。

  • 紧急报修接口不会做这层重复校验,响应里会附带 duty_phone

  • assistantsuperadmincounselor 可以更新工单状态并自动成为 assignee

  • 报修人本人可通过 /confirm 将工单标记为 confirmed

违规举报 apps.abuse

  • AbuseReport 记录举报人、教室、描述、图片、状态和备注。

  • superadmin 只能查看自己的举报记录。

  • 只有 superadmin 可以更新举报状态和处理备注。

操作日志 apps.logs

  • OperationLogContextMiddleware 使用 ContextVar 记录当前请求,便于服务层或装饰器写日志。

  • log_operation() 会记录用户、活动角色、动作、模块、目标对象、IP、UA 和附加 JSON 数据。

  • 目前借用、报修、签到等关键动作都已有显式日志写入。

  • 日志接口支持按时间、用户、角色、模块、动作过滤,并支持导出 CSV。

  • 需要注意:IsLogAdmin 仍保留对历史字段 user.role 的判断,但当前 User 模型已无该字段;因此实际可稳定访问日志接口的通常是 Django is_superuseris_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_IDWEBAUTHN_RP_NAMEWEBAUTHN_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