简介:「机房管理系统」数据库课程设计配套资料,面向计算机相关专业正在完成数据库课程设计的学生及入门开发者,解决从ER建模到SQL脚本实现缺少完整案例参照的问题,也可供教师作为课堂示例。包内共3个文件,含2份doc设计文档与1份sql脚本,压缩后约155KB;文档分别给出数据库设计说明书与系统详细设计,SQL脚本则提供建表、约束与基础数据等可执行语句,两者对照阅读即可还原完整设计流程。目前已有756人学习下载,适合作为课设选题的现成蓝本。内容围绕机房信息、设备信息、用户信息、预约信息与使用记录五类实体展开,读者可从中看到ER图的画法、关系模式向数据表的转换、1NF至3NF的规范化处理、索引与查询优化的取舍,以及增删改查语句的具体写法。整体篇幅精炼,便于短时间内理清课设的文档结构与技术路线,也能为答辩与后续修改提供参照。
1. 机房管理系统:先想清楚谁在用,再谈数据库怎么建
机房管理员最怕两件事:月底核对上机时长,Excel 里同一个学号能出现三种写法;某台机器坏了三天没人报修,等排课班级坐下去才发现。这两件事看着琐碎,落到系统上就是一套围绕人、设备、时间三条线运转的机房管理系统。它要解决的不是把纸质登记表搬进网页,而是让上机记录、设备状态、课程排期三者互相对得上,任何一条变动都能追溯。适合拿它做毕设或课设的人,通常已经学过数据库原理,想找一个业务闭环完整、又不至于大到做不完的题目。下面按需求、选型、数据库设计、模块实现、避坑、验收的顺序,把这套方案完整拆开,数据库设计部分给到可直接执行的建表语句。
2. 需求拆解与技术选型:四类角色与三条数据主线
2.1 四类角色决定权限模型
机房管理系统表面上是管机器,实际管的是「谁在什么时间占用了哪台机器」。一个中等规模的机房通常有 60~120 个机位,白天排满课程,晚上开放自由上机,周末还要承接考试和培训。围绕这个场景,至少要区分四类角色:学生和教师、机房管理员、设备维修员、系统管理员。学生只关心能不能约到机位、还剩多少机时;管理员关心今天的上座率和哪台机器该报修;维修员只看派给自己的工单;系统管理员管账号、角色和计费参数。
权限模型别一上来就设计五张表的 RBAC。课程设计周期有限,通常的做法是三张表搞定:用户表带角色字段、角色权限映射表、菜单表,接口层用装饰器校验。粒度控制到「菜单 + 操作」两级就够,再细到按钮级,工作量翻倍但答辩时讲不出更多内容。真正要留扩展位的是数据权限——管理员只能看自己负责的机房,这个用 room_id 归属过滤即可,不需要引入复杂的组织树。
功能需求按三条主线收拢。第一条是人机时,包括预约、签到、签退、计费、机时余额;第二条是设备,包括台账、状态、报修、巡检、耗材更换;第三条是教学,包括课程排课、班级占用、调课。三条线争夺的其实是同一个资源——「机位 × 时间段」,所以数据库里必须有一张表专门表达这种占用关系,它是整个设计的锚点,第 3 章会展开。
非功能需求写进开题报告能加分,也是答辩时老师最爱追问的地方。按单机房 120 机位、峰值每分钟 30 人签到估算并发;页面响应控制在 2 秒内;上机记录保留 3 年;断电后已签到未签退的记录必须支持人工补录。这几个数字不是拍脑袋,是后面选型和建索引的依据。
2.2 技术选型:单体 B/S 结构够用
技术栈按「一个人能维护、一台机器能跑起来」的标准选。前端 Vue 3 加 Element Plus,后端 Python Flask 或 FastAPI,数据库 MySQL 8.0,反向代理 Nginx。这套组合的部署路径很短:一台 4 核 8G 的服务器上,Nginx 负责转发和静态资源,Gunicorn 起 4 个 worker,MySQL 单独占内存,机房规模的并发完全压得住。
不上微服务不是偷懒,是这个场景的事务边界太清晰了。一次签到要同时写会话记录、改机位状态、扣机时余额,三张表在同一个事务里,单体应用一次 commit 就完事;拆成服务就得引入分布式事务或最终一致,出问题时的排查成本成倍上升。同理不用文档型数据库:排课冲突检测和计费结算都是典型的强一致场景,灵活模式在这里是负担。
数据库选 MySQL 还有个现实原因。机房管理系统是典型的教学题目,评委会看建表语句、索引和执行计划,这些内容在数据库课上都讲过,答辩时能讲清楚 B+ 树索引为什么这样建,比讲一堆中间件更有说服力。
项目目录按业务分层,不要按技术分层:
machine-room/ ├── app/ │ ├── models/ # 数据模型,一表一文件 │ ├── services/ # 业务逻辑,计费、冲突检测都在这层 │ ├── api/ # 路由层,只做参数校验和返回封装 │ ├── utils/ # 权限装饰器、日志、时间工具 │ └── config.py # 数据库、分页、计费单价等配置 ├── migrations/ # 建表与变更脚本,按版本号命名 ├── tests/ # 接口测试,至少覆盖签到、结算、报修 └── requirements.txt把所有路由塞一个目录、所有模型塞一个目录,项目小的时候看着整齐;等模块超过五个,改一个功能要在三个目录之间来回跳。上面这种按业务分层的写法,好处是 services 层可以被单元测试直接调用,不依赖 HTTP 请求,计费这种核心逻辑能单独测。
依赖清单也列一下,方便直接复现环境:
# requirements.txt Flask==3.0.3 Flask-SQLAlchemy==3.1.1 PyMySQL==1.1.1 cryptography==42.0.8 python-dotenv==1.0.1 APScheduler==3.10.4 # 定时任务:超时未签退自动补结算 pytest==8.2.2APScheduler 这个依赖最容易被忽略。机房晚上十点关门,总有学生忘记签退,第二天记录还挂着「进行中」。定时任务每 10 分钟扫一次超时会话,按闭馆时间自动结算并标记异常,比人工补录靠谱得多。这里要设一个阈值,比如超过闭馆时间 30 分钟仍未签退才触发,否则学生临时去接杯水都被判异常,投诉电话会打爆。
2.3 接口清单把前后端分工定死
开工前先把接口定下来,别一边写页面一边改字段。核心接口大概二十个,按资源分组即可:
| 分组 | 接口 | 方法 | 说明 |
|---|---|---|---|
| 认证 | /api/auth/login | POST | 返回 token 与角色 |
| 机位 | /api/seats/available | GET | 按机房、时间段查空闲机位 |
| 上机 | /api/sessions/checkin | POST | 签到,返回会话 ID |
| 上机 | /api/sessions/{id}/checkout | POST | 签退并结算 |
| 排课 | /api/schedules | POST | 新增排课,内含冲突检测 |
| 报修 | /api/repairs | POST | 提交报修单 |
| 报修 | /api/repairs/{id}/transition | PUT | 状态流转 |
| 统计 | /api/stats/daily | GET | 按日读取汇总数据 |
接口约定三条规矩:统一返回{code, message, data}三段结构;分页参数固定page和page_size,默认 20;所有时间字段用YYYY-MM-DD HH:mm:ss字符串传输,前后端都不做时区转换。最后一条是血泪经验,一旦前端按本地时区转、后端按 UTC 存,结算出来的时长能差 8 小时,排查半天才发现是新日期对象解析惹的祸。
提示:接口文档用 Markdown 写在仓库 docs 目录里,和代码一起提交,答辩时直接翻给老师看,比临时开网页演示更稳。
3. 数据库设计:七张表撑起人、机、时三条线
3.1 核心表结构与字段取舍
主表定七张:用户表、机房表、机位表、排课表、上机会话表、报修单表、操作日志表,另外加一张计费规则表作为参数配置。
用户表存 username、real_name、role、student_no、phone、credit_score、status。real_name 在列表页要做脱敏展示,手机号中间四位打码,这些在序列化层处理,不要存两份。credit_score 是违约扣分,预约未到场扣分,扣到阈值以下禁止预约一周,这是机房管理里非常实用的一条规则。
机房表存 room_name、building、seat_count、open_time、close_time、status。seat_count 是冗余字段,机位增删时同步更新,查询列表时不用每次都 count 子表。
机位表存 room_id、seat_no、ip、mac、hardware_sn、status、is_deleted,并对 (room_id, seat_no) 建唯一约束。同一个机房不允许出现两个 A-023,这条约束能挡掉大批手工导入的脏数据。
排课表存 room_id、teacher_id、course_name、weekday、start_slot、end_slot、start_date、end_date、class_name。用「周几 + 第几节」而不是具体日期,是因为课程表按周循环,存日期会让一学期的课变成几百条记录。
上机会话表是整个系统的核心,存 seat_id、user_id、checkin_time、checkout_time、duration_min、fee、status、source。这里有几个字段取舍值得说:金额用 DECIMAL(10,2) 不用 FLOAT,浮点数算钱迟早出问题;时长存分钟整数,不存「2 小时 15 分」这种字符串,统计时没法直接求和;状态用 TINYINT 加代码里的枚举常量,不用数据库 ENUM 类型,因为 ENUM 增加取值要 ALTER TABLE,线上锁表。
删除策略统一软删除,is_deleted 加 deleted_at 两个字段。日志表不建外键,其他核心表保留外键并设置ON DELETE RESTRICT。这个折中方案在答辩时好解释:日志是流水,不应该被业务表约束;核心数据要防误删,让数据库兜底。
3.2 设备状态机与会话生命周期
机位状态定义五个值:0 空闲、1 使用中、2 维修中、3 报废、4 锁定。锁定状态专门给考试用,管理员可以把整间机房锁住,学生端看不到可预约机位。状态迁移必须走服务层的统一函数,不能在各个接口里直接 update。我见过一个项目,签退接口把机位置空闲,报修接口也置空闲,结果一台正在维修的机器被人签退操作改成了空闲,第二天排课直接派了学生上去。
上机会话的状态更复杂,定义六个值:0 已预约、1 已签到、2 已签退、3 已结算、4 异常、5 已取消。合法迁移关系如下:
| 当前状态 | 允许迁移到 | 触发动作 |
|---|---|---|
| 0 已预约 | 1 已签到 | 学生签到 |
| 0 已预约 | 5 已取消 | 超过预约时间 15 分钟未到 |
| 1 已签到 | 2 已签退 | 学生签退 |
| 1 已签到 | 4 异常 | 定时任务判定超时 |
| 2 已签退 | 3 已结算 | 结算任务或管理员确认 |
| 4 异常 | 3 已结算 | 管理员补录实际离开时间 |
状态迁移写在一张 Python 字典里,服务层统一校验,前端可以直接拉这份配置渲染按钮。非法迁移一律抛业务异常,不允许「静默跳过」。这条规则看着死板,但它保证了一个性质:任何一条已结算的记录,其金额和时长都能被复算出来。
3.3 建表语句与索引设计
先看机位表和会话表,这两张表决定了系统的读写比例。
CREATE TABLE `seat` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `room_id` INT UNSIGNED NOT NULL COMMENT '所属机房', `seat_no` VARCHAR(16) NOT NULL COMMENT '机位号,如 A-023', `ip` VARCHAR(32) DEFAULT NULL COMMENT '终端 IP,用于自动签到', `hardware_sn` VARCHAR(64) DEFAULT NULL COMMENT '设备序列号', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0空闲 1使用中 2维修中 3报废 4锁定', `is_deleted` TINYINT NOT NULL DEFAULT 0, `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_room_seat` (`room_id`, `seat_no`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='机位设备表'; CREATE TABLE `use_session` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `seat_id` BIGINT UNSIGNED NOT NULL, `user_id` BIGINT UNSIGNED NOT NULL, `checkin_time` DATETIME NOT NULL, `checkout_time` DATETIME DEFAULT NULL, `duration_min` INT DEFAULT NULL COMMENT '结算分钟数,向上取整', `fee` DECIMAL(10,2) DEFAULT NULL COMMENT '金额,元', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '0预约 1签到 2签退 3结算 4异常 5取消', `source` TINYINT NOT NULL DEFAULT 1 COMMENT '1自由上机 2预约 3课程', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_seat_time` (`seat_id`, `checkin_time`), KEY `idx_user_time` (`user_id`, `checkin_time`), KEY `idx_status_checkout` (`status`, `checkout_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='上机会话表';索引不是越多越好,这三个联合索引各自对应一个明确的查询:idx_seat_time支撑「某机位今天有没有被占」,签到前的可用性判断走这条;idx_user_time支撑「我的上机记录」按时间倒序分页,个人中心页面的主查询;idx_status_checkout给定时任务用,扫 status=1 且签到时间过久的记录,前缀区分度高,扫描范围小。
排课表的冲突检测没法靠唯一索引解决,因为「区间重叠」不是等值匹配。通常的做法是给 (room_id, weekday) 建普通索引,把同一机房同一天的课先捞出来,再在应用层做区间判断,插入前用SELECT ... FOR UPDATE锁住机房行,防止两个管理员同时排课。这个锁的粒度是机房级,不同机房互不影响,比表锁合理得多。
CREATE TABLE `course_schedule` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `room_id` INT UNSIGNED NOT NULL, `teacher_id` BIGINT UNSIGNED NOT NULL, `course_name` VARCHAR(64) NOT NULL, `class_name` VARCHAR(64) NOT NULL COMMENT '上课班级', `weekday` TINYINT NOT NULL COMMENT '1-7,周一到周日', `start_slot` TINYINT NOT NULL COMMENT '第几节开始,1-12', `end_slot` TINYINT NOT NULL COMMENT '第几节结束,含', `start_date` DATE NOT NULL COMMENT '学期起始周', `end_date` DATE NOT NULL, PRIMARY KEY (`id`), KEY `idx_room_weekday` (`room_id`, `weekday`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程排课表';end_slot采用闭区间,第 3 节到第 5 节表示连上三节。判断冲突时用start_slot <= 5 AND end_slot >= 3,而不是简单的等于比较。这一点在答辩时经常被问,因为很多人只考虑「开始节次相同」这一种冲突。
4. 模块实现:签到计费与报修流转的代码落地
4.1 签到:先写对冲突检测,再谈并发
签到接口要处理三个问题:机位是否可用、用户是否已有进行中的会话、两个请求同时抢同一台机器。第三个问题最隐蔽,单机测试永远发现不了,一上多人演示就露馅。
先看区间冲突检测,这个函数在排课和预约里都要用:
def has_conflict(room_id, start_dt, end_dt, exclude_id=None): """判断机房在 [start_dt, end_dt) 内是否已被课程占用。 重叠条件:已有.start < 新.end 且 已有.end > 新.start。 写成 >= / <= 会把首尾相接的两节课误判为冲突。""" q = CourseSchedule.query.filter( CourseSchedule.room_id == room_id, CourseSchedule.start_dt < end_dt, CourseSchedule.end_dt > start_dt, ) if exclude_id: q = q.filter(CourseSchedule.id != exclude_id) return q.first() is not None函数返回布尔值,调用方只关心有没有冲突。exclude_id参数用于编辑场景,修改一节已经存在的课时,不能把它自己算成冲突。四个比较符号的顺序不能调换,这是区间判断的标准写法,换成between就只覆盖了「新段完全落在旧段内部」这一种情况。
签到接口的完整逻辑如下:
@app.post("/api/sessions/checkin") def checkin(): data = request.get_json() seat_id = data["seat_id"] user_id = g.current_user_id # 1. 事务内锁机位行,锁的是主键,不同机位互不阻塞 seat = db.session.execute( select(Seat).where(Seat.id == seat_id).with_for_update() ).scalar_one_or_none() if seat is None or seat.is_deleted: return fail("机位不存在") if seat.status != SeatStatus.IDLE: return fail("该机位当前不可用") # 2. 同一用户不能有两条进行中的会话 running = UseSession.query.filter_by( user_id=user_id, status=SessionStatus.CHECKED_IN ).first() if running: return fail(f"你已在 {running.seat.seat_no} 上机,请先签退") # 3. 会话与机位状态在同一事务提交 sess = UseSession( seat_id=seat_id, user_id=user_id, checkin_time=datetime.now(), status=SessionStatus.CHECKED_IN, source=data.get("source", 1), ) seat.status = SeatStatus.IN_USE db.session.add(sess) db.session.commit() return ok({"session_id": sess.id})with_for_update()是关键,它给选中的机位行加排他锁,第二个并发请求会阻塞到第一个事务提交。锁的顺序也要注意:先锁机位、再查用户会话,如果反过来,两个用户互相锁对方正在用的机位,就可能死锁。参数source区分自由上机和课程上机,统计上座率时口径不同,不能混在一起算。
注意:
with_for_update必须在事务中生效,Flask-SQLAlchemy 默认自动提交模式下要显式开启 session 事务,否则锁加了个寂寞。
4.2 计费:用 Decimal 和分钟整数
计费规则按机房可配置,先写一个默认策略:前 30 分钟免费,超出按每分钟 0.10 元,单日封顶 20 元。结算函数独立放在 services 层,不依赖请求上下文,方便写单元测试。
from decimal import Decimal, ROUND_HALF_UP import math RATE_PER_MIN = Decimal("0.10") FREE_MIN = 30 DAILY_CAP = Decimal("20.00") def settle(session, rate=RATE_PER_MIN): """结算一次上机会话,返回 (fee, duration_min)。 分钟数向上取整,不足一分钟按一分钟计,防止卡点签退。""" end = session.checkout_time or datetime.now() if end <= session.checkin_time: raise BizError("签退时间早于签到时间,请检查数据") seconds = (end - session.checkin_time).total_seconds() duration_min = max(1, math.ceil(seconds / 60)) billable = max(0, duration_min - FREE_MIN) fee = (Decimal(billable) * rate).quantize( Decimal("0.01"), rounding=ROUND_HALF_UP ) fee = min(fee, DAILY_CAP) return fee, duration_min三个细节值得展开。第一,end <= checkin_time直接抛异常,不做兜底修正,因为这种数据一旦出现,说明前端或导入环节有问题,静默改成 0 元会把问题藏起来,后面查账根本对不上。第二,math.ceil先算秒再转分钟,而不是用//整除,整除会把 61 秒算成 1 分钟,等于白送。第三,封顶判断放在 quantize 之后,保证入库的金额和展示的金额是同一个数,不会出现「页面显示 20.00、库里存 20.0049」这种对账事故。
日封顶还需要查当天已结算金额,简单做法是在 fee 计算前查一次当日累计,复杂做法是维护一张日汇总表。毕设规模用前者足够,但要在注释里写清楚这是简化实现,答辩时主动说明,比被老师问出来强。
4.3 报修单流转:用迁移表挡住非法操作
报修流程是 pending → assigned → repairing → done → closed,中间任何一步都可以直接关闭。把这套规则写死在 if-else 里,前端加一个按钮就要改一次后端,很容易漏。更稳的写法是把迁移关系抽成配置:
REPAIR_TRANSITIONS = { RepairStatus.PENDING: {RepairStatus.ASSIGNED, RepairStatus.CLOSED}, RepairStatus.ASSIGNED: {RepairStatus.REPAIRING, RepairStatus.CLOSED}, RepairStatus.REPAIRING: {RepairStatus.DONE, RepairStatus.CLOSED}, RepairStatus.DONE: {RepairStatus.CLOSED}, } def transition(order_id, target, operator_id, remark=None): order = RepairOrder.query.get_or_404(order_id) allowed = REPAIR_TRANSITIONS.get(order.status, set()) if target not in allowed: raise BizError(f"报修单不能从 {order.status} 变为 {target}") order.status = target if target == RepairStatus.DONE: # 维修完成,机位从维修中恢复空闲 order.seat.status = SeatStatus.IDLE if target == RepairStatus.CLOSED: order.finished_at = datetime.now() order.remark = remark db.session.commit()迁移表的好处是前端可以直接拉这份配置渲染可用按钮,后端只做一次校验,前后端不会各写一套规则然后对不上。机位状态的联动只放在DONE这一处,提交报修时就把机位置成维修中,这样排课和预约都能立刻感知到某台机器不可用,避免「已报修但还能被排课」的尴尬。
4.4 权限装饰器与操作日志
权限校验写装饰器,避免每个接口开头都复制一段角色判断:
def require_role(*roles): """接口级角色校验,roles 为空表示登录即可访问""" def wrapper(fn): @wraps(fn) def inner(*args, **kwargs): if g.role not in roles: return fail("无权限访问该资源", 403) return fn(*args, **kwargs) return inner return wrapper @app.before_request def attach_user(): # 从 token 解析用户与角色,挂到请求上下文 g.current_user_id, g.role = parse_token(request.headers.get("Authorization"))装饰器只做粗粒度控制,细粒度的数据归属(管理员只能看自己机房)放在 service 层用 room_id 过滤。日志用after_request统一写,记录操作人、接口路径、目标对象、IP 和耗时,写入失败要 try 住,不能因为日志表满了就导致业务接口 500。这一条在演示时很关键,老师如果现场让你断网测试,日志模块不能成为新的故障点。
5. 常见问题排查:五个最容易翻车的地方
5.1 排课冲突漏判,两门课排进同一机房
现象:周三下午第一节和第二节的课能同时排进 A 机房,冲突检测接口返回「无冲突」。原因:判断写成了start_slot BETWEEN old_start AND old_end,只覆盖新课程开始节次落在旧课程区间内的情况,如果新课程把旧课程整个包住,或者两节课首尾相接,就全部漏判。解决:统一改成old_start <= new_end AND old_end >= new_start,并且在插入前对整个机房加行锁,避免两个管理员同时提交时都查不到对方。
5.2 结算金额出现一串小数或负数
现象:报表里金额显示 0.30000000000000004,偶尔还有 -2.00。原因:金额字段用了 FLOAT,Python 里又混用整数和浮点;负数则是签退时间早于签到时间造成的,常见于测试时手改数据或前端传了错误的本地时间。解决:字段改 DECIMAL(10,2),Python 全程用 Decimal 并显式 quantize;结算入口先校验时间先后,不满足直接抛业务异常并记一条日志,让问题暴露在源头而不是藏在报表里。
5.3 同一个学生同时挂在两台机器上
现象:上座率报表显示某学生同时在 A-01 和 B-07 上机。原因:签到接口没有幂等控制,用户连点两下或者网络超时重试就多插一条;应用层的「查有没有进行中会话」在并发下两个请求都查到空,然后各插一条。解决:三层一起上——前端按钮点击后立即禁用;后端在事务里对用户行加SELECT ... FOR UPDATE;数据库加一个兜底查询任务,每天凌晨扫出重复会话并标记异常,留痕而不是直接删。
5.4 删除机位导致历史记录断链
现象:管理员删掉一台报废机器后,上机记录列表里那一行显示空白,导出报表直接报错。原因:用了物理删除,会话表里的 seat_id 变成孤儿;如果加了外键,删除直接失败,管理员就改用改状态糊弄,数据照样乱。解决:统一软删除,查询默认过滤 is_deleted=1,报表用 LEFT JOIN 并对空值兜底显示「已删除机位」。外键保留但设ON DELETE RESTRICT,从数据库层面挡住误删,管理员只能走「报废」状态。
5.5 日报页面越用越慢
现象:刚上线时统计页秒开,数据到十几万行后要等七八秒,晚高峰更慢。原因:每次打开都从会话明细表全量COUNT和SUM,没有任何预聚合;统计口径还散落在多个控制器里,同一个指标两个页面算出来不一样。解决:建一张日汇总表,定时任务每天凌晨按机房、按小时聚合,页面查询优先读汇总表;同时限制查询范围,默认只查近 7 天,跨月统计走异步导出。口径统一收进一个 service 函数,谁调都一样。
6. 验收与答辩:三条链路跑通,比十页 PPT 管用
验收不要按页面挨个点,按链路跑。准备一份演示数据:三个机房、120 个机位、两门排课、一条报修单。然后走三条主链路,每条都能从入口走到出口,中途不切库、不改数据。
| 验收项 | 验收方法 | 通过标准 |
|---|---|---|
| 签到结算链路 | 学生签到 → 等待 1 分钟 → 签退 | 生成会话记录,时长与金额可复算 |
| 冲突检测 | 同一机房同一时段连排两门课 | 第二次提交被拒绝并提示冲突时段 |
| 报修闭环 | 提交报修 → 派单 → 维修 → 关闭 | 机位状态随流转自动变化 |
| 超时兜底 | 手工造一条昨天未签退的记录 | 定时任务扫描后标记异常并可补录 |
| 权限隔离 | 用管理员账号访问非所属机房 | 返回 403 或查不到数据 |
答辩时老师最常问三类问题,提前准备好答案。第一类是索引,「这条 SQL 走了哪个索引」,答不上来说明建表语句是抄的;第二类是并发,「两个人同时签到会怎样」,把行锁和事务顺序讲清楚就够;第三类是异常,「学生忘记签退怎么办」,答定时任务加人工补录,并且说明为什么不能自动按闭馆时间一刀切——有人加班做实验到十一点,一刀切会算出天价账单。
现场演示有两个技巧。一是提前把数据库切到演示库,别用积累了半年的测试数据,列表第一页全是脏数据很难看。二是故意演示一次失败:提交一个冲突的排课,让系统弹出提示。很多同学只演示成功路径,老师反而会怀疑你是硬编码的。
我一般会在答辩前一天晚上,自己拿手机计时把三条链路各走一遍,掐着表控制在 8 分钟内,超时就砍演示项。这个习惯来自一次翻车:现场投影分辨率不对,页面按钮全被挡住,只能靠键盘 Tab 切,结果发现某个弹窗的确认按钮根本不在 Tab 顺序里。从那以后我每次演示前都强制走一遍「只靠键盘能不能完成主流程」。希望这套结构和清单能帮到你,把该讲的地方讲透,剩下的就是熟练。
本文还有配套的精品资源,点击获取