基于Node.js与Express的宿舍分配系统:JWT鉴权与床位锁定实践

发布时间:2026/10/23 22:11:57

基于Node.js与Express的宿舍分配系统:JWT鉴权与床位锁定实践 简介Hostel-Allocation 是一套面向高校宿舍场景的全栈 Web 预订系统源码适合具备 Node.js 与前端基础、希望练习完整项目开发的在校学生或初级开发者。它解决学生线下排队订房、管理员人工分配效率低的问题学生可远程查看并预订房间管理员能实时掌握入住情况减少人员聚集与接触。资源包共 22 个文件约 34KB以 11 个 js 脚本、4 个 html 页面为主辅以 json 配置、css 样式及证书密钥文件覆盖登录注册、房间管理、预订页面与后端路由等模块。技术栈融合 Express.js 与 Sequelize 操作 MySQL前端采用 jQuery、HTML5、CSS3 与 Bootstrap4 构建响应式界面并通过 JWT 实现身份认证前后端逻辑划分清晰。已有 114 人学习读者可借此理解路由设计、ORM 建模、鉴权流程与页面交互的完整实现适合作为课程设计或全栈练手参考。1. 从一张排队长龙照片说起宿舍分配系统到底该解决什么问题每年开学季宿舍楼下总会出现一条蜿蜒的队伍。学生攥着纸质申请表宿管阿姨翻着厚厚的登记本一个名字一个名字地找空床位。这个场景在不少高校依然存在而它带来的问题远不止“排队累”——人群聚集、信息不透明、分配结果靠手写记录容易出错后续查寝、调宿、退宿全是一笔糊涂账。这套 Hostel-Allocation 方案要解决的就是把“人找人、纸对纸”的流程搬到线上。学生远程登录就能看到哪些楼栋、哪些楼层还有空位提交预订后系统自动锁定床位管理员在后台能一眼看清谁住在哪个房间、哪个床位调宿退宿都有记录可查。技术栈选的是 Node.js Express MySQL Sequelize JWT前端用 jQuery 配合 Bootstrap4 和 HTML5/CSS3没有上 React 或 Vue原因后面会讲。适合正在做 Web 开发方向毕设或课程设计的同学也适合需要快速交付一套内部管理系统的开发者参考。2. 需求拆解与技术选型为什么用 Express Sequelize 而不是全家桶2.1 三类角色的核心诉求先把角色分清楚后面所有设计都围绕这三类人展开。学生端最关心的是“我能不能订到”“我订的是哪个床位”“能不能改”。具体功能包括注册登录、查看宿舍楼与房间列表、查看床位占用状态、提交预订、查看自己的预订记录、申请调宿或退宿。管理员端最关心的是“谁住在哪”“还有多少空位”“怎么快速调整”。功能包括登录后台、查看所有楼栋房间的入住情况、按楼栋/楼层/房间号筛选、手动分配床位、审批调宿申请、导出住宿名单。系统本身要保证的是床位不能被重复预订、用户只能操作自己的订单、管理员操作要有日志可追溯。这三条是后面数据库设计和接口鉴权的出发点。2.2 技术选型的取舍逻辑很多同学做毕设喜欢直接上 Vue Element UI Spring Boot前后端分离看起来很“正规”但实际开发周期会拉长至少一倍。这套方案选 jQuery Bootstrap4 的原因很直接页面交互复杂度不高主要是表格渲染、表单提交、状态切换jQuery 的 DOM 操作和 AJAX 足够用Bootstrap4 的栅格和组件能快速搭出可用的管理界面。省下来的时间可以花在业务逻辑和边界条件上而不是折腾构建工具和状态管理。后端选 Express 而不是 Koa 或 NestJS是因为 Express 的中间件生态最成熟JWT 鉴权、请求校验、错误处理都有现成的中间件可以直接接入。Sequelize 作为 ORM 负责 MySQL 操作好处是模型定义清晰、关联查询方便、迁移脚本可版本化。对于宿舍分配这种关系型数据密集的场景用 ORM 比手写 SQL 更不容易在关联查询上翻车。提示如果导师明确要求前后端分离或使用特定框架优先满足导师要求。这套方案的价值在于业务逻辑和数据库设计技术栈可以替换。2.3 数据库表结构设计核心表一共五张关系不复杂但约束要写清楚。-- 用户表区分学生和管理员 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, role ENUM(student, admin) DEFAULT student, real_name VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 楼栋表 CREATE TABLE buildings ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, gender ENUM(male, female) NOT NULL, floors INT NOT NULL ); -- 房间表 CREATE TABLE rooms ( id INT PRIMARY KEY AUTO_INCREMENT, building_id INT NOT NULL, room_number VARCHAR(10) NOT NULL, floor INT NOT NULL, capacity INT DEFAULT 4, FOREIGN KEY (building_id) REFERENCES buildings(id) ); -- 床位表这是分配的最小单位 CREATE TABLE beds ( id INT PRIMARY KEY AUTO_INCREMENT, room_id INT NOT NULL, bed_number VARCHAR(5) NOT NULL, status ENUM(available, occupied, maintenance) DEFAULT available, FOREIGN KEY (room_id) REFERENCES rooms(id) ); -- 预订记录表 CREATE TABLE allocations ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, bed_id INT NOT NULL, status ENUM(active, checked_out, transferred) DEFAULT active, allocated_at DATETIME DEFAULT CURRENT_TIMESTAMP, released_at DATETIME, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (bed_id) REFERENCES beds(id) );床位表单独拆出来而不是在房间表里用数字表示剩余床位是为了让“一个床位一条记录”成为硬约束。预订时直接对 beds 表的 status 字段做更新配合数据库事务就能防止并发重复预订。allocations 表保留历史记录退宿或调宿时不删数据只改状态和释放时间这样后续查历史有据可依。3. 后端接口与鉴权实现JWT 中间件和床位锁定逻辑3.1 JWT 鉴权中间件的写法登录成功后签发 token后续请求在 Header 里带上Authorization: Bearer token。中间件负责校验 token 并把用户信息挂到req.user上。// middleware/auth.js const jwt require(jsonwebtoken); const SECRET process.env.JWT_SECRET || hostel-secret-key; // 校验 token解析出用户信息 function authenticate(req, res, next) { const header req.headers.authorization; if (!header || !header.startsWith(Bearer )) { return res.status(401).json({ message: 未登录或 token 缺失 }); } const token header.slice(7); try { const payload jwt.verify(token, SECRET); req.user { id: payload.id, role: payload.role }; next(); } catch (err) { return res.status(401).json({ message: token 无效或已过期 }); } } // 角色校验管理员接口专用 function requireAdmin(req, res, next) { if (req.user.role ! admin) { return res.status(403).json({ message: 无权限执行此操作 }); } next(); } module.exports { authenticate, requireAdmin };authenticate只负责“你是谁”requireAdmin负责“你能不能干这个”。两个中间件分开写的好处是学生接口只挂authenticate管理员接口挂authenticate requireAdmin职责清晰。token 过期时间建议设 2 小时太长不安全太短学生填个表就掉线。3.2 床位预订的事务处理这是整个系统最容易出 bug 的地方。两个学生同时点同一个床位如果不加锁就会超卖。// routes/allocation.js const { sequelize, Bed, Allocation } require(../models); router.post(/allocate, authenticate, async (req, res) { const { bedId } req.body; const t await sequelize.transaction(); try { // 锁定该床位行防止并发读取到相同状态 const bed await Bed.findByPk(bedId, { lock: t.LOCK.UPDATE, transaction: t }); if (!bed || bed.status ! available) { await t.rollback(); return res.status(400).json({ message: 该床位已被占用 }); } // 检查该学生是否已有 active 预订 const existing await Allocation.findOne({ where: { user_id: req.user.id, status: active }, transaction: t }); if (existing) { await t.rollback(); return res.status(400).json({ message: 你已有预订请先退宿或调宿 }); } // 更新床位状态并写入分配记录 await bed.update({ status: occupied }, { transaction: t }); await Allocation.create({ user_id: req.user.id, bed_id: bedId, status: active }, { transaction: t }); await t.commit(); res.json({ message: 预订成功 }); } catch (err) { await t.rollback(); res.status(500).json({ message: 预订失败请重试 }); } });关键在lock: t.LOCK.UPDATE这一行它会对该床位行加排他锁第二个请求必须等第一个事务提交后才能读取。配合事务的原子性要么床位状态和分配记录同时写入要么都不写。existing检查是防止一个学生占多个床位这个约束在业务上必须卡死。3.3 管理员查看入住情况的关联查询管理员需要一张表看到“楼栋-房间-床位-学生”的完整链路用 Sequelize 的 include 做嵌套关联。// routes/admin.js router.get(/occupancy, authenticate, requireAdmin, async (req, res) { const beds await Bed.findAll({ include: [{ model: Room, include: [{ model: Building, attributes: [name, gender] }] }], order: [[room_id, ASC], [bed_number, ASC]] }); // 查出所有 active 分配记录按 bed_id 映射学生信息 const allocations await Allocation.findAll({ where: { status: active }, include: [{ model: User, attributes: [real_name, username] }] }); const allocMap {}; allocations.forEach(a { allocMap[a.bed_id] a.User; }); const result beds.map(bed ({ building: bed.Room.Building.name, room: bed.Room.room_number, bed: bed.bed_number, status: bed.status, student: allocMap[bed.id] ? allocMap[bed.id].real_name : null })); res.json(result); });先查床位再查分配记录在内存里做映射比在 SQL 里写多层 LEFT JOIN 更直观也方便前端直接渲染表格。数据量大的时候可以加分页但一般宿舍楼也就几百个床位一次性返回没问题。4. 前端页面与交互jQuery 表格渲染和表单校验4.1 学生端床位选择页学生登录后看到的是按楼栋分组的床位卡片可用床位显示为绿色可点击已占用显示为灰色。// public/js/student.js function loadBeds(buildingId) { $.ajax({ url: /api/beds?buildingId buildingId, headers: { Authorization: Bearer localStorage.getItem(token) }, success: function (beds) { var html ; beds.forEach(function (bed) { var cls bed.status available ? bed-card available : bed-card occupied; var disabled bed.status available ? : disabled; html div classcol-md-3 div class cls p房间 bed.roomNumber /p p床位 bed.bedNumber /p button classbtn btn-sm btn-primary disabled onclickallocate( bed.id )预订/button /div/div; }); $(#bed-list).html(html); }, error: function (xhr) { if (xhr.status 401) { window.location.href /login.html; } } }); } function allocate(bedId) { if (!confirm(确认预订该床位)) return; $.ajax({ url: /api/allocate, method: POST, contentType: application/json, headers: { Authorization: Bearer localStorage.getItem(token) }, data: JSON.stringify({ bedId: bedId }), success: function () { alert(预订成功); loadBeds(currentBuilding); }, error: function (xhr) { alert(xhr.responseJSON.message); } }); }用 jQuery 的$.ajax统一处理 token 注入和 401 跳转比在每个请求里手写 fetch 更省事。Bootstrap4 的col-md-3让床位卡片自适应排列available和occupied两个 class 控制颜色和禁用状态。confirm弹窗虽然简陋但能防止误触答辩时演示也够用。4.2 管理员端入住总览表格管理员页面用 Bootstrap4 的 striped table 展示所有床位状态支持按楼栋筛选。// public/js/admin.js function loadOccupancy() { $.ajax({ url: /api/admin/occupancy, headers: { Authorization: Bearer localStorage.getItem(token) }, success: function (rows) { var html ; rows.forEach(function (r) { var badge r.status occupied ? span classbadge badge-danger已入住/span : span classbadge badge-success空闲/span; html trtd r.building /tdtd r.room /tdtd r.bed /tdtd badge /tdtd (r.student || —) /td/tr; }); $(#occupancy-table tbody).html(html); } }); }表格只做展示调宿和退宿操作通过行内按钮触发单独的接口。管理员页面不需要花哨的交互信息密度和可读性最重要。Bootstrap4 的 badge 组件用来区分状态比纯文字更直观。5. 避坑与常见问题从重复预订到 token 过期5.1 床位超卖并发请求下的重复分配现象是同一个床位被两个学生同时预订成功数据库里出现两条 active 记录。原因是没有加行锁两个请求同时读到status available。解决方法是像 3.2 节那样在事务里用LOCK.UPDATE锁定床位行第二个请求会阻塞到第一个事务提交再读到的就是occupied。如果用的是 MySQL 默认的 REPEATABLE READ 隔离级别不加锁的普通 SELECT 读的是快照更容易出问题。5.2 token 过期后页面无响应现象是学生填完表单点提交页面卡住没反应控制台报 401。原因是 token 过期后接口返回 401但前端没有统一处理。解决方法是在$.ajaxSetup里加全局的 error 回调遇到 401 就清除本地 token 并跳转登录页。不要在每个请求里单独写容易漏。$.ajaxSetup({ error: function (xhr) { if (xhr.status 401) { localStorage.removeItem(token); window.location.href /login.html; } } });5.3 Sequelize 关联查询字段名对不上现象是管理员页面学生姓名列全是 undefined。原因是include里的模型别名和实际返回的对象键不一致。Sequelize 默认用模型名作为键如果定义了as别名就要用别名取。排查方法是先console.log(JSON.stringify(result, null, 2))看实际结构再决定用a.User还是a.user。这个坑几乎每个用 Sequelize 的人都会踩一次。5.4 密码明文存储现象是数据库里能看到用户密码原文。原因是注册接口直接存了req.body.password。解决方法是用 bcrypt 做哈希注册时bcrypt.hash(password, 10)登录时bcrypt.compare(password, hash)。这个不是可选项是必须做的。答辩时如果被问到安全性密码哈希是基本要求。5.5 前端直接暴露管理员接口现象是学生用浏览器控制台直接调/api/admin/occupancy也能拿到数据。原因是管理员接口只挂了authenticate没挂requireAdmin。解决方法是在所有/api/admin/*路由上统一加requireAdmin中间件或者在路由注册时用router.use(authenticate, requireAdmin)批量挂载。鉴权要放在后端做前端隐藏按钮只是障眼法。6. 验收与答辩怎么证明这套系统真的能用验收不是把页面点一遍就完事。我一般会按下面这张清单走一遍每项都要有明确的通过标准。验收项操作步骤通过标准注册登录新用户注册后登录token 正常签发跳转对应角色首页床位展示学生端查看楼栋床位可用/已占用状态正确颜色区分明显预订锁定两个浏览器同时预订同一床位只有一个成功另一个提示已占用重复预订同一学生再次预订提示已有预订不允许占多个床位管理员总览管理员查看入住表楼栋、房间、床位、学生姓名完整对应调宿退宿学生申请退宿后管理员查看床位状态变回 available历史记录保留权限隔离学生 token 调管理员接口返回 403不泄露数据密码安全查看数据库 users 表password_hash 为 bcrypt 哈希值答辩时老师最常问的三个问题床位并发怎么处理的、token 怎么防伪造、数据库表之间什么关系。前两个在 3.1 和 3.2 里有现成答案第三个把 2.3 的建表语句讲清楚就行。如果老师问“为什么不用 Vue”就按 2.2 的取舍逻辑回答交互复杂度低jQuery 够用省下的时间花在业务约束上。还有一个小技巧答辩前把数据库清空现场从注册开始演示一遍完整流程。比对着已有数据念页面更有说服力也能证明系统不是“只能看不能跑”的花架子。从那以后我每次做完这类管理系统都会在交付前强制走一遍并发测试和权限越权测试这两项过了基本就不会在答辩现场翻车。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/23 22:06:56

