每年四五月我都会收到一批几乎相同的消息:“学长,我选的毕设题目是高校后勤报修系统,但跑了几份网上的源码,要么缺表,要么登录进去一片空白,到底应该从哪入手?”
说实话,这个题目热度高是有原因的。它看起来就是一个普通的增删改查管理系统,但真正做进去就会发现,里面装着角色权限、工单状态流、图片上传、消息通知,甚至统计报表这些可讲可问的点。Django 做这类项目又特别合适,ORM、Admin 后台、自带的用户体系都能直接复用,所以源码也好、文档也好,市面上一直不缺。但只把代码跑起来完全不够,你要能在答辩时把“为什么这么设计”讲清楚,才算是把这套系统真正拿到手了。
这篇文章我按自己带学生做这套 Django 高校后勤报修系统的顺序来写,从需求拆解、数据建模、核心维修流程,到 wangeditor 富文本接入、远程调试部署、以及答辩常见的追问,全部过一遍。如果你正准备交这个题目,或者刚做完一个“很像报修系统但不像能用”的 Demo,这篇应该能帮你把整个项目重新理一遍。
1. 这个题目到底在考察什么:报修单背后的角色协作与状态机
1.1 为什么很多“报修系统”答辩时会被问倒
如果只在数据库里建一张“报修表”,页面上让用户填内容,管理员在后台看看列表,那这不叫报修系统,这只是一张在线登记表。我看过很多类似的半成品毕设,页面不少,前端框架也上了,但点开业务逻辑会发现全是死的。
老师最喜欢问的几个问题,这类项目往往一个都答不上来:
- 用户提交一张报修单之后,谁有权限看到它?
- 维修工接了单,单子状态是怎么从“待审核”变到“维修中”的?
- 同一个维修工能不能把别人手里的单子直接改成“已完成”?
- 用户说没修好,重新提交时,原来的单子怎么处理?
这些问题全部指向同一个核心,报修单是有生命周期的。一个完整的报修流程是:用户提交故障,后勤管理员审核,管理员派单给维修工,维修工接单处理,处理完提交结果,用户确认并评价,这条工单才算是真正闭环。任何一步缺失或者状态乱跳,整个系统的可信度就崩了。
1.2 先还原一个真实场景:教室的灯管坏了
我们直接看一个最普通的场景:第二教学楼三楼 305 教室靠窗第二盏灯管坏了。
学生要做的事情很简单,打开系统,填位置、填故障描述、拍一张照片传上去,留个手机号方便联系。这个“很简单”的动作,在系统里就是一条报修记录的产生,里面至少带着提交人、故障位置、故障分类、图片、联系电话这几个字段。
管理员收到这条记录后,先根据图片和描述判断是不是真的需要派人。如果灯管确实坏了,就把它指派给负责电工类维修的师傅。这一步在系统里叫“审核并派单”,也是整个流程里权限最敏感的地方。
维修工端的视角是:我能看到派给我的单,接单后根据地址去现场,如果只是换个灯管那就直接处理,完工后上传处理结果。学生收到维修完成的通知后,回到系统里确认故障是否解决,再打个分,这条工单才彻底结束。
你会发现,在这个过程里,系统真正做的是两件事:一是记录每一步是谁在什么时间做了什么,二是约束每一步只能由有权限的角色操作。想明白这一点,建表就顺了,写视图也不会乱。
一个报修系统的设计质量,基本由状态机和权限模型决定。前端漂不漂亮反倒是次要的。
1.3 为什么这类项目用 Django 做很顺手
如果说状态机是这个题目的灵魂,那 Django 就是大多数学生最省力的实现载体。
第一,Django 自带 User 和认证登录。不管是学生、维修工还是管理员,登录、登出、Session、密码加密这些都不用自己写,哪怕你不太懂密码学,直接用框架提供的功能也比自己造要安全得多。
第二,ORM 加 migrations 在做毕设迭代时简直是救命工具。很多学生一开始建表肯定想不全,中途发现少一个字段、想加一张表,直接改模型然后执行 makemigrations 就好,不用手动去数据库里敲 ALTER TABLE。
第三,Django Admin 是天然的“后台演示器”。毕设要求往往有“后台管理”这一项,用 Django Admin 再搭配一点自定义字段,几分钟就能把用户管理、报修分类管理、工单管理这些页面拉出来。
第四,Django 的模板、表单、信号这些机制都能在系统里找到很自然的落点。这意味着你不需要额外引太多第三方组件,也能写出一个分层清晰的项目。这点对要写设计文档、画系统架构图的学生来说特别友好,因为每一块都能对应文档里的一个模块。
2. 先建模再写代码:权限、工单表和状态枚举
2.1 用户角色不是靠“猜”的,要落在数据上
很多学生在做多角色系统时会犯一个错:在一个前端页面里用if user_type == 'admin'来控制显示内容,但用户类型根本没有存进数据库,或者只存在前端的 localStorage 里。这种方案在真实系统里是绝对不行的,因为接口没有做任何权限校验,别人直接构造一个请求就能操作不属于自己的数据。
用 Django 实现角色,主流有两种做法:
一是用 Django 自带的 Group 模型,把不同角色放进不同分组,配合@permission_required装饰器来控制访问;
二是扩展 User 表,建一个 Profile 表,把角色、电话、所在校区这些额外信息都放在 Profile 里。对于课程设计、毕业设计这个量级的系统,我更推荐第二种,因为更直观,查当前用户的角色直接request.user.profile.role就行,不需要额外查权限表。
核心代码大致长这样:
class Profile(models.Model): USER_ROLE = [ ("student", "普通用户"), ("repairer", "维修工"), ("manager", "后勤管理员"), ] user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") role = models.CharField(max_length=20, choices=USER_ROLE, default="student") phone = models.CharField(max_length=20, blank=True) location = models.CharField(max_length=255, blank=True, verbose_name="所在校区/楼栋") def __str__(self): return f"{self.user.username} - {self.get_role_display()}"然后写装饰器做视图层拦截:
from functools import wraps from django.shortcuts import redirect def role_required(*roles): def decorator(view_func): @wraps(view_func) def _wrapped(request, *args, **kwargs): if not request.user.is_authenticated: return redirect("login") if request.user.profile.role not in roles: return redirect("index") return view_func(request, *args, **kwargs) return _wrapped return decorator以后在维修工相关的视图上直接加@role_required("repairer"),权限问题就变得一目了然,答辩时也很好讲:权限不是在前端隐藏菜单,而是在视图层强制校验。
2.2 核心表的字段设计思路
报修系统最核心的表就是报修工单表。我见过有些学生把“故障分类”“维修人员”“状态”全部塞进一张表里,字段越加越多,互相纠缠,最后自己都改不动。更合理的做法是先拆出几张职责单一的表,再通过外键把它们关联起来。
这里列一张核心表设计的参考模板,字段并不需要多,但每一列都要有存在价值:
| 字段名 | 类型 | 说明 |
|---|---|---|
| application_user | FK to User | 提交报修的用户 |
| category | FK to RepairCategory | 故障分类,比如水电、桌椅、网络 |
| title | CharField(64) | 报修标题,例如“教室灯管损坏” |
| description | TextField | 详细描述 |
| image | ImageField | 现场图片,可空 |
| location | CharField(255) | 故障发生的具体楼栋与房间 |
| phone | CharField(20) | 联系电话 |
| status | CharField/TextFieldChoices | 当前工单状态 |
| assignee | FK to User, null | 被指派的维修工 |
| created_at / updated_at | DateTimeField | 创建时间和最后更新时间 |
除了这张主表,还应该有两张辅助表。一张是“工单处理记录表”(RepairLog),用来保存每一次派单、接单、完工、回退的操作者和操作内容;另一张是“评价表”(RepairComment),保存用户对工单的评分和文字评价。有这两张表之后,管理员的工单详情页就是一个完整的时间线,答辩时把页面切出来给评委看,效果远胜于一句“我有评价功能”。
维修工和用户之间不直接建表关联,而是通过工单的 assignee 字段体现,谁接手这张单,谁就是当前责任人。
2.3 状态字段用 Django 枚举,而不是散落一堆魔法数字
状态字段的处理是很多初学者代码变得混乱的分水岭。我看到不少项目里状态值是1、2、3、4,模板里显示还得手动写 if 判断,数据库里改数据也分不清数字什么意思。更合理的做法是直接在模型里定义成 TextChoices 枚举,代码里写状态名,数据库里存字符串,模板里也能自动拿到中文显示。
class OrderStatus(models.TextChoices): PENDING = "pending", "待审核" ASSIGNED = "assigned", "待接单" PROCESSING = "processing", "维修中" FINISHED = "finished", "待确认" COMPLETED = "completed", "已完成" CANCELLED = "cancelled", "已取消"模型里声明状态字段时:
status = models.CharField( max_length=20, choices=OrderStatus.choices, default=OrderStatus.PENDING, )这样做的好处很朴素:写查询时用order.status == OrderStatus.PROCESSING不会打错,或者打错了代码直接报错而不是运行后查出空数据。模板里用{{ order.get_status_display }}就能显示中文。
3. 维修流程里的关键业务:派单、接单、完工评价与消息通知
3.1 提交报修时不能只做表单入库
提交报修是用户接触系统的第一步,也是最容易被人忽视细节的模块。因为涉及到图片上传,我这里会用 ModelForm 和 Django 的ImageField来处理。
一个标准的处理逻辑是:
- 用户登录后打开报修页,选择故障分类;
- 填写位置和详细描述,上传现场图片;
- 提交后创建一条状态为“待审核”的工单;
- 同一页面重定向到“我的报修记录”,并能看到当前进度。
视图中需要处理的不仅是入库,还要考虑重复提交的问题。最简单有效的策略是在同一用户的维度上做时间限制,比如限制“同一用户一分钟内只能提交一次”,去数据库里查一下最近一条工单的创建时间,如果距离现在太近就直接拒绝。这个点看起来很小,但能挡住很多演示时的尴尬场景。有些学生在演示时手快点了好几次提交,结果生成了好几条一模一样的单子,老师看到第一反应就是系统设计不严谨。
3.2 审核派单的事务一致性和权限校验
管理员看到的待审核列表,是状态等于“待审核”的所有工单。管理员点开详情页后,可以选择指派给某个维修工,也可以对描述不清的单子选择退回。
这里有一个非常关键的技术点:从“待审核”变成“待接单/处理中”的同时,还要写一条处理日志。如果只用普通save()分步执行,一旦第二步写入日志时出错,工单状态已经变了,数据就不一致了。正确做法是用事务把两个操作包起来:
from django.db import transaction @transaction.atomic def assign_order(request, order_id): order = get_object_or_404(RepairOrder, pk=order_id) if order.status != OrderStatus.PENDING: return JsonResponse({"code": 1, "msg": "当前状态不可派单"}) repairer_id = request.POST.get("repairer_id") repairer = User.objects.filter(pk=repairer_id, profile__role="repairer").first() if not repairer: return JsonResponse({"code": 1, "msg": "维修工不存在"}) order.status = OrderStatus.ASSIGNED order.assignee = repairer order.save() RepairLog.objects.create( order=order, operator=request.user, action="派单", remark=f"指派给 {repairer.username}", ) return JsonResponse({"code": 0})注意这里的第二行判断,只有待审核状态的单子才能被派单。这个约束很关键,因为如果一个处于“维修中”的单子还能被重复派给别人,数据就会乱套。状态机的本质就是:每个状态都规定了它能跳转到哪些状态,不允许任意横跳。
3.3 维修工视角:我只看得到派给我的单
维修工登录系统后,首页应该展示的是跟自己有关的工作台,而不是整个系统的所有报修单。用 Django ORM 实现非常简单:
my_orders = RepairOrder.objects.filter(assignee=request.user)页面可以分成三个 Tab:待接单、维修中、已完成。维修工点“接单”时,工单从“待接单”变成“维修中”;点击“完工”时,要填写处理描述、使用材料这些信息,然后状态变成“待确认”,等待用户验收。
维修工不能把工单直接改成“已完成”,必须经过用户确认。因为报修是否真正解决,最有发言权的是提交报修的人。这个设计逻辑讲出来,评委是认的。
用户端发现维修完成后,进入详情页能够看到维修工填写的处理过程,然后选择“满意”或“不满意”并打分。如果选择不满意,可以填写补充意见,系统会把状态回退到“待审核”或者“维修中”,让管理员重新干预。这个“回退”动作比直接关闭工单更符合真实后勤管理场景,因为问题没有解决,流程就不能结束。
3.4 消息通知,别急着上 WebSocket
很多学生一上来就问要不要用 WebSocket,我一般都会劝他们先冷静。毕设场景里,用户主要是登录后查看消息中心,需要的是“通知已读”和“未读小红点”,这个用数据库表就完全够了。如果真做到实时短信提醒,那是另一个量级的事情,不是这个项目该背的复杂度。
在 Django 里做一个轻量通知模型:
class Notice(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notices") content = models.CharField(max_length=255) link = models.CharField(max_length=255, blank=True) is_read = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True)在派单、接单、完工这些动作发生的时候,向相关用户创建一条 Notice 记录。用户打开页面左上角能看到未读数量,点击后进入通知列表,点进去自动标记已读。这套机制简单可靠,而且答辩时你还能顺带讲一句“生产环境如果要求实时提醒,可以把创建 Notice 的逻辑替换成消息队列或 WebSocket 推送”,这就表明你是有扩展视野的。
4. wangeditor 富文本与图片上传的实战配置
4.1 富文本编辑器并不是多此一举
报修系统里哪些地方会用到富文本?至少有两个:管理员管理“故障分类说明”或“公告通知”时,需要写一段带格式的文本;维修工在填写复杂维修方案时,可能需要插入换行、列表甚至现场照片。如果所有正文都用一个纯文本域搞定,页面会很难看。
在毕设项目里,wangeditor 是性价比极高的选择。它不像 TinyMCE 那样配置繁琐,也不需要后端做复杂的权限联动,中文文档齐全,界面干净,5.x 版本对现代前端非常友好。这里注意一点:wangeditor 3 和 5 的接口差异很大。如果是从旧项目里抄的代码,直接换成最新版大概率跑不通,不如统一用 5.x 然后按官方文档改。
wangeditor 5 创建编辑器的方式是靠createEditor和createToolbar,所以在 Django 模板页里需要绑定两个 DOM 节点,一个给编辑器本身,一个给工具栏:
<div id="toolbar-container"></div> <div id="editor-container"></div> <textarea name="content" id="contentInput" style="display:none;"></textarea>页面里引入 JS 后进行初始化:
const { createEditor, createToolbar } = window.wangEditor; const editorConfig = { placeholder: '请输入说明...', onChange(editor) { document.getElementById('contentInput').value = editor.getHtml(); } }; const editor = createEditor({ selector: '#editor-container', html: '<p><br></p>', config: editorConfig, mode: 'default' }); const toolbar = createToolbar({ editor, selector: '#toolbar-container', config: {}, mode: 'default' });表单提交时,隐藏的 textarea 已经拿到了 HTML 内容,所以 Django 后端收到的就是一个普通字符串,直接存进 TextField 就可以。
使用 |safe 模板过滤器显示富文本时,一定要注意清理脚本标签,最低限度也要保证内容是管理员或可信后台写入的。如果允许普通用户提交富文本,更推荐在服务端用 bleach 之类的库做标签过滤,否则 XSS 漏洞会很致命。
4.2 图片上传返回格式必须和编辑器对齐
wangeditor 5 默认的上传图片需要后端返回一个固定结构:{ errno: 0, data: { url: '图片地址', alt: '', href: '' } }。很多学生第一次接上传接口时拿不到图片,就是因为后端的返回格式和编辑器预期不一致,编辑器拿到数据后不知道怎么渲染。
一个能用的上传视图长这样:
import uuid import os from django.http import JsonResponse from django.conf import settings from django.views.decorators.http import require_POST @require_POST def upload_image(request): upload_file = request.FILES.get("file") if not upload_file: return JsonResponse({"errno": 1, "message": "未收到文件"}) # 限制大小 5MB if upload_file.size > 5 * 1024 * 1024: return JsonResponse({"errno": 1, "message": "图片大小不能超过5MB"}) ext = os.path.splitext(upload_file.name)[-1].lower() if ext not in [".jpg", ".jpeg", ".png", ".gif", ".webp"]: return JsonResponse({"errno": 1, "message": "不支持的图片格式"}) filename = f"editor/{uuid.uuid4().hex}{ext}" file_path = os.path.join(settings.MEDIA_ROOT, filename) os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, "wb+") as f: for chunk in upload_file.chunks(): f.write(chunk) url = f"{settings.MEDIA_URL}{filename}" return JsonResponse({"errno": 0, "data": {"url": url, "alt": "", "href": ""}})这里有三个容易被坑到的地方:
第一,文件名不要用用户原始文件名。中文名或者特殊字符在 URL 里会引发各种奇怪问题,用 uuid 重命名最省心。
第二,扩展名不要直接相信上传者的文件名,一定要用小写后再判断。IMG_20230501.PNG这种后缀如果不转小写,会被直接拦掉。
第三,生产环境中传入的MEDIA_URL必须能通过 Nginx 访问到,否则富文本里的图片只存在于本地目录,前端页面完全加载不出来。这个问题我后面展开讲。
4.3 本地开发时媒体文件访问的正确姿势
开发阶段,如果你希望“双击图片 URL 就能打开”,必须在项目的urls.py里加一行:
from django.conf import settings from django.conf.urls.static import static urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)不加这一行,本地模拟上传时虽然文件已经写进了 media 目录,但访问 http://127.0.0.1:8000/media/editor/xxx.png 会返回 404。很多远程调试的工单最后都卡在这一步,因为代码看起来没毛病,model 里 ImageField 也配了,就是图片不显示。
部署到服务器后,这行代码就不再是推荐方案了。正常情况下 Django 不应该负责提供媒体文件的 HTTP 服务,而是把/media/路径交给 Nginx 去映射到磁盘目录。这部分我在下一节一起梳理。
5. 从源码到跑通:远程调试、服务器部署与常见排错
5.1 收到源码跑不起来?远程调试常用的三种姿势
毕设源码买卖或同学之间互相流传代码时,“在本地跑不起来”是最常见的问题。环境不一致、数据库版本不对、依赖缺包、Python 版本差异,每一条都能让新手卡住。远程调试本身的价值就在这里:不是你本地没有环境吗?我直接用一台统一配置好的服务器,把代码放上去跑,你在浏览器里复现问题,我在这边看日志调代码。
目前最常见的三种远程调试方式:
第一种是 PyCharm Professional 的 Remote Interpreter。原理是把本地项目通过 SSH 同步到远程服务器,用服务器上的 Python 解释器解释执行代码,断点调试像本地一样生效。适合需要逐行跟进去看的场景,比如“为什么这个表单校验一直不通过”。
第二种是 VSCode Remote-SSH。它不要求本地有 Python 环境,直接远程打开服务器上的项目目录,改代码、看日志、执行命令都在同一个窗口里完成。优点是非常轻,很多老程序员帮人远程看项目也喜欢这种方式,因为在服务器上复现出来的问题往往才是最真实的。
第三种是纯日志调试。如果你只需要帮别人排查部署问题,不一定要用 IDE 的断点功能。先重启服务看启动日志,再复现操作看异常栈,Django 开发模式下错误页面能给出非常完整的调用链,绝大多数问题都能在这里找到答案。
我不建议上来就开断点。先看报错信息,再决定要不要断点,这才是经验丰富的开发者会做的事。Django 报错页面已经把异常链、请求参数、上下文变量全都列出来了,足够判断 90% 的 Bug。
5.2 部署时最常用的一套命令与配置
一个 Django 系统要交付给别人长期用,不能只靠python manage.py runserver。虽然 runserver 在小规模内网演示时也能用,但它性能较低,也不够安全。比较稳妥的组合是 Gunicorn + Nginx,如果你服务器上已经装了宝塔之类的面板,直接在面板里改反向代理也差不多。
打包部署的核心步骤大约是:
# 进入项目目录 cd /var/www/repair_system # 建虚拟环境并安装依赖 python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # 迁移数据库、收集静态文件 python manage.py migrate python manage.py collectstatic --noinput # 启动 gunicorn gunicorn config.wsgi:application -b 127.0.0.1:8001如果有管理后台、需要定时清理临时图片,还可以先建一个管理员账号python manage.py createsuperuser。
Nginx 配置里最核心的两段 location 大致是这样:
location /static/ { alias /var/www/repair_system/static/; } location /media/ { alias /var/www/repair_system/media/; } location / { proxy_pass http://127.0.0.1:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }有人会问为什么要让 Nginx 单独处理 static 和 media,Django 自己不是能访问吗?原因在于 Django 处理静态文件的能力太弱,在高并发访问图片时特别浪费性能。把静态文件和用户上传的媒体文件都交给 Nginx,Django 专心处理业务接口,这是最常见的生产架构。
5.3 我远程帮人调过的五个最高频报错
第一类错误:静态文件全部 404。多半是STATIC_ROOT没有配置,或者执行完collectstatic之后 Nginx 的alias路径写错了。排查时可以在服务器上跑一下python manage.py findstatic admin/css/base.css,能返回路径说明文件没问题,那就去看 Nginx。
第二类错误:Django admin 页面正常显示数据,但完全没有样式。原因同上,基本就是 collectstatic 没有执行成功,或者执行后文件没落在 Nginx 指向的那个目录里。
第三类错误:数据库无法连接。如果是 mysqlclient 安装失败,通常是因为系统里缺少依赖,需要先安装 mysql 的开发库。替换 PyMySQL 也是一种临时选择,但记得在__init__.py里加一行pymysql.install_as_MySQLdb()。如果是 SQLite 文件没有写权限,更简单,给项目目录授权就行。
第四类错误:迁移时报 “No migrations to apply”。这通常是项目历史迁移文件跟数据库状态不一致导致的。解决思路是确认哪些 app 的表还没建,而不是盲目删除数据库,否则会把已有数据搞丢。处理这类问题前一定要先备份数据库文件。
第五类错误:登录后跳转有问题。多半是LOGIN_URL、LOGIN_REDIRECT_URL配置不对,或者视图里写死了跳转路径。排查时看页面地址栏的改变最直观。
5.4 ALLOWED_HOSTS 和 DEBUG 这两个容易被忽略的开关
部署完成后出现Bad Request (400),基本都是没配置ALLOWED_HOSTS。本地跑通不填没问题,那是因为 Django 默认本机访问不受限制。一旦被别人访问,就必须把域名或服务器 IP 填进去,不然 Django 会拒绝处理请求。
DEBUG也要特别注意。在服务器上如果不关 DEBUG,一旦程序报错,用户就能在页面上看到异常堆栈和项目路径,这在真实环境里等于把系统内部结构主动暴露给攻击者。正规做法是在生产环境把DEBUG=False,然后单独配置日志,调试时再临时打开或分析日志文件。我在帮人远程调 bug 时经常就是先把 DEBUG 打开复现,定位完再关掉,但交付前一定记得切回 False。
6. 定制扩展与答辩准备:做完系统不等于做好毕设
6.1 被要求“加功能”时,我通常会建议先加这几个模块
源码交付后,很多学生会根据指导老师的反馈做二次定制。我遇到比较多的扩展需求有三个。
第一个是把“报修记录”升级成“耗材配件出入库管理”。维修过程里会涉及换灯管、换水龙头这些耗材,如果希望能统计每个月的耗材支出,就需要加一张耗材表和出入库记录,再把报修单关联到具体耗材上。这个需求改动不大,但对系统的实用价值提升非常明显,因为后勤管理的核心之一就是成本核算。
第二个是加统计图表。用 ECharts 或者 Chart.js 把“各分类报修数量”“每月报修趋势”“维修工时排行”这些数据做成图表放到首页。这个方向技术上不难,因为 Django 后端只需要输出聚合好的 JSON,难点在于你会不会用 ORM 的annotate方法做分组统计。如果你能在答辩现场展示一段 annotate 聚合代码,老师会觉得你的数据处理能力过关。
第三个是移动端适配。不一定要开发独立 App,做一个响应式前端或者对接一个小程序登录入口,让用户能直接拍照上传,这个对使用场景的契合度很高,因为谁也不会为了一个灯管专门打开电脑登录网页。
6.2 答辩现场高频问题怎么答
第一个高频问题:为什么选择 Django 而不是 Spring Boot 或者 PHP?回答时不要只说“因为我会”,要结合项目优势说。比如利用 Django ORM 快速建模,利用 Django Admin 简化后台管理,利用内置的认证系统完善登录权限,再加上 Python 生态对数据处理、图表分析的支持。这个回答既展示了知识面,又体现你做选择时是有判断的。
第二个高频问题:不同角色的权限是怎么实现隔离的?直接回答“在视图层通过自定义装饰器拦截,数据库层通过外键关联实现数据隔离”,然后举一个例子:普通用户只能查询自己创建的工单,维修工只能查询指派给自己的工单。这里有个小技巧,你甚至可以主动说“以维修工身份登录系统后,在 URL 里手动输入其他用户的报修单详情地址,会被系统拒绝”,然后现场演示一遍,这比口头解释有力得多。
第三个高频问题:如果两个管理员同时操作同一条报修单,会发生什么?这个问题看似在考并发,其实不用讲得很深。你可以说在 Python 层面用transaction.atomic确保状态并更和日志写入在同一事务里,在数据库层面可以对报修单加版本号或行锁,但这套系统作为教学项目,关键做了状态前置校验,操作前先判断当前状态是否允许该操作,所以即使重复提交,第二个无效操作也会被拦截。
第四个高频问题:富文本内容如何在保证安全的同时正常显示?这里要提到 XSS。原理是:如果直接输出用户提交的 HTML,别人粘贴一段<script>代码进去就可能在你的网站上执行。所以在显示富文本内容时,不能粗糙地使用|safe,要根据角色和内容来源决定要不要做标签白名单过滤。
6.3 我整理这套源码和辅导流程时的一点经验
我开始做报修系统辅导时也只用一份简单模板,后来发现每个学生的诉求差异极大,有人是要把系统部署到自己的服务器答辩,有人是想在原项目上加一个统计图表模块,有人则连 Python 虚拟环境是什么都不清楚。
后来我给自己定了一条规则:交付代码只是一种结果,交付“能自己改代码的能力”才是服务重点。远程调试的过程中,我会先带对方看一遍项目目录结构和入口配置,再跑通一个最简单的报修流程,然后逐步定位问题。远程讲解答疑也一样,不要一上来直接甩代码,而是先让对方理解这个模块在整个流程里的位置,这样他自己调起参来才不会慌。
如果你现在正在做自己的报修系统,我也建议你给自己设置一条底线:无论最终用什么技术栈,都必须亲手走完一遍“用户提交报修 -> 管理员派单 -> 维修工接单 -> 维修完成 -> 用户评价”的完整闭环。只要这条链走通了,后面加什么功能都是锦上添花。很多人都卡在“每个功能点都做了,但没有一条完整链路能跑通”,最后演示时像在翻幻灯片,而不是在展示一个可用系统。
把主流程做扎实,把状态机和权限边界讲明白,你的 Djanggo 后勤报修系统就不只是一个花架子。这套思路不仅适合这个题目,任何带角色、带流程的管理系统都同理。