ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python和Flask搭建高校新生报到管理系统:从需求到部署的完整实战

用Python和Flask搭建高校新生报到管理系统:从需求到部署的完整实战 每年八月底到九月中旬学校信息中心基本全员进入“战备状态”。这个项目就是用 Python 和 Flask 框架在最短时间里把迎新流程线上化让新生到校之后不再拿着纸质流程单去各个窗口排队。它的核心价值是把教务系统里的录取名单、财务的缴费状态、后勤的宿舍分配、院系的报到确认这几套原本各管各的数据整合到一个可查询、可统计、可追踪的平台上。系统上线后现场老师和学生志愿者只需要扫一个二维码就能确认新生身份后台实时能看到报到率、各院系进度、缴费进度对比往年只能用 Excel 汇总、从早忙到晚的场景提升非常明显。如果你正打算做类似的管理系统不管是自学 Flask、拿它当课程设计选题还是学校信息中心想自建工具这个项目都很值得参考。项目名里的“报道”实际工作里大家更习惯写成“报到”。我下面的内容都按这个场景来讲核心还是新生入学报到管理系统。1. 项目背景与需求拆解1.1 新生报到流程的真实痛点在做这个系统之前我们学校的新生报到基本还是传统模式新生到校后先找到自己学院的迎新点出示录取通知书和身份证志愿者在一堆纸质表格里翻名字找到之后在名单后面打勾再引导去缴费、办宿舍、领军训物资。整个过程有几个非常要命的问题。第一个是数据割裂。教务系统里有录取名单财务那边有缴费名单后勤有宿舍安排学工有绿色通道申请但每个部门的数据只在各自手里现场对一个新生要查好几遍信息。新生说自己已经交过费了财务那边的老师还得去翻台账。第二个是统计滞后。迎新那两三天学校领导最关心的就是实时报到率但传统模式下当天晚上能把各学院手工上报的数据汇总出来都不错了更不要提分钟级的刷新。第三个是异常情况处理困难。新生可能改名字、换专业、申请绿色通道、晚到、走错校区这些情况一旦发生纸质流程基本就要重来志愿者要带着学生来回跑好几个窗口重新签字。所以这个项目的核心目标很明确让所有报到数据有一个统一入口让每个角色都只看到自己需要的那部分并且让现场操作从“翻表打勾”变成“扫码完成”。一句话概括需求就是把“线下一件事”变成“线上一条流水线”。1.2 技术选型为什么是 Python Flask技术选型上我当时没有太多犹豫直接定了 Python Flask原因有几个。首先是开发团队的技术栈。我们信息中心的几个人平时用 Python 做数据处理最多用 Django 也能写但 Flask 在这个场景下明显更合适。Flask 轻、灵活、自由度高一个迎新系统虽然业务不算复杂但每年都会有临时需求比如今年分管校领导要看某个维度的统计报表明天现场要加一个“是否领取学生卡”的选项Flast 能很快改完重启不会有 Django 那一套固定项目结构带来的束缚感。其次是生态非常合适。Flask 的配套扩展比很多框架都成熟SQLAlchemy 做 ORMFlask-Login 做登录会话Jinja2 做模板渲染WTForms 做表单校验Pandas 直接读 Excel 导入名单qrcode 库生成报到二维码。这些库本身都是各自领域用得最多的出问题搜一下就有答案对开发效率和排障都有利。另外一点是对团队成员友好。迎新期间迎新系统出问题值班的人不一定是最初的开发人员。Flask 项目结构简单、路由清晰一个小功能就是几行代码临时接手的人看半小时就能上手这对学校这种小团队来说非常重要。1.3 系统整体设计思路整体设计上我参考了“一个主表 一圈记录 三层权限”的思路。一个主表是新生名单表所有学生的基本信息、录取学院、专业、班级、联系电话都在这里是全系统的“地基”。一圈记录是各个业务环节产生的流水包括报到确认、缴费核验、宿舍分配、绿色通道申请、办理完成时间这些记录都通过 student_id 关联到主表谁在什么时候办了什么业务一目了然。三层权限是系统管理员、学院迎新账号、现场扫码操作用户每一层能看能操作的范围不一样避免一个账号通杀全场。数据流也很简单开学前管理员从教务系统导出录取名单导入系统后自动生成每个学生的报到二维码新生在来校前可以通过短信或小程序查到自己的学号、班级、报到码现场老师扫码后进入办理页完成身份核验、确认缴费、登记宿舍等操作后台统计页面实时汇总所有数据。这条链路里最核心的不是某个花哨功能而是每一步操作都必须可追溯、可反查、可统计。2. 核心模块与数据模型设计2.1 功能模块划分这个系统按业务可以切成七个模块下面这个表格能比较清楚看出每个模块的职责和主要使用角色。模块名称核心功能主要角色新生名单导入从 Excel 导入录取名单、查重、更新信息系统管理员二维码管理为每个新生生成报到码、支持打印/重发系统管理员、新生预报到新生来校前填写到校时间、交通方式新生现场扫码核销扫码确认身份、办理报到登记院系迎新操作员缴费与住宿核验核验缴费状态、办理宿舍分配财务/后勤操作员绿色通道困难生信息登记、缓缴申请审核学工/辅导员数据看板实时报到率、各院系进度、分时段趋势校领导、管理员模块划分里我特别想强调一点不要把“缴费”和“报到”做成强耦合。实际业务里经常有新生还没缴费就先来报到的情况就算财务要求“先缴费后办理”系统层面也应该允许现场操作员先提交信息、再单独处理异常否则一旦财务系统数据同步延迟现场就会卡死。2.2 数据库表设计与关系数据库我用的是 MySQLORM 用 Flask-SQLAlchemy。核心表一共五张student、checkin_record、user、fee_record、dormitory_assign。student 表是主表关键字段包括 stu_no考生的考号唯一、name、gender、dept_name、major_name、class_name、phone、report_status、qr_token、checkin_time。这里要说明几个设计动机。为什么直接存 dept_name、major_name 而不是只存学院 ID因为新生报到期间的查询几乎都是“按学院汇总”“按专业搜索”如果每次都要关联学院表SQL 写起来复杂索引也难命中。直接把学院名称冗余进来配合普通索引统计查询就非常快。另外 qr_token 字段存的是扫码时用的唯一令牌不是学生 ID 明文这样就算二维码被别人拍照不通过系统的扫码接口也拿不到有效信息。checkin_record 表是现场操作的流水表核心字段是 student_id、step、operator、remark、created_at。设计上比较关键的是加了一个联合唯一约束UniqueConstraint(student_id, step)目的是防止同一环节被重复提交。这个约束在并发场景下能兜底后面我会再讲。fee_record 和 dormitory_assign 结构类似都是 student_id 加业务字段加操作时间。绿色通道我并没有单独建表而是复用了 student 表里的一个 apply_green_channel 布尔字段加一个缓缴理由字段因为绿色通道在真实业务里其实只关心“有没有申请、批没批、缓缴多少”不需要很复杂的子表。用 utf8mb4 编码是我的另一个固定操作。很多老系统还在用 utf8遇到新生姓名里带生僻字就会出现乱码这种问题在报到现场特别尴尬所以建库时一定要指定CHARACTER SET utf8mb4。2.3 状态机设计与流程闭环新生报到状态我设计了五个pending未报到、pre_registered预报到完成、arrived已到校、in_progress现场办理中、completed办结。每一步都记录时间戳这样既能知道当前有多少人“正在办理中”也能算出每个新生从到校到办结平均用了多长时间。为什么用状态字段加时间戳的组合而不是简单给每个业务环节设布尔值因为现场操作员和领导看的维度不一样操作员关心“这个学生进行到哪一步了”领导关心“现在有多少人正在排队、今天办结了多少”。状态字段能直观回答前者而时间戳能支撑后者的趋势统计。给站内大屏展示用状态计数给决策分析用时间差计算缺一不可。流程闭环体现在二维码的生命周期上。导入名单时生成二维码此时状态是 pending新生预报到填完信息后变成 pre_registered现场第一次扫码后变成 arrived办理完缴费、住宿等环节后变成 in_progress最后一个环节点“完成办理”后变成 completed同时记录 checkin_time。整个链路不能跳跃这样如果有人把一个没预报到的学生直接拖到现场办理系统会提醒先补预报到信息避免信息缺漏。3. 实操从零搭建核心功能3.1 环境准备与项目初始化实际开发时我用了 Python 3.8现在大家用 3.10、3.11 也完全没问题。先建议用虚拟环境把项目依赖隔离我一般习惯用 venv 或 conda避免把系统 Python 环境搞乱。安装依赖这一块核心是几个库pip install flask flask-sqlalchemy flask-login pandas openpyxl qrcode pillow gunicorn值得提醒的是 pandas 读取 Excel 需要 openpyxl 库很多新手只装 pandas 就报错其实是少了这一步。qrcode 库需要搭配 pillow 才能生成图片文件也是很容易漏掉的点。项目结构我习惯按功能分目录不要把所有代码塞进一个 app.py。下面是我用的结构new_student_checkin/ ├── app/ │ ├── __init__.py # 创建 Flask 实例、注册蓝图 │ ├── models.py # SQLAlchemy 数据模型 │ ├── views/ │ │ ├── admin.py # 管理端路由导入、二维码、权限 │ │ ├── student.py # 新生端路由预报到、查询状态 │ │ ├── checkin.py # 现场扫码核销路由 │ │ └── stats.py # 数据看板路由 │ ├── templates/ │ ├── static/ ├── config.py # 数据库连接、密钥等配置 ├── run.py # 启动入口 ├── requirements.txt └── deploy/init.py 里做几件事创建 Flask 应用、加载配置、初始化 SQLAlchemy、注册蓝图。Flask-SQLAlchemy 的初始化方式有一个小坑如果先创建 db 对象再在 models 里定义模型必须在应用上调用db.init_app(app)否则 import 模型时会报 “db object has no attribute Column”这个我在早期踩过几次。3.2 实现新生导入与报到码生成新生名单导入是整个系统的第一步。管理端上传 Excel 后我用 pandas 读入数据做一轮清洗再批量写入。import pandas as pd from app import db from app.models import Student from uuid import uuid4 def import_students_from_df(df): count 0 for _, row in df.iterrows(): stu_no str(row.get(考生号, )).strip() name str(row.get(姓名, )).strip() if not stu_no or not name: continue # 跳过空行 if Student.query.filter_by(stu_nostu_no).first(): continue # 已存在则跳过 student Student( stu_nostu_no, namename, genderstr(row.get(性别, )), dept_namestr(row.get(学院, )), major_namestr(row.get(专业, )), class_namestr(row.get(班级, )), qr_tokenuuid4().hex ) db.session.add(student) count 1 db.session.commit() return count这里面几个细节考生号统一转成字符串是因为 Excel 里学号、考号经常被显示成科学计数法或者变成长整型用 pandas 读的时候可能出现精度丢失所以我建议dtype{考生号: str}读文件或者读出后统一转字符串。其次导入要做幂等不能重复导入同一批名单把数据搞两遍所以按考生号去重判断。报到码我用 token 而不是学生 ID 明文token 用 UUID 生成。给学生的二维码内容是系统的一个链接比如https://checkin.example.edu.cn/e/{qr_token}新生打开这个链接可以直接看到自己的预报到状态现场人员扫码则进入核销页面。生成图片的关键代码import qrcode def generate_qr(token, output_path): url fhttps://checkin.example.edu.cn/e/{token} img qrcode.make(url) img.save(output_path)如果需要在纸质材料上打印二维码建议图片设成 300 DPI 以上不然扫码枪识别率很低。这一点后面我会再提。3.3 实现扫码核销与报到记录写入现场扫码是使用频率最高、并发压力最大的接口。我把它设计成 POST 接口前端页面扫码后自动把 token 传上来。from flask import request, jsonify, redirect, url_for app.route(/api/checkin, methods[POST]) def api_checkin(): data request.get_json() token data.get(token, ) operator data.get(operator, ) student Student.query.filter_by(qr_tokentoken).first() if not student: return jsonify({code: 404, msg: 未找到该新生记录}) if student.report_status completed: return jsonify({code: 200, msg: 该新生已完成报到, student: student.name}) student.report_status arrived db.session.add(CheckinRecord( student_idstudent.id, steparrive, operatoroperator, remark现场扫码核销 )) db.session.commit() return jsonify({code: 200, msg: ok, student: student.name})这段逻辑简化后看起来很简单但真实运行中有几个必须处理好的点。一是重复提交问题。现场网络不稳定扫码人员点完“确认”后请求超时通常会再点一次如果不做处理流水表里就出现两条“arrive”记录统计就会多算。解决办法就是我前面提到的联合唯一约束uk_student_step。第二次插入时数据库会报 IntegrityError我们在代码里捕获这个异常直接返回“已办理过”即可。from sqlalchemy.exc import IntegrityError try: db.session.add(CheckinRecord(student_idstudent.id, steparrive, operatoroperator)) db.session.commit() except IntegrityError: db.session.rollback() return jsonify({code: 200, msg: 该环节已办理无需重复提交})再一个是事务边界。如果一个步骤要同时更新 student 表状态和插入 CheckinRecord两件事必须在一个事务里完成我上面把状态更新和插入操作放在一起再 commit目的就是这个。如果先 commit 状态再插流水中途报错就会造成状态和流水不一致。3.4 数据看板与查询统计这个系统里校领导最看重看板。看板页我用了 SQLAlchemy 的聚合查询核心指标包括总报到人数、已报到人数、报到率、各学院报到排行榜、分时段办理趋势。其中一个最简单的统计写法from sqlalchemy import func def get_dept_stats(): rows (db.session.query( Student.dept_name, func.count(Student.id).label(total), func.count(Student.checkin_time).label(finished) ) .group_by(Student.dept_name) .all()) result [] for dept, total, finished in rows: result.append({ dept: dept, total: total, finished: finished, rate: round(finished / total * 100, 1) if total else 0 }) return sorted(result, keylambda x: x[rate], reverseTrue)这里我直接用count(Student.checkin_time)来统计完成人数比另加一个 status 条件简单得多因为 checkin_time 只有办结时才会写入没办结就是 NULL。看板页我建议直接用 Fetch API 每隔 30 秒拉一次数据再渲染不用开 WebSocket。迎新现场对实时性的要求没那么高30 秒内的数据延迟完全可接受也能减少服务器并发压力。数据缓存方面实时统计接口每次全表 group by 虽然不快但数据量不大的时候压力也不大。一旦学校规模上万或者想统计半分内趋势建议给统计结果加 Redis 缓存每 30 秒失效一次。我第一年没加缓存高峰期一次统计接口 300 多毫秒看板 10 个人同时打开就有点卡加完缓存直接降到 10 毫秒以内。4. 部署上线与实战排查4.1 生产环境部署开发调试时可以直接python run.py但正式上线绝对不能再用 Flask 内置服务器这一点是新手最常犯的错误。我用 gunicorn nginx 做过最稳妥的组合。先用 gunicorn 起应用gunicorn -w 4 -b 127.0.0.1:8000 run:app-w 4表示 4 个 worker 进程具体数量可以参考CPU 核心数 * 2 1。迎新高峰期有人会盲目加 worker其实 worker 太多反而因为 GIL 限制和数据库连接数瓶颈导致性能下降实测 4 个 worker 处理扫码接口足矣。systemd 服务配置可以保证进程挂了自动拉起[Unit] DescriptionNew Student Checkin Service Afternetwork.target [Service] Userweb WorkingDirectory/opt/new_student_checkin ExecStart/opt/new_student_checkin/venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 run:app Restartalways [Install] WantedBymulti-user.targetnginx 主要作用是反向代理、静态文件处理和 Https 证书。静态资源不要直接走 Flask否则 gunicorn 处理图片、CSS、JS 会白白消耗 worker 资源。Https 一定要上报到现场很多老师手机会直接在公共 Wi-Fi 下扫码明文 HTTP 传输 token 容易被中间人截获。环境变量管理我很早就改成从 config.py 读取.env文件把数据库密码、SECRET_KEY 都放到环境变量里避免密码硬编码在代码仓库。SECRET_KEY 忘记设置的话 Flask-Login 会报错而且每次重启会话全部失效这个细节会在现场造成尴尬场面。4.2 常见问题速查与排查思路我把实际用了一年多踩过的坑整理成一张排查表希望能给做类似系统的朋友省点时间。现象原因解决思路导入 Excel 后中文变问号数据库使用 utf8 而非 utf8mb4建库时指定CHARACTER SET utf8mb4连接串加?charsetutf8mb4新生手机端扫码打开空白二维码 URL 生成时用了 localhost 或内网地址改为对外可访问的正式域名或公网 IP扫码枪扫出字符串不跳转二维码内容被短信/聊天软件截断二维码内容尽量缩短不要带多余参数现场操作员反复点按钮流水表重复记录缺少唯一约束对 (student_id, step) 加 UniqueConstraint看板统计接口高峰期变慢大量实时聚合查询没有缓存加 Redis 缓存或预计算每日日报表报到完成但 checkin_time 为空状态更新和办结时间写入不在同一事务在同一操作里统一赋值并一次 commitgunicorn 启动后端口已被占用之前进程未正常结束lsof -i:8000找到进程再 kill新生名字带生僻字乱码Excel 编码或数据库编码不一致CSV 用 utf-8-sigExcel 用 openpyxl 读库表用 utf8mb4这里面最容易被忽略的是二维码 URL 问题。我第一年测试时全用内网 IP开发环境一切正常到了迎新现场才发现新生手机访问的是场馆内网地址根本打不开。做二维码内容前一定要先确定最终部署的域名否则所有码都要重新生成。4.3 几个值得记住的避坑经验第一年迎新结束后我复盘了几件事这里挑三个最想分享的。第一个经验是现场必须有“离线兜底”。扫码核销依赖网络但迎新现场最容易出现公共网络拥塞甚至完全断网的情况。我后来的方案是每天凌晨把当天新生名单导出成全量 Excel交给各学院志愿者一旦系统瘫痪马上切回纸质登记事后补录。系统再先进也要给极端场景留一手。第二个经验是权限控制要收敛。现场有很多勤工俭学学生和临时志愿者他们只需要“扫码-确认”这一个动作所以操作账号不要给查询名单列表、导出数据等高级权限。我给志愿者账号做成了只允许访问/api/checkin和对应页面其他路由一律拦截。权限范围越小出事的概率越小。第三个经验是晚上前后一定要跑一遍数据一致性脚本。报到期间系统 24 小时运行晚上人少的时候我跑一次全量检查把状态为 arrived 但没有 CheckinRecord 的、或者 report_status 与流水不一致的数据挑出来修复。这种静态检查脚本不到五十行但每年都帮我发现过手工补录产生的脏数据比上线前写多少测试都管用。这个项目第二年复用时我最大的体会是系统设计得再完整也永远要为“人会操作失误、网络会抽风、流程会临时变”留出余地。代码里多一个事务保护现场就少一次返工数据库里多一个唯一约束统计报表就多一分可信。希望这份从思路到落地再到排障的完整记录能帮你在做新生入学报到管理系统时少走几个弯路。
返回列表