不用写繁琐前端!用 Dify / Coze 配合微信 API 极简构建微信 AI 助手

想快速让微信上有个能聊的 AI 助手,自己搭一套对话管理、prompt 配置、知识库太折腾。Dify 和 Coze 这类平台把这些都做好了,你只需要做两件事:把微信消息转成它们认识的格式、把它们的回复发回微信。一、桥接模式:微信 API 是接入…

2026/10/23 22:06:56

DeepSeek + 微信 API 实战:从零搭建 24 小时运维机器人

"运维机器人"听起来是个新东西——其实就是把 DeepSeek 的对话能力和微信的消息接口接起来,让运维人员不用半夜爬起来看服务器。但不是直接接个 API 就完了,要解决三个问题:哪些运维命令能让机器人执行、哪些不能;机器人…

2026/10/23 23:07:09

C语言+EasyX像素鸟源码拆解:主循环、碰撞与双缓冲

简介:面向C语言初学者与游戏开发入门者的Flappy Bird像素鸟完整源码,基于easyX图形库实现,可用于学习图形绘制、双缓冲防闪屏、飞行物理模拟、管道碰撞检测及游戏状态管理等核心环节。压缩包共49个文件,约17.09MB,包含…

2026/10/23 23:07:09

Winform 桌面端 HTTP POST JSON 请求封装与避坑实践

