每年毕业季,某高校很多专业都要安排毕业设计导师选择。之前常常通过Excel表格、QQ群接龙或教务系统在线填报,Excel容易多人同时编辑冲突,QQ群消息一刷就看漏,教务系统又往往只有“指定导师”没有“学生自主选择”的完整流程。一个人盯后台、维护表格,出问题就要手动改来改去。这个项目要做的就是用Django搭建后端服务,给微信小程序提供接口,实现学生浏览导师信息、提交志愿、导师审核、管理员兜底分配的一整套双选流程。
我说的“导师选择系统”不是简单做一个“填表+汇总”,核心是双向选择流程管理。学生端能看到导师的研究方向、可用名额、毕业设计课题;导师端能收到学生的申请并确认或拒绝;教务管理员能监控整体进度,设置开放时间段,最后导出师生匹配名单。整个闭环走完,才真正减少教务老师重复劳动。这篇分享会按照需求拆解、技术选型、数据库设计、后端API、小程序实现、匹配规则、踩坑记录和部署上线的顺序,完整讲一遍我做这个项目时的思路和实操细节。
1. 项目概述与需求拆解
1.1 这套系统到底解决什么问题
每年毕业设计开始前,教务老师都要重新面对一轮“导师分配战役”。学生不知道自己适合哪个方向,导师抱怨邮箱里申请太多,管理员拿着表格一个个核对名额,光是催学生确认消息就要花两三天。用Excel收集志愿最大的问题不是存储,而是“状态不可见”。学生提交之后不知道老师有没有看到,老师打开同一个文件时又可能覆盖别人的操作,最后汇总出来的名单经常有冲突。
导师选择系统的核心目标,就是把“学生申请、导师审核、管理员兜底”这一个完整流程搬到线上,让每个人都只看到自己需要处理的内容。学生看到的是导师列表和申请进度,导师看到的是待审核队列,管理员看到的是全量匹配状态。所有数据统一存在数据库里,状态更新有记录,名额变化有校验,不需要再靠人工同步。
1.2 三类用户的角色与核心流程
系统里分成三种身份:学生、导师、管理员。管理员由Django自带后台处理;学生和导师通过微信小程序登录。学生进入小程序先完成学号绑定,查看导师列表与课题,提交第一志愿和第二志愿,状态从“待审核”变成“已通过”或“被拒绝”。导师登录后能看到申请自己的学生列表,可以按志愿顺序依次处理,确认某个学生后,在名额范围内同意或拒绝,最终双方确认即锁定匹配关系。
核心流程我用一句话总结:学生按志愿投递,导师按名额审核,管理员做最终兜底和导出。看似不复杂,真正做起来需要把状态机、并发、权限、通知都处理好,这也是这个项目最有价值的地方。比如导师只有5个名额,第6个学生申请时要被系统拦截;学生被导师接受后,他投给其他导师的申请必须自动失效,否则就会出现“一个学生被两位导师接收”的尴尬情况。
1.3 需求边界与优先级取舍
我当时做这个项目时,第一反应是功能越多越好,但真正排优先级时发现最关键的三件事:第一,导师容量不能超;第二,学生状态流转不能乱;第三,数据最终能导出成教务处要的格式。至于消息提醒、在线聊天、论文查重这些都属于加分项,不是核心诉求。因此我在第一版里优先完成了双选主流程,把IM这类功能直接砍掉,用了非常简单的“申请/审核”状态通知。
这句话也想送给做同类项目的同学:先做能跑通闭环的主流程,再谈花哨功能。需求分析阶段最好画一张“最小可用流程”图,只保留学生看导师、学生申请、导师审核、管理员导出这四个节点。等主流程稳定了,再考虑订阅消息、调剂轮次、自动匹配推荐这些扩展能力。
2. 技术选型:为什么是Django + 微信小程序
2.1 后端选型的心路历程
做这个项目之前,我考虑过Node.js、Spring Boot,也想过直接用小程序云开发。既然题目明确写了Python与Django,那就重点讲选型逻辑。Django最舒服的地方是自带Admin后台、ORM和认证体系。导师选择系统里“管理员维护导师信息、导入导出名单”这类需求特别适合Django Admin;ORM让我不用手写SQL就能把User、StudentProfile、TeacherProfile、Application这几张表的关系理清楚;自带的auth模块可以快速处理管理员登录。配合django-rest-framework写REST API,开发效率很高。
如果你的基础是Python,又没有特别复杂的实时要求,Django是这类管理型业务系统的最佳选择。它有点“重”,但重得有价值,尤其是后期加审计日志、加权限组、加数据字典这些需求时,Django的生态能让你少踩很多坑。相比Flask,Django的Model和Admin可以省下大量重复代码;相比Spring Boot,Python的开发速度更适合课程设计和中小型上线项目。
2.2 小程序端为什么不做纯Web
可能有人问,做Web页面不是更简单吗?实际场景里,学生和导师的手机上最常用的就是微信,小程序不用下载App、不用记网址、可以收到订阅消息,分发成本非常低。尤其高校场景,微信小程序已经是数字校园的标配,很多师生都熟悉。加上微信小程序提供 wx.login 获取 code,后端再用 code 换 openid,天然适合做免密登录,比Web端填账号密码少很多阻力。
如果选择纯Web,你还需要考虑浏览器兼容、移动端适配、短信验证码等,小程序反而把这些都省了。当然工作量不在前端样式的精细度上,而在于怎么组织API鉴权和页面状态同步。这个项目的小程序端我采用原生开发,没有用uni-app,原生语法对微信平台能力调用最直接,遇到问题查文档也最容易。如果你希望以后还能发布到支付宝小程序或者App,可以换成uni-app,但核心接口设计思路是一样的。
2.3 整体架构与数据流转
整个系统可以拆成两层:微信小程序(客户端)负责展示和交互,Django后端负责业务逻辑、数据存储和权限控制。小程序通过HTTP API与后端通信,请求格式为JSON,鉴权基于自定义登录态(token)。数据流转大致是:小程序wx.login拿code -> Django调微信接口换openid -> 查询或创建用户 -> 返回token -> 后续请求带上token访问导师列表、提交申请、查看状态。
我用到的技术栈如下:
- 后端:Python 3.10 + Django 4.2 + Django REST framework
- 数据库:MySQL 8.0,开发环境用SQLite
- 鉴权:djangorestframework-simplejwt 生成JWT,openid作为用户唯一标识
- 小程序:微信原生小程序,基础库版本使用最新稳定版
- 部署:某云轻量服务器 + Nginx + uWSGI + 备案域名HTTPS
这套结构几乎是中小型微信小程序项目的标准答案,没有复杂的微服务,也没有消息队列。在起步阶段能把一个单体Django项目写好、把接口设计清楚,已经是合格的全栈实践。生产环境如果并发量不大,完全不需要上Redis和消息队列,等真有几千人同时使用再考虑性能优化也不迟。
3. 数据库设计与核心模型
3.1 用户体系与角色扩展
Django自带的User表只负责账号密码,我需要把学生和导师的业务信息分开存。我的做法是:新建StudentProfile和TeacherProfile,通过OneToOneField关联到User。StudentProfile字段包括学号、姓名、专业、班级、联系电话、已选中的导师(ForeignKey到TeacherProfile,可空);TeacherProfile字段包括工号、姓名、职称、研究方向、学院、个人简介、总名额、已用名额、可带课题列表。
这么设计的好处是,不破坏Django原生的用户认证体系,又能在Admin后台直观看到扩展信息。很多同学喜欢直接在User上增加字段,后果是迁移复杂、权限容易混,一旦和自带的认证功能冲突非常麻烦。按profile扩展是更稳妥的方案。还有一个细节:用户名可以用wx_{openid}这种格式生成,不需要让学生自己取用户名,登录体验会顺畅很多。
3.2 导师信息与项目课题模型
导师的“研究方向”适合用多对多或者JSON字段,我为了灵活性用了JSONField,存一个字符串列表,比如["机器学习", "自然语言处理", "数据挖掘"]。小程序端展示时解析数组即可。课题(Project)模型则单独建表,一个导师可以发布多个课题,学生申请时可以选“跟随导师方向”也可以指定课题。Project字段包括标题、简介、方向标签、所属导师、需求人数、已选人数。
有人问课题和学生归属之间是什么关系?我是这样处理的:学生选择导师后,默认关联该导师发布的第一个课题,也可以由导师在后台调整。如果强行让学生选了导师再选课题,流程会复杂很多。第一版里课题只做展示和参考,真正匹配关系是学生-导师,课题在导师端手动管理即可。这个取舍在答辩时也说得通:导师选择系统的核心是“匹配师生关系”,课题只是辅助信息。
3.3 双向选择:志愿申请表的设计
这是全系统的核心表。我命名为Application,字段包括:
- student:外键到StudentProfile
- teacher:外键到TeacherProfile
- round:志愿轮次(第一轮、第二轮、调剂)
- priority:志愿顺序(1或2)
- status:状态,用枚举字符串表示
- remark:学生自述,选填
- is_active:是否当前生效,用于处理撤回和改派
- created_at、updated_at
状态的流转非常关键。我定的状态集合是:PENDING(待审核)、ACCEPTED(已通过)、REJECTED(已拒绝)、CANCELLED(学生撤回)、WITHDRAWN(已被导师移除)、ALTERNATE(候选)等。每次状态更新都要记得更新学生的teacher_id或者导师的used_slots,事务必须包裹,否则容易出现“学生被两个导师同时接收”的问题。
3.4 事务与容量控制
这里必须多说一句“容量控制”。导师名额比如5人,两个学生同时申请且同时被导师点“接受”,如果只是先查used_slots再update,很可能超招。解决办法有两种:一是在导师信息表上增加一个version字段做乐观锁,update时加where version = 当前值;二是对名额操作放在事务里,配合select_for_update()行锁。
我当时选择的是后者,在审核接口里先开启事务,用select_for_update()锁住导师记录,再判断名额余量,最后更新申请状态。实测下来即使是并发请求也不会超。这个细节是很多教程不讲但极其重要的点,后面会再展开。数据库模型设计完成后,不要急着写接口,先用Django Admin把几种类别的数据录进去,手工模拟一遍“学生申请-导师接受-名额减少”的流程,能提前暴露很多设计缺陷。
4. Django后端API实现细节
4.1 项目初始化与关键配置
后端部分我建议分步骤操作,先建虚拟环境,再装依赖:
python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate pip install django djangorestframework djangorestframework-simplejwt django-cors-headers mysqlclient创建项目和应用:
django-admin startproject mentor_system python manage.py startapp accounts python manage.py startapp mentors python manage.py startapp applications需要注意的配置项有几个。INSTALLED_APPS要加入rest_framework、rest_framework_simplejwt、corsheaders和三个自定义app。在settings.py里设置REST_FRAMEWORK的DEFAULT_AUTHENTICATION_CLASSES为SimpleJWT,设置JWT过期时间。数据库如果先用SQLite,后面迁移到MySQL只需要改DATABASES并执行迁移。另外务必配置跨域。虽然小程序不是浏览器,不强制CORS,但开发调试时可能会有问题。用django-cors-headers把CORS_ALLOW_ALL_ORIGINS在开发环境设为True即可,生产环境一定要收紧。
4.2 微信登录与鉴权方案
小程序端wx.login拿到临时code后,发给后端/api/exchange-code/接口,后端用code加appid和secret请求微信接口,获得openid和session_key。openid是微信用户在某个小程序下的唯一标识,我们拿它作为用户查找凭据,不存在就新建一个User,并返回JWT token给小程序。
这里有个常见的实现坑:微信接口返回的openid和session_key不能直接都存数据库,session_key是临时会话密钥,用于解密手机号等敏感信息,我们用不到就丢掉。code是一次性的,后端要校验微信接口返回的errcode,避免非法请求。
简化版视图代码如下:
# accounts/views.py from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework_simplejwt.tokens import RefreshToken from django.contrib.auth.models import User import requests @api_view(['POST']) @permission_classes([AllowAny]) def exchange_code(request): code = request.data.get('code') url = 'https://api.weixin.qq.com/sns/jscode2session' params = { 'appid': settings.WECHAT_APPID, 'secret': settings.WECHAT_SECRET, 'js_code': code, 'grant_type': 'authorization_code' } resp = requests.get(url, params=params).json() if not resp.get('openid'): return Response({'error': 'code无效'}, status=400) openid = resp['openid'] user, created = User.objects.get_or_create( username=f'wx_{openid}', defaults={'username': f'wx_{openid}', 'is_staff': False} ) refresh = RefreshToken.for_user(user) return Response({'access': str(refresh.access_token), 'refresh': str(refresh)})拿到token后,后续每个请求都在Authorization头里带Bearer <token>。学生和导师的身份区别通过request.user关联的profile判断,而不是靠前端传角色,这样可以避免越权。在SimpleJWT配置里,我建议把ACCESS_TOKEN_LIFETIME设置为10小时或1天,否则用户在小程序里停留一会儿就要重新登录,体验很不好。
4.3 导师列表与搜索接口
导师列表接口需要支持关键词搜索、研究方向筛选、按可用名额排序。我用DRF的SearchFilter和自定义FilterBackend,查询参数格式类似/api/teachers/?keyword=机器学习&sort=available。返回的字段只包含必要项:id、姓名、职称、研究方向、简介、可用名额。不要把手机号、工号等隐私字段直接暴露给所有学生,导师联系电话可以考虑脱敏或者只在学生被接受后展示,这个细节很影响隐私安全。
分页用DRF的PageNumberPagination,每页10或20条,小程序端用onReachBottom触底加载下一页。如果你不做分页,导师多了之后小程序首屏会很卡,所以分页是必须的。还有一个小技巧:导师列表接口要判断当前请求用户是否已经绑定导师,如果已绑定,前端可以显示“你已选择XX导师”,避免学生重复提交申请。
4.4 志愿申请与审核接口
申请接口很简单,学生提交teacher_id和remark,后端校验该学生是否已绑定导师、是否已经申请过、导师名额是否已满,然后在Application表创建一条PENDING记录。注意同一学生对同一导师不能重复申请,不同导师最多只能有两个活跃志愿。
导师审核接口则是核心。我提供服务端主动确认和拒绝两个action:POST /api/applications/{id}/accept/和POST /api/applications/{id}/reject/。接受时要在事务里检查并占用名额,同时把该学生的其他志愿标记为CANCELLED;拒绝时则只更新状态,学生可以把该志愿删掉并换投其他导师。
我建议把这类状态更新写在Django的模型方法里,不直接写在view中。当时的Application模型里加了enroll_student()方法,内部用transaction.atomic装饰并加行锁,代码更清晰,也方便自动化测试。接口层尽量只做参数校验和序列化,业务逻辑下沉到模型层,后面维护起来会轻松很多。
4.5 用Django Admin做管理后台
Django Admin是这个项目最大的效率神器。管理员登录后可以维护导师信息、导入学生名单、查看每个学生的申请状态、清空错乱的数据、手动调整匹配。Admin不是简单的CRUD,我通过自定义ModelAdmin重写了保存方法,在保存导师信息时自动计算已用名额,在删除学生时解除导师绑定。
还有导出功能,我安装了django-import-export,为Application表配置了导出资源,管理员可以直接从Admin后台导出“学号、姓名、专业、导师、课题、状态”的Excel或CSV,交给教务处。这一功能在答辩演示时非常加分,因为它直接解决了用户最实际的数据交付需求。Admin后台的list_filter、search_fields一定要配好,否则数据多了之后找一条申请记录都要翻半天。
5. 微信小程序端实现要点
5.1 小程序项目结构与基础配置
小程序的工程结构我按pages/teacher-list、pages/teacher-detail、pages/apply、pages/mine划分。全局配置app.json里设置页面路径,并把navigationBarTitleText定为“导师选择系统”。根目录utils/request.js封装请求,config.js里统一管理API baseURL和开发模式开关。
拿到微信小程序AppID后,需要在project.config.json里配置appid。如果还没注册,可以用测试号,但测试号不能调用一些接口和订阅消息,建议还是找学校或自己注册一个小程序账号,个人主体也能注册,选择“工具-教育”类目。开发阶段建议打开开发者工具中的“不校验合法域名”选项,否则本地联调会被域名校验拦住。
5.2 登录态处理与全局封装
小程序端登录的关键在于“静默登录”。用户打开小程序,没有点击任何按钮,就应该先调用wx.login,把code发给后端换取token,然后存到storage。用户点击“学号绑定”时才做身份校验,这样导师使用起来也很顺畅。
我的request.js代码如下:
// utils/request.js const BASE_URL = 'https://api.example.com/api'; function request(path, method, data, needAuth = true) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method || 'GET', data: data, header: { 'Content-Type': 'application/json', 'Authorization': needAuth ? ('Bearer ' + wx.getStorageSync('token')) : '' }, success(res) { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data); } else if (res.statusCode === 401) { wx.removeStorageSync('token'); wx.showToast({ title: '登录已过期', icon: 'none' }); reject(res); } else { wx.showToast({ title: res.data.error || '请求失败', icon: 'none' }); reject(res); } }, fail(err) { reject(err); } }); }); } module.exports = { request };这里有个很实用的技巧:在app.js的onLaunch里调用wx.login并换取token,但不要等登录完成就渲染页面。为了避免白屏,在页面onLoad时如果发现没有token,先调用一个ensureLogin()函数,登录完成后再发业务请求。简化逻辑是:首页onLoad里用Promise.all([ensureLogin(), getTeacherList()]),确保token和列表数据同时准备。
5.3 核心页面与交互逻辑
导师列表页我使用了搜索栏、方向筛选标签和分页列表。列表项展示导师姓名、职称、研究方向和可用名额。可用名额少于1时按钮置灰,并加“名额已满”小标签。这里要注意微信小程序对数组渲染的key必须设置稳定唯一值,我用导师id,避免虚拟DOM复用导致的状态错乱。
导师详情页展示完整信息、课题列表和“申请指导”按钮。点击后弹起bottom-sheet,让用户填写一句话自述,选择志愿优先级,再提交。前端需要限制不可重复提交,我用一个submitting布尔变量防止双击。用户提交成功后就跳转到“我的申请”页面查看状态。我的申请页需要展示三个区块:已通过的导师、待审核的申请、被拒绝的历史。已通过的导师卡片上显示导师联系方式;待审核的显示进度图标;被拒绝的可以删除记录并重新申请。这个页面是学生最关心的,所以状态切换的样式要明显,否则学生不知道有没有被接受。
5.4 状态管理与消息通知
小程序自带的消息推送有两种:订阅消息和模板消息。我用的是订阅消息,学生提交申请后不再主动发通知,导师“接受”时向学生发送一条“申请已通过”的消息。需要在后端通过小程序access_token调用subscribeMessage.send接口,同时学生在前端要用wx.requestSubscribeMessage授权。因为订阅消息每次都要用户触发授权,体验上会繁琐,但比短信便宜且稳定。
如果不想碰订阅消息,也可以在小程序里做一个“消息中心”,每次进入小程序时轮询接口获取未读数。实际效果也不错,而且不会因为用户没点授权而漏掉通知。第一版我就用轮询,代码量更少。导师端则不需要复杂推送,导师打开小程序就能看到待办数量,所以我在导师首页卡片上直接展示“待审核申请:N”,很直观。
6. 匹配规则与算法细节
6.1 先到先得还是志愿排序
导师选择系统最敏感的是匹配逻辑。很多学校采用“志愿优先,同志愿按成绩排名”。这就不是简单的先到先得了。我在设计时把“轮次”字段抽象出来:第一轮学生可填两个志愿,第一志愿优先处理,第二志愿只有当第一志愿被拒后才参与。导师在每个轮次内按照申请时间顺序审核,看到的就是待处理队列。
实际执行时,不用全自动匹配算法,而是“导师在系统中手动接收”,这样符合真实决策需求。系统后台可以给管理员提供一个“一键预匹配”按钮,按志愿优先级+申请时间排序生成候选列表,但最终由导师或管理员确认。把算法作为辅助而非强制的思路,能减少很多争议。毕竟导师选学生不只是看成绩,还要看研究方向匹配度,算法只能做初筛。
6.2 容量校验与并发处理
前面提到事务和行锁。我再给出简洁的代码示例:
from django.db import transaction from django.db.models import F class Application(models.Model): @transaction.atomic def accept(self): teacher = TeacherProfile.objects.select_for_update().get(id=self.teacher_id) if teacher.used_slots >= teacher.max_slots: raise SlotExceededError('导师名额已满') teacher.used_slots = F('used_slots') + 1 teacher.save() self.status = 'ACCEPTED' self.save() # 取消该学生的其他申请select_for_update()会把导师记录锁住直到事务结束,另一个并发请求进入时会等待,等锁释放后再读取最新used_slots,所以不会超。F('used_slots') + 1是让数据库执行原子加,而不是把整行取回来再更新。这两层保护叠加后,并发安全性是很稳的。需要提醒的是,行锁只能在MySQL/PostgreSQL等支持行级锁的数据库生效,SQLite不检查并发,所以开发阶段别测并发,部署到MySQL后再压测。
6.3 调剂流程与兜底策略
第一轮结束后,某些学生可能没被任何导师接收,某些导师还有名额。系统单独开启“调剂轮次”,这些学生可以再次申请剩余导师,或者由管理员直接分配。我的实现是给Application增加round字段,并让学生在收到所有志愿被拒后进入“待调剂池”。管理员在Admin后台点“进入调剂轮次”按钮,开放申请;调剂轮次同样受容量控制。
兜底策略也不能少。如果存在完全没选导师的学生,系统会生成一个“待分配列表”,管理员可以手动一对多匹配,并把匹配结果通知师生。这个实际上不是算法能完全替代的,因为牵涉研究方向匹配和学生的成绩排名,人工判断更可靠。我在系统里给待分配列表加了一个备注字段,管理员可以填“学生主动联系导师后确认”“教师指定安排”等来源,方便后期追溯。
7. 踩坑实录与排查指南
7.1 微信小程序请求域名的坑
小程序上线或真机调试时,所有请求的域名必须在小程序后台配置为HTTPS合法域名,并且域名要有备案。我第一次真机调试时,发现所有请求都失败,console提示“不在以下request合法域名列表中”。解决方法是先在开发者工具中勾选“不校验合法域名”进行联调,但上线前必须把域名配好并启用HTTPS。
还有一个容易忽略的点:如果API域名有路径前缀,比如https://api.example.com/api,后台配置合法域名时只写https://api.example.com即可,不用带路径。否则会出现“配置的合法域名不完整”之类的问题。如果同时有文件上传需求,uploadFile合法域名也要单独配置,不能和request共用一个。
7.2 本地联调时遇到的身份校验问题
本地开发时后端跑在http://127.0.0.1:8000,而小程序真机请求该地址会被微信拦截。我通常会使用内网穿透工具或者在同一局域网内用电脑IP地址,并且把后端Debug设为True、关闭CORS限制,才能在真机上看到效果。但要注意,关闭CORS和Debug在生产环境是绝对不行的。
另一个常见问题是JWT过期时间。SimpleJWT默认access有效期只有5分钟,学生在列表页停留久一点后再提交申请就会401。我把ACCESS_TOKEN_LIFETIME改成10小时或者1天,对于教务场景足够安全,体验也更好。如果担心过期,可以在每次接口返回401时自动用refresh_token刷新。
7.3 并发申请导致的超招问题
我在实际压测时发现,如果没有行锁和事务,两个学生同时被同一导师接受时,used_slots会从4跳到6,这就是典型的超招。排查过程很简单,我打印了日志发现两次请求都通过了容量校验,原因就是先查后写不是原子操作。解决了之后我又加了一个唯一约束:一个导师的ACCEPTED申请数量不超过名额,但这个约束无法动态更新,所以最终以代码控制方式实现。
为了不再踩坑,我建议给Application表加一个唯一约束,保证一个学生只能有一条ACCEPTED申请。这样即使代码出现Bug,数据库层面也能兜底。
7.4 导师端审核状态的错乱问题
导师端偶尔出现刷新后看到同一个学生既被标记为“已接受”又出现在“待处理”列表中。原因是我没有把已经ACCEPTED的Application从待处理列表中排除,列表查询只过滤了status=PENDING。修复方式很简单:考虑is_active字段,一个申请被接受后,同学生的其他申请要置为CANCELLED,并且列表查询始终带着is_active=True条件。
我还加了一个应用层检查:在导师返回列表时,如果发现该学生已经有ACCEPTED申请,就不能再操作该学生的其他PENDING记录。前端也给导师明确的提示,避免误操作。这算是一个典型的“逻辑状态没有彻底清干净”问题,排查思路是先看列表查询条件,再看状态更新时有没有把关联记录一起处理。
7.5 数据导入导出实用小工具
学校老师手里通常有Excel名单,格式是学号、姓名、学院、专业。我写了一个管理命令,支持python manage.py import_students filename.xlsx,用openpyxl解析后批量创建User和StudentProfile。导出则用django-import-export的export action,选择字段后点导出即可。这个小工具让我在对接教务数据时省了大量时间。另外强烈建议保存一份“配置清单”,记录导师名额、轮次开放时间、微信AppID等信息,因为类似系统的上线交接往往不止一次,新接手的人靠这份清单可以快速维护。
8. 部署上线与后续扩展
8.1 从开发机到云服务器的部署
部署我选择了某云轻量服务器(2核4G),系统是Ubuntu 22.04。流程大概是这样:安装Python、Nginx、MySQL,克隆代码到/opt/mentor_system,创建虚拟环境,安装依赖,运行迁移和collectstatic,用uWSGI启动Django应用,Nginx反向代理到uWSGI的socket文件,配置HTTPS证书,然后在小程序后台配置合法域名。
uWSGI配置简单来说就是:
[uwsgi] chdir=/opt/mentor_system module=mentor_system.wsgi:application master=true processes=4 socket=/tmp/mentor_system.sock chmod-socket=664 vacuum=trueNginx里核心是配置location /代理到unix:/tmp/mentor_system.sock,并加上SSL证书。这些配置网上都有,关键是记得开防火墙端口,并且用systemd管理uWSGI进程,否则服务器重启后应用不会自动拉起。静态文件处理也很重要,Django的collectstatic会把静态文件收集到一个目录,Nginx直接托管这个目录,不要交给uWSGI。
8.2 HTTPS证书与小程序合法域名
微信小程序的request请求只认HTTPS,而且证书链必须完整。我一开始用自签名证书联调,真机不认。后来用云厂商的免费SSL证书,申请域名后把证书上传到Nginx配置。设置完之后可以在小程序开发工具里点“清缓存”再试,避免旧证书缓存导致问题。另外,小程序后台有request合法域名、uploadFile合法域名、downloadFile合法域名三类,如果后续要做头像上传,必须把上传域名也配好,否则会报错。
8.3 后续可以扩展的方向
这个系统的骨架其实可以扩展成更通用的“师生双选平台”。比如加入导师评分、往届学生评价、科研项目展示、自动匹配算法优化(用学生成绩和导师方向做推荐)、消息提醒(邮件、企业微信、钉钉),还可以把数据汇总成大屏展示页,让学院领导实时看到双选进度。如果让我重做一个版本,我会把前后端放到两个独立仓库,后端用Django REST framework,前端从原生小程序迁移到uni-app方便后续跨端。但就目前这个系统来说,原生小程序 + Django单体已经足够稳定,教学演示和上线使用都没问题。
最后说一点个人体会:做这类系统,技术不是最难的,最难的是把业务规则理清楚。我在做导师选择系统的过程中,花了至少三分之一的时间去问教务老师“如果两个学生同时申请怎么办”“导师拒绝了之后还能不能反悔”“调剂轮次什么时候开启”,这些问题远没有写代码直观,但它们决定了数据表设计和接口状态流转。先把这些规则写成文字,再动手写代码,项目大概率不会跑偏。希望这篇分享能给正在做类似选题的同学一些参考,也欢迎在实际开发中遇到具体问题时再交流。