“Python+Django怎么就要做宿舍管理系统了?功能多不多?说实话,这题我熟。”如果你是个刚学完 Django 基础、正处于“什么都懂一点但拼不成一个完整项目”状态的人,那这个方向恰恰是练手神器——宿舍管理系统的边界足够清晰、业务场景足够丰富,把权限、关联查询、报表、文件导出这些 Django 核心玩法全串起来了。
它本质上不是一个“Demo”,而是一个“五脏俱全”的企业级后台雏形。你写完它之后,再做任何带权限的 WEB 管理系统,都可以直接套壳子换皮。
1. 整体架构与技术选型思路
1.1 为什么是 Django,而不是 Flask 或 Spring Boot
我知道有人会问:Flask 轻量、Spring Boot 强大,为什么偏偏是 Django?我的答案是:宿舍管理系统的核心痛点不在“高并发”或“复杂计算”,而在于“后台多、权限杂、数据关系绕”。
- 三端角色(超管、宿管员、学生)意味着你需要完整的后台管理界面、登录鉴权、会话控制,Django 的 admin 后台 + auth 模块直接省下 40% 的基础代码量。
- 宿舍楼、寝室、床位、学生这几张表之间是典型的一对多、多对一关系,Django 的 ORM 把这种关系的查询链写成了 Python 的链式调用,极度友好。
- 调度任务(比如每月自动生成水电费账单)用 Celery 或 django-crontab 很容易推下去。
- 更关键的是,它自带“电池”,Django 的 Model-View-Template 分层结构配合 DRF(Django Rest Framework),后期把小程序端接进来也不用重写核心逻辑。
我的项目架构核心选型方案:
| 层 | 技术栈 | 说明 |
|---|---|---|
| 前端页面 | Bootstrap 5 + jQuery | 后台管理界面,快速出活 |
| Web 框架 | Django 4.x | 核心后端 |
| ORM | Django ORM | 数据库操作用原生 ORM |
| 数据库 | MySQL 8.0 / SQLite | 开发环境 SQLite,生产 MySQL |
| 认证 | Django Auth + Session | 支持多用户类型判断 |
| 图表 | ECharts + 自写 JSON 接口 | 数据可视化 |
有人说“怎么不上 Vue 3 + Element Plus 做前后端分离?”我个人觉得,宿舍管理系统属于典型的信息管理类系统(MIS),重后台表格、重搜索筛选、重权限层级,用服务端渲染反而更快,一个模板循环就能出列表,修改需求时也更快。等后面需要小程序版再做前后端分离,核心还是那套 Django 接口。
1.2 系统角色的边界设计:先想清楚谁能干什么
这一步是整个系统的地基,我不建议直接从建表开始。我花了差不多两个小时专门画角色权限矩阵,这个动作在后来的联调阶段救了我非常多次。合理的角色定位如下:
- 超级管理员:管理宿管员账号、查看全站统计数据、接收所有操作的审计日志、系统设置的维护。
- 宿管员(也叫楼栋管理员):负责自己对应楼栋的学生入住/退宿/调宿办理、卫生检查打分、水电费录入、维修工单处理进度录入、访客登记。
- 学生:查询自己的住宿信息、提交维修申请并查看进度、查看水电账单并进行“模拟缴费”、在线登记晚归信息、接收公告。
这三种角色不是互不关联的“三套系统”,而是共享同一套数据权限——宿管员只能操作自己的楼栋数据,学生只能读自己的记录。整个权限过滤全靠一处自定义 Mixin 来控制查询集(QuerySet),比如:
class HousekeeperViewMixin: """宿管员基础视图类:强制过滤当前宿管员所在楼栋""" def get_queryset(self): qs = super().get_queryset() if self.request.user.role == 'admin': return qs # 宿管员只允许访问自己管理的楼栋 return qs.filter(building__manager=self.request.user)核心思路是“不要在每个视图里重复写权限判断”。你只要在全局基类里统一处理,后面新增一个功能模块也自动带上权限隔离,省了很多重复劳动。
2. 数据库模型设计与核心字段设计
2.1 实体关系梳理,画清六张核心表
宿舍管理系统表面上看着简单,无非“住哪个楼哪个房间哪张床”,但真正建模时会遇到很多实际细节。比如:一个学生转专业了要不要换宿舍?毕业退宿后他的床位历史记录要不要留?维修记录要不要定位到具体某一台空调?
我最终把数据库结构设计成了这样的核心模型:
- Building 宿舍楼表:楼栋编号、楼栋名称、楼层数、是否男女楼、宿管员外键、总床位数、备注。
- Dormitory 宿舍房间表:归属楼栋外键、房间号、房间类型(四人间/六人间)、已住人数、床位数、当前空床数。
- Student 学生信息表:学号(主键)、姓名、性别、学院、班级、联系方式、入住状态、紧急联系人。
- Bed 床位表:关联宿舍房间、床位编号(如A201-3)、当前状态(空闲/入住/维修中)、当前入住学生外键(可空)。
- Repair 维修工单表:报修人关联学生、宿舍房间、故障描述、上报时间、处理状态、处理人员、完成时间、处理反馈。
- ElectricBill 水电费账单表:关联宿舍房间、账单月份、电费金额、水费金额、缴费状态、缴费时间。
为什么单独拆一张“床位表”,而不是在宿舍房间里存一个“床位学生列表字段”?因为一个宿舍里每张床的状态需要单独维护,状态完全独立,而且存在“一人住宿舍但某张床处于维修未开放”的情况。这种单拆出来的“细粒度实体”方式,后续做换寝、退宿、调宿的逻辑会很干净。
2.2 状态与逻辑的细节设计,考虑清楚每个字段的真实含义
数据库字段真正打磨起来,常用数值必须有独立状态。比如“入住状态”,学生不可能永远只有一个“在读”状态。新生刚录取、待报到、未分配宿舍、已入住、已毕业退宿等状态都要识别出来。
在 Python 里我觉得最佳实践是用 Django 的 TextChoices 定义枚举类,而不是直接用魔法数字:
class StudentStatus(models.TextChoices): NO_ROOM = 'noroom', '未分配宿舍' LIVING = 'living', '已入住' LEFT = 'left', '已退宿'这样你在 QuerySet 里过滤逻辑可读性非常强:Student.objects.filter(status=StudentStatus.NO_ROOM),一眼就能知道搜索什么。
关键字段里还必须有对应的唯一性约束。比如“某栋楼宿管员唯一”这种业务规则,要落到数据库层的UniqueConstraint上:
class Meta: constraints = [ models.UniqueConstraint( fields=['building', 'manager'], name='unique_building_manager' ) ]凡是业务上不允许重复的数据,必须在数据库层防住,只靠视图逻辑过滤的话,一旦有并发请求或测试数据绕过了校验,脏数据想清理非常麻烦。
2.3 关联查询优化:提前设计常驻索引与查询场景
Django ORM 开发时最典型的坑就是 N+1 查询。一开始列表页显示 30 个宿舍房间,每个房间要查一次所属楼栋和宿舍成员数量,页面加载慢得让人抓狂。解决办法有两个,我也是在开发过程中慢慢调整完毕的:
第一,select_related 解决外键关联的部分。如果你想在宿舍房间列表页面展示所属楼栋名称,最简单的方式是:
dorm_list = Dormitory.objects.select_related('building').all()把楼栋表的关联查询用一条 JOIN 直接拿回来,不再每个房间单独查一次。
第二,annotate + Count 解决子查询统计的部分。要显示每个宿舍当前剩余空床数量,直接在模型层级算好用注释字段带出即可:
from django.db.models import Count, F dorm_list = dorm_list.annotate( empty_beds=Count('bed', filter=Q(bed__is_occupied=False)) )这一套下去,列表接口原本 5 秒钟才返回数据,优化到百毫秒级别。以后对系统有性能洁癖的话可以继续做 Redis 缓存,但现阶段这个操作已经足够了。
3. 核心功能模块实现:从页面到接口的完整链路
3.1 学生入住/退宿流程管理,最复杂的业务闭环
我先从业务最复杂的地方讲起——学生入住和退宿。这是一条会操作多张表的“事务性”业务流水线。开发时很容易想到“新增一个学生记录”就完事了,但其实真正的入住流程需要同时完成多个动作:
- 在“待分配学生列表”选定某个学生;
- 选择目标楼栋 -> 目标房间 -> 自动显示可用床位;
- 确认入住时,需要同时完成三件事:更新学生宿舍信息、修改 Dormitory 的已住人数、修改 Bed 的状态为已入住并绑定学生;
- 生成一条入住历史记录,方便后续追溯。
这些步骤必须放在同一个数据库事务里。否则就可能出现“学生分配了床位但房间人数没加一”的半旧半新数据。Django 里面用transaction.atomic()包住整段逻辑,而且每种情况都要有回滚机制:
from django.db import transaction @transaction.atomic def check_in_student(student, room, bed): """学生入住核心逻辑,多条数据一致性更新""" if bed.is_occupied: raise ValueError('目标床位已被占用') # 更新床位 bed.is_occupied = True bed.student = student bed.save() # 更新房间已住人数 room.occupied_beds += 1 room.save() # 学生状态调整 student.room = room student.bed_no = bed.bed_no student.status = StudentStatus.LIVING student.save()退宿办理是对称操作,但还得额外确认“该学生是否存在未结清的水电费或未完成的维修工单”,有的话要先挂起,不允许直接退宿。这里有一个很重要的产品设计思路:强制设置业务前置条件的校验机制,让系统提前发现意外情况。
3.2 宿舍自动分配与手动调宿,模拟遗传算法的“两版迭代”
最早版本的分配功能,我做成纯“按学院+性别匹配空床位”的规则,很简单,但是不实用。因为宿舍管理中存在大量细节:同一个班级最好住一起、少数民族学生可能有特殊餐饮位置需求、个别同学明确提出不想住上铺。
后来我把“自动分配”设计成两层思路:
- 过滤硬性条件:性别、楼栋属性、学院(可选)、是否允许混合住宿。
- 排序软性条件:优先分配同班级学生相邻床位,如果目标房间无同班同学则推到下一间房。
算法上其实并不需要搞多复杂的遗传算法或优化模型。我个人的建议是:只要把“过滤 + 固定规则排序”先做好,就已经能满足 80% 的实际使用场景,剩下 20% 手动调宿即可。
调宿的核心逻辑要特别注意事务性——A 学生从 201 房间搬到 305 房间,必须一边释放旧床位,一边占用新床位。写之前想清楚,之后测试就特别顺利。
3.3 设备维修工单闭环,把状态流转做成可追溯流程
维修管理是另一个核心模块,学生端提交维修申请,宿管员端处理派单,维修人员更新进度。这个模块的价值在于将线下“报修靠喊、维修靠看、反馈靠问”的模式,转变成了线上的闭环流程。
- 状态流转:“待受理 -> 维修中 -> 已完成”;
- 每步操作必须留下记录与状态变更时间;
- 针对维修评价,后续给学生推送反馈,形成闭环。
这里最关键的坑是:维修状态只让宿管员变更,还是维修工也可以变?我做的是给维修工在后台设置成只读权限角色,可以查看但无法编辑;实际操作中我们进行了简化,让宿管员代办录入维修结果,减少二次开发量。
我在视图层做了动作控制:每次状态变更都写入一张 FlowLog 表,留下操作人和时间节点,随时能倒查。
3.4 公告与消息推送的实现细节
公告模块建议使用“站内公告 + WebSocket(或轮询)红点提醒”的组合。初次迭代不需要做 WebSocket,Django 的轮询接口虽然稍显“老套”,但也能满足校园稳定性的用户量,而且极其容易调试。
模板中直接判断是否有未读公告(比如对比最近阅读时间与公告发布时间):
{% if notice.publish_time > request.user.last_notice_read_time %} <span class="badge bg-danger">新</span> {% endif %}当用户点击公告详情时就更新last_notice_read_time,这样新公告标记整体就是一个非常简单但很有效的状态逻辑。
3.5 可视化统计报表:也许比预想的更重要
宿舍管理者最关心的不是有哪些数据,而是实时床位利用率、空宿舍分布、性别比例、水电费收缴率。用 ECharts 实现前,后端先写统一的统计接口,返回结构化 JSON 数据,前端展示时再直接用 Ajax 获取。
比如床位利用率数据:
def bed_stats_api(request): total_beds = Bed.objects.count() occupied_beds = Bed.objects.filter(is_occupied=True).count() rate = round(occupied_beds / total_beds * 100, 2) if total_beds else 0 return JsonResponse({ 'total': total_beds, 'occupied': occupied_beds, 'rate': rate, })这套图表统计往往被当作“锦上添花”,但在我实际交付给学院宿管科后,他们告诉我:反而这个看板页面是平时打开频率最高的页面,因为领导看数据方便了。这提醒我——做系统永远不要低估管理层的日常看数需求。
4. 页面与前端实现要点:让后台系统真正好用
4.1 基于 Bootstrap 5 的布局,把复杂表格做清楚
Django 模板自带后台就是一个个表单和数据网格。页面美观度不是第一要务,结构清晰与操作可达性才是管理者最关心的。
我根据过往的习惯把整体布局拆成三块:
- 顶部导航栏:系统标题 + 当前登录用户 + 退出快捷入口;
- 左侧侧边栏:按角色显示菜单,管理员看到全部,宿管员只看自己楼栋相关菜单;
- 右侧内容区:表格页面 / 编辑页面 / 统计页面。
因为不用现代前端框架,代码直接采用模板继承机制,整体复用性很好:
<!-- templates/base.html 骨架 --> <aside class="sidebar"> {% include 'sidebar_menu.html' %} </aside> <main class="content"> {% block content %}{% endblock %} </main>4.2 列表页必须有这三个功能:搜索、筛选、导出 Excel
根据过去几年给业务方做后台的经验,列表页的核心价值其实就四个字——导出 Excel。很多开发同学忽略了这个需求,但这类管理系统每天的使用场景就是:宿管老师需要一份住校生名单做线下检查,需要一份空床统计表交给财务,等等。如果系统里没有导出,对方必然会手动复制,也会容易出错。
因此我在每个核心列表页都加了导出按钮,使用 Django 的openpyxl,实现也非常简单,关键是要注意确保字段顺序符合业务习惯:
import openpyxl from django.http import HttpResponse def export_students_excel(request): wb = openpyxl.Workbook() ws = wb.active ws.title = '学生住宿名单' ws.append(['学号', '姓名', '学院', '班级', '楼栋', '房间号', '床号']) for stu in Student.objects.filter(status='living'): ws.append([...]) response = HttpResponse( content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = 'attachment; filename=students.xlsx' wb.save(response) return response搜索和筛选则建议用一个函数基类做封装,维护性收益巨大。每个视图只要确定模型和字段配置,其他都交给通用类处理。
4.3 用 Django Form 完成“数据校验 + 带错回显”,而不用裸页面手写校验
虽然现代前端渲染都趋向于独立交互,但服务端渲染场景下,我还是强烈推荐使用 Django 的表单体系:
- 编辑表单时可以自动回显传入初始值到字段;
- 提交时自动校验 Email、联系电话格式;
- 校验失败后回到页面自动展示错误信息。
这里有个容易被新手忽略的问题:如果你用 jQuery 手动 POST 数据后返回 JSON 错误信息,页面上的错误提示不会自动关联到具体字段,而 Django Form 天然就实现了“哪个字段出错就显示在哪个下方”的体验,开发成本极低。
5. 安全管理与异常处理:全程不变的底线
5.1 登录与权限的控制,不要自己造轮子
开发后台系统最大的禁忌之一是“自己手写登录逻辑验密码”。Django 自带的authenticate函数其实已经做了密码校验、加盐哈希、会话管理,安全性和可靠性都是经过大量实战验证的。
登录视图的基本逻辑:
from django.contrib.auth import authenticate, login def user_login(request): if request.method == 'POST': username = request.POST['username'] password = request.POST['password'] user = authenticate(request, username=username, password=password) if user is not None and user.is_active: login(request, user) return redirect('dashboard') # 登录失败 return render(request, 'login.html')登录成功后还需要根据角色将用户导向对应首页。我的做法是:
user_profile = UserProfile.objects.get(user=request.user) if user_profile.role == 'admin': return redirect('admin_dashboard') elif user_profile.role == 'housekeeper': return redirect('housekeeper_dashboard') elif user_profile.role == 'student': return redirect('student_dashboard')5.2 装饰器控制路由与应用 CSRF 的保护
Django 的@login_required装饰器要应用到每个受保护页面,防止未登录用户直接访问。同时,自定义一个role_required(['admin'])的装饰器,将该逻辑集中于一个地方统一引用,避免多个视图反复粘贴权限代码。
def role_required(allowed_roles): def decorator(view_func): @wraps(view_func) def _wrapped_view(request, *args, **kwargs): profile = UserProfile.objects.get(user=request.user) if profile.role not in allowed_roles: return HttpResponseForbidden('您没有访问该页面的权限') return view_func(request, *args, **kwargs) return _wrapped_view return decorator关于跨站请求伪造攻击的防护,很多人觉得把 CSRF 中间件和模板中的{% csrf_token %}引入就万事大吉,但如果后台有 Ajax POST 请求,还要单独在处理逻辑的 JS 里带上 csrftoken。第一次上线时我普遍跳过这个步骤,直到一次本地安全测试发现 POST 提交接口外网可调用,马上就把自定义的 X-CSRFToken 请求头加上去了。
5.3 日志与操作审计:出现问题时有据可依
管理系统一定会涉及“某个同学宿舍被误调了”“有人偷偷把自己的宿舍床位改了”这类场景。除了依靠权限控制保护视角,还要在数据模型层面设计操作日志表。核心模型是用户、操作类型、操作对象、操作详情、操作时间、IP 地址。
Django 的signals可以帮你做这一切:
from django.db.models.signals import pre_save, post_save @receiver(post_save, sender=Bed) def bed_changed_log(sender, instance, **kwargs): if instance.tracker.changed(): OperationLog.objects.create( user=instance.operator, action='bed_update', detail=instance.tracker.changed(), )不要写任何死代码,只要核心被修改时自动记录即可,跑通后就能及时证明数据发生问题的责任归属是否明确。
6. 常见问题排查与后端避坑技巧
6.1 “我明明改了 models 但数据库表没有变化”
Django 的开发流程是“迁移迁移”:先python manage.py makemigrations生成迁移文件,再python manage.py migrate应用到数据库。很多新手搞混的地方在于:只 runserver 看效果就以为表已经生成,这是明显不合逻辑的。“我改完全没有反应”,八成就是少了 makemigrations 那一步。
如果是生产环境更新,还需要注意数据备份;另外假如二次开发需要给已有表新增非空字段,最好先用 makemigrations 的时候指定默认值(例如default=''),否则迁移时它会生成交互式提示,容易卡在非交互部署环节。
6.2 “页面报错 403 CSRF verification failed”
出现这个问题的原因只有两种可能:
- 模板表单没写
{% csrf_token %}; - 用了 AJAX 但没有在请求头带上 CSRF token。
排查过程很简单,先看模板源码,再看浏览器窗体网络面板的请求头,90% 都是这两点其一。请求头完整加上之后写错误提示问题迎刃而解。
6.3 数据库时区与默认时间错误
Django 的 settings 里面TIME_ZONE如果不设成'Asia/Shanghai',数据库中显示的时间会与本地不一致。造成印象时间不准的原因都出在这一处配置项上。
更好做法是设置USE_TZ = True且TIME_ZONE = 'Asia/Shanghai',在模板输出时使用本地化时间函数{{ obj.create_time|date:"Y-m-d H:i:s" }},就可以让所有页面显示本地时间。
6.4 部署之后静态文件全部丢失
开发时runserver会自动帮我们处理静态资源,但布到生产环境的 Nginx + uWSGI/Gunicorn 时,Django 不再主动处理/static/请求,常常界面就变成“裸奔”了。
需要在配置里做两件事:
- 在
settings.py里设置STATIC_ROOT = BASE_DIR / 'staticfiles'; - 执行
python manage.py collectstatic,把所有 App 的静态文件都复制过去; - 在 Nginx 配置中把
/static/路径映射到staticfiles目录。
这三步非常关键,一旦少了 Nginx 那一步,即使程序跑飞了前端也没有任何渲染效果。
7. 环境搭建与部署实操:从本地开发到服务器上线
7.1 本地开发环境完整搭建记录
不管在什么项目里,创建虚拟环境都是第一件要认真做的事,在 Django 项目里更加不需要“全局安装依赖”的思路。推荐用uv(一个很快的 Python 包管理器)或传统的virtualenv/venv建立隔离环境,下面是以 venv 为例的操作:
python -m venv venv source venv/bin/activate # Windows 为 venv\Scripts\activate pip install django==4.2.* mysqlclient # mysqlclient 可能因平台而异 pip install openpyxl requests完成依赖安装后用django-admin startproject dorm_system .创建项目,再用python manage.py startapp accounts、python manage.py startapp dormitory分别创建用户信息和宿舍管理两个 App。
7.2 数据迁移与初始化管理员账号
迁移完成之后,需要创建超级管理员来进入后台:
python manage.py migrate python manage.py createsuperuser生产环境要启多个角色时,可以在项目初始数据脚本里新建 UserProfile 并分配对应角色,也可以用管理命令:
# management/commands/init_admin.py 简化版 class Command(BaseCommand): def handle(self, *args, **options): user = User.objects.create_superuser('admin', 'admin@example.com', '初始密码') UserProfile.objects.create(user=user, role='admin')7.3 Linux 服务器部署路线与踩坑记录
我常用的是“Gunicorn + Nginx + MySQL”这套经典组合。部署之前先确保开发环境下DEBUG = False,同时准确设置ALLOWED_HOSTS。写清真实服务器域名地址,否则外部访问会出现 DisallowedHost 错误。
推荐流程:
- 将代码上传到服务器,在项目目录下启用虚拟环境;
- 安装依赖,确保数据库可连接;
python manage.py collectstatic收集静态文件;- 启动 Gunicorn 测试连通:
gunicorn dorm_system.wsgi:application -b 0.0.0.0:8000 - 再挂到 Nginx 反代,并通过
supervisor或 systemd 托管守护进程防止死掉。
非常典型的坑是安装mysqlclient需要系统有libmysqlclient-dev,如果不想编译源码,项目初期可以直接换成 SQLite 跑通,再切 MySQL。部署上线初期如果 MySQL 没配好,一度卡了好几天,教训就是环境依赖要提前确认。
8. 扩展方向与个人实际操作总结
8.1 “功能多”的下一步:微信小程序端、门禁联动与消息通知
如果这个项目要持续做下去,后续最值得扩展的方向有三个:
- 微信小程序学生端:学生通过微信查询床位、报修、缴水电费,比手机浏览器体验更贴近实际生活,后端以 Django REST Framework 提供 API 供小程序调用,用户登录改成 openid + session 管理;
- 晚归/访客登记数字化转型:宿舍楼下的门禁刷脸数据接入系统后,晚归记录自动同步,不再让宿管手动录入;
- 日常事项的定时调度:每月 1 号自动生成每间宿舍的水电账单,用 Django-celery-beat 定时执行即可,这样宿管员不需要每个月手动拉数据。
8.2 代码组织上的优化心得
我还是建议有条件的同学把“业务服务层”单独拆出来。常规 Django 新手很容易把逻辑全部写死在视图层,看起来很快,但一到后期继续加功能时视图会迅速膨胀到几百上千行。
我的组织方式并不复杂:
views.py只负责接收 HTTP 参数并调用服务函数;services.py负责全部业务逻辑,例如入住、退宿、调整床位;- 函数内部必须用 Django 的
transaction.atomic包住,保证多条数据操作同步成功或失败。
这种拆法会让代码的难度下降非常多,一旦改需求,你多半只需要去 services.py 里微调一个小函数就行,不用大动模板与路由。
8.3 最后两件小技巧:单元测试和 Admin 懒加载机制
再分享一个不太被提到的坑——Django Admin 后台直接编辑外键关联字段时,如果数据量非常大(一万间宿舍的房间),页面加载会让你想放弃项目。建议在 ModelAdmin 中使用懒加载机制:
class DormitoryAdmin(admin.ModelAdmin): autocomplete_fields = ['building']同时在 BuildingAdmin 上注册search_fields = ['building_no', 'building_name'],这样操作界面会变成一个搜索框,大大减轻数据库压力。
开发测试非常关键的是“在写接口前先用 Django 的测试客户端跑一个冒烟用例”。我多年做项目的感受是:宿舍管理系统虽然“不大”,但依然会碰上复杂多表联动。通过单元测试把入住/退宿/水电费生成这三条主链路锁死,后续再怎么改其他地方,都不需要担心改坏了核心逻辑。
我在实际开发里几乎都能遇到:某个小版本改完界面,发现自己没注意保存逻辑导致学生迁寝后旧床位没有释放,这类严重 bug 必须在测试阶段就能挡住,等上线后才发现,那就是事故了。
最后再提一个实用建议:不要在一个新项目里追求一步到位,先把“学生入住 + 换宿 + 退宿”这条主链路跑通后交予真实宿管老师试用,然后根据使用反馈去加其他模块。系统是给人用的,功能再丰富,也要先解决用户每天都要重复做的最核心的那件事。