简介:面向Winform开发者的HTTP POST提交JSON与接收返回结果示例程序,聚焦桌面端与Web接口之间的数据交互场景,适合具备C#基础的初中级开发者学习。内容围绕HttpClient发起异步POST请求、使用Json.NET完成对象序列化与响应解析,并处…

2026/10/23 23:02:08

《小王子》第六章的叙事密码与情感疗愈机制

1. 项目概述:解读《小王子》第六章的深层意蕴《小王子》第六章是全书情感浓度最高的章节之一,用短短千余字构建了关于"仪式感"的永恒寓言。这一章记录了小王子与飞行员在沙漠中的第四次对话,通过"四十四次日落"的经典场景…

2026/10/23 22:56:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/23 0:02:26

Verilog实现FPGA密码锁:矩阵键盘与状态机设计实战

说实话,这几年接触了不少想入门FPGA的朋友,如果你问哪个项目能一口气把Verilog的核心基本功串起来,我通常会推荐电子密码锁。这个项目看着不起眼,却把矩阵键盘扫描、按键消抖、状态机设计、动态显示刷新、外设控制这些数字逻辑里的…

2026/10/23 0:02:26

数据服务监控运维实战:从指标体系到告警治理与故障排查

周末凌晨两点,手机震了。第一反应是“数据服务挂了”。打开电脑看了下,果然,某张核心报表的数据产出时间已经超过SLA两小时,下游三个业务方连着发来问询。这种场景,做过大数据运维的人都不会陌生。数据服务的监控与运维…

2026/10/23 0:02:26

FreeRTOS任务通知实战详解:内核原理、API与避坑指南

裸机编程切到RTOS之后,任务之间的通信和同步永远是躲不开的坎。前面聊过队列、信号量、互斥锁、事件组,这次把FreeRTOS任务通知(Task Notification)单独拎出来讲透。原因很简单:任务通知是FreeRTOS里最容易用错、也最容…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