# Software design Author: Zhenyu Yang , Sixu Wei Last updated: Feb 4, 2026 This document is used to describe the software functional requirements to the developer. --- ## MySQL 模板 ### 用户`user`模板 | 字段名 | 数据类型 | 约束 | 默认值 | 说明 | |-------------------|----------------------------|-----------------|-------------------|----------------------------| | `id` | INT | 主键,自增 | — | 数据库内部唯一标识 | | `user_code` | VARCHAR(50) | NOT NULL,UNIQUE | — | 学号/工号 | | `username` | VARCHAR(100) | NOT NULL,UNIQUE | — | 学号/工号,若无则填邮箱 | | `password` | VARCHAR(128) | NOT NULL | — | 加密后的密码(Django 自动加盐) | | `first_name` | VARCHAR(150) | — | '' | Django `AbstractUser` 内置字段 | | `last_name` | VARCHAR(150) | — | '' | Django `AbstractUser` 内置字段 | | `full_name` | VARCHAR(100) | NOT NULL | — | 用户真实姓名 | | `email` | VARCHAR(100) | UNIQUE | — | 邮箱,用于找回密码或通知 | | `phone` | VARCHAR(20) | — | — | 手机号码 | | `wxid` | VARCHAR(50) | UNIQUE | — | 用户微信ID | | `role` | ENUM(),七选一 | NOT NULL | `'student'` | 用户角色 | | `roles` | JSON | — | `[]` | 多角色列表(兼容保留 `role`) | | `current_role` | ENUM(),七选一 | 可为空 | NULL | 当前使用的角色 | | `status` | ENUM('active', 'inactive') | — | `'active'` | 用户账户状态 | | `student_class` | VARCHAR(100) | 可为空 | NULL | 学生班级 | | `totp_secret` | VARCHAR(64) | 可为空 | NULL | TOTP 密钥,初始化后保存 | | `is_totp_enabled` | BOOLEAN | — | `false` | 是否启用 TOTP 双因素认证 | | `date_joined` | DATETIME | — | CURRENT_TIMESTAMP | 账户创建时间 | | `last_login` | DATETIME | 可为空 | NULL | 最近登录时间 | | `remark` | TEXT | 可为空 | — | 备注信息 | > 说明:`first_name` / `last_name` 来自 Django `AbstractUser`,为兼容保留,业务不使用;系统真实姓名字段为 `full_name`。 ### 教室`classroom`模板 | 字段名 | 数据类型 | 约束 | 默认值 | 说明 | |--------------------|-----------------------------------------------------------|----------|-----------------------------------------------|-----------------| | `id` | INT | 主键,自增 | — | 教室唯一标识 | | `building` | VARCHAR(100) | NOT NULL | — | 教学楼名称 | | `room_number` | VARCHAR(50) | NOT NULL | — | 教室编号(如 A101) | | `capacity` | INT | — | `0` | 可容纳人数 | | `equipment` | JSON | 可为空 | — | 教室设备信息(JSON 格式) | | `status` | ENUM('available', 'occupied', 'under_repair', 'disabled') | — | `'available'` | 当前教室状态 | | `last_maintenance` | DATE | 可为空 | NULL | 上次维修日期 | | `create_time` | DATETIME | — | CURRENT_TIMESTAMP | 创建时间 | | `update_time` | DATETIME | — | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 最近更新时间 | | `remark` | TEXT | 可为空 | — | 备注信息 | ## 用户组定义 ### 服务端(后台) | 中文名称 | 用户组 | 审批教室借用 | 调整教室占用 | 接收报修信息 | 进行人事调整 | 增删教室 | |-------|--------------|--------|--------|--------|--------|------| | 超级管理员 | `superadmin` | 允许 | 允许 | 允许 | 允许 | 允许 | | 教学秘书 | `secretary` | 允许 | 允许 | 拒绝 | 拒绝 | 拒绝 | | 辅导员 | `counselor` | 允许 | 拒绝 | 拒绝 | 拒绝 | 拒绝 | | 办公室助理 | `assistant` | 允许 | 允许 | 允许 | 拒绝 | 拒绝 | ### 客户端(前台) | 中文名称 | 用户组 | 向谁申请教室 | 报修 | |------|-------------------|-------------------------------------------------------------------|----| | 中方教师 | `chinese_teacher` | `student_activity`: `counselor`; `teaching_activity`: `secretary` | 允许 | | 外方教师 | `foreign_teacher` | `student_activity`: `counselor`; `teaching_activity`: `secretary` | 拒绝 | | 学生 | `student` | 仅 `student_activity` -> `counselor` | 允许 | --- ## 教室借用流程 所有人(即使没有登录)也可通过系统查看教室占用情况,但是不显示占用具体信息(占用人信息等)和教室基本信息。 ### 关键字段(BorrowApplication) - `activity_type`:`student_activity`(学生活动)/`teaching_activity`(教学活动) - `review_level`:`first_review`(一审)/`second_review`(二审) - `reviewer_role`:当前应由哪个角色审批(例如 `counselor`/`secretary`/`assistant`/`superadmin`) - `is_urgent`:加急申请标记 ### 审批流转规则(两级审批) - 学生(`student`):仅允许申请学生活动(`student_activity`)。 - 中方教师/外方教师(`chinese_teacher`/`foreign_teacher`):可申请学生活动或教学活动。 - 学生活动(`student_activity`):一审 `counselor`。 - 教学活动(`teaching_activity`):一审 `secretary`。 - 一审通过后:自动流转到二审(`review_level=second_review`),二审角色为 `assistant`,办公室助理仅看到二审工单。 - 二审通过:状态变更为 `approved`。 - 超级管理员(`superadmin`):可查看所有工单,并允许在任意阶段直接通过。 - 转交(`forward`):可将 `reviewer_role` 调整为 `superadmin`(或其他角色)进行处理。 ### 时间限制规则 系统会根据申请人角色和 `is_urgent` 对申请开始时间进行校验: - `assistant` / `superadmin`:不受时间限制。 - `is_urgent=true`:只能选择当天。 - `student`:仅允许申请未来 **+1 到 +7 天**。 - `chinese_teacher` / `foreign_teacher` / `counselor` / `secretary`:仅允许申请未来 **至少 +1 天**。 取消规则:申请人可取消自己的申请,但若距离开始时间小于 6 小时则禁止取消。 ### 冲突校验 提交借用申请时会同时校验: - 已有人工借用(`pending/approved` 且 `source_type=manual`)的时间冲突。 - 课程表(`CourseSession` 展开后的占用)冲突。 --- ## 教室维修需求 ### 1. 非紧急情况 1. 所有已登录用户可通过**普通报修**进行报修; 2. `assistant`接到工单,尝试自行解决或联系维修人员; 3. 维修结束后,报修人检查维修结果,确认无误后工单结束。 注:同一教室存在未结束的工单时,禁止发起新的工单。 ### 2. 紧急情况 1. 所有已登录用户可通过**紧急报修**进行报修; 2. 系统显示当日值班`assistant`的电话号码; 3. `assistant`接到求助,尝试自行解决或联系维修人员; 4. 若无法自行解决,则`assistant`通过本系统查询同时段空闲教室,更换教室。 --- ## 人员认证机制 * 学工部API:通过学校统一身份认证获取人员信息 (11/23备注:此条废弃) * 微信ID:第一次使用时,绑定用户微信 * 邮箱:对于没有学号&工号&微信的外方教师,经`superadmin`或`secretary`邀请后,允许使用邮箱注册;使用邮箱+密码认证;可选使用 OTP 应用进行 2FA 认证。 * TOTP:账号可选择启用基于时间的一次性密码;后台提供密钥生成、启用、关闭接口,登录时需同时校验口令与 TOTP 码。 ## 教室调整 * 教室以门牌号进行区分; * 教室信息支持从 Excel 文件导入。 ## 人事调整 * 人员信息支持从 Excel 文件导入。 ## 附加功能 * 举报滥用情况:已登录用户可举报教室滥用情况,该工单直接推送至`superadmin`。 ## 课程表方案(Courses) 为便于维护周期性课程信息,新增课程基础表 `courses` 与课程节次表 `course_sessions`。课程基础信息与周期范围落在 `courses`,具体的每周上课节次(可包含不同教室/时段)落在 `course_sessions`,避免借用申请表承载周期性课表。 ### 课程 `courses` 表 | 字段名 | 数据类型 | 约束 | 默认值 | 说明 | |---------------------|--------------|-------------------|-----------------------------------------------|----------| | `id` | INT | 主键,自增 | — | 课程唯一标识 | | `name` | VARCHAR(200) | NOT NULL | — | 课程名称 | | `course_code` | VARCHAR(50) | 可为空 | NULL | 课程编号 | | `teacher_id` | INT | 外键(`user.id`),可为空 | NULL | 授课教师 | | `class_name` | VARCHAR(100) | 可为空 | '' | 上课班级/教学班 | | `expected_students` | INT | — | 0 | 预计人数 | | `start_date` | DATE | NOT NULL | — | 课程开始日期 | | `end_date` | DATE | NOT NULL | — | 课程结束日期 | | `remark` | TEXT | 可为空 | — | 备注 | | `created_at` | DATETIME | — | CURRENT_TIMESTAMP | 创建时间 | | `updated_at` | DATETIME | — | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 | ### 课程节次 `course_sessions` 表 | 字段名 | 数据类型 | 约束 | 默认值 | 说明 | |----------------|---------|--------------------|------|-----------------------------| | `id` | INT | 主键,自增 | — | 节次唯一标识 | | `course_id` | INT | 外键(`courses.id`) | — | 课程 | | `classroom_id` | INT | 外键(`classroom.id`) | — | 教室 | | `weekday` | TINYINT | 0-6(周一到周日) | — | 上课星期 | | `sections` | JSON | — | `[]` | 节次列表(如 `[1,2]` 表示第 1-2 节) | | `week_list` | JSON | 可为空 | NULL | 周次列表(如 `[2,3,7,8]`),为空表示全周次 | | `remark` | TEXT | 可为空 | — | 节次备注 | > `course_sessions.weekday` 在数据库内部按 `0-6 = 周一到周日` 存储,以对齐 Python `date.weekday()`;对外 API 序列化为字符串枚举 `MONDAY` 到 `SUNDAY`,不保留旧整数协议兼容。 > **多教室/多时段适配** > 同一课程若在周一、周三上课且教室/时段不同,可在 `course_sessions` 中为同一个 `course_id` 创建多条节次记录(分别设置不同 `weekday`、`classroom_id`、`start_time`/`end_time`)。这样一门课可拥有多条节次配置,不需要拆分课程基础信息。 ### 课程表生成逻辑 课表查询时,按日期范围筛选 `courses`(`start_date`~`end_date`),再按日期星期匹配 `course_sessions.weekday`,并按 `week_list` 过滤周次交集,将节次展开为具体日期的占用时段。 节次到时间的换算依赖系统“季节作息”配置: - 系统配置存于 `SystemConfig.season`(`winter`/`summer`)。 - 后端通过 `SeasonConfigService.get_timeslots()` 返回当前作息的节次时间表,并做缓存优化。 最终课表由: - `CourseSession` 展开的占用时段 - 已审批的临时借用记录(`BorrowApplication`,`status=approved` 且 `source_type=manual`) 共同组成,输出结构化时间块供前端渲染。 --- ## 课堂签到(Checkins) ### 开关定义 课堂签到为“可选功能”,任课教师可主动开启,系统将课程表中的节次信息拉取到 `CheckinSession` 表中,用于后续的签到与统计。 > 注:为避免夏/冬季时令变化导致时间计算偏差,`CheckinSession` 只保存节次(`sections`),不落地具体时间。 ### 核心数据表 #### 1. 课程签到配置 `course_checkin_settings` | 字段名 | 数据类型 | 说明 | |--------------|----------|----------------------| | `course_id` | INT | 对应 `courses.id`,单例配置 | | `enabled` | BOOLEAN | 是否开启签到 | | `enabled_by` | INT | 开启人 | | `enabled_at` | DATETIME | 开启时间 | | `created_at` | DATETIME | 创建时间 | | `updated_at` | DATETIME | 更新时间 | #### 2. 签到场次 `checkin_sessions` | 字段名 | 数据类型 | 说明 | |---------------------|----------|-------------------------------| | `course_session_id` | INT | 对应 `course_sessions.id` | | `course_id` | INT | 对应 `courses.id` | | `classroom_id` | INT | 对应 `classroom.id` | | `session_date` | DATE | 当日 | | `sections` | JSON | 签到节次(如 `[1,2]`) | | `status` | ENUM | `draft/open/closed/cancelled` | | `session_code` | UUID | 签到码(扫码/预留) | | `opened_at` | DATETIME | 开启时间 | | `closed_at` | DATETIME | 关闭时间 | | `created_by` | INT | 创建人 | | `remark` | TEXT | 备注 | #### 3. 签到记录 `checkin_records` | 字段名 | 数据类型 | 说明 | |----------------|----------|-----------------------------| | `session_id` | INT | 对应 `checkin_sessions.id` | | `student_id` | INT | 对应 `user.id` | | `status` | ENUM | `present/late/absent/leave` | | `checkin_time` | DATETIME | 签到时间 | | `method` | ENUM | `manual/qr/face` | | `source` | ENUM | `web/miniapp/signage/api` | | `remark` | TEXT | 备注 | | `confidence` | FLOAT | 人脸识别置信度(预留) | ### 业务规则(MVP) 1. 开启签到时,系统从 `course_sessions` 展开出课程方案,将各个上课日入库为 `checkin_sessions`。 2. 手动签到由任课教师在当天课程场次中对学生进行打卡,标记 `present/late/absent/leave`。 3. 人脸签到在以后扩展,现在仅做数据结构保留。