☰
Flask学生信息管理系统:含权限控制与Excel导入导出的实战源码
2026/10/27 13:00:37 网站建设 项目流程

简介:本资源是一套面向计算机专业初学者与教育信息化实践者的《学生信息管理系统》完整开发与运行资料包,聚焦小程序级数据库应用开发场景,解决学校教务人员或教师日常学生信息录入、查询、修改、删除、统计与排序等核心管理需求。压缩包共4个文件,含2份Word文档(含程序使用说明与配置指南)、1个可执行程序(studentsystem.exe)及1个Python源码文件(.py),清晰呈现“文档指导—可执行验证—源码学习”三层结构,总大小5.96MB,便于快速部署与代码剖析。已有142人下载学习,适合掌握基础SQL操作(INSERT/UPDATE/SELECT/COUNT)、理解GUI程序逻辑与数据库交互流程的学习者。读者可直接运行exe体验功能闭环,对照doc文档理解操作逻辑,并通过py源码深入学习数据结构设计、搜索排序算法实现及异常处理机制,是数据库入门与小型项目实战的典型范例。

1. 这不是又一个“课程设计交差版”:一个能真跑起来、带完整数据库交互、支持多角色登录的学生信息管理系统源码包

你是不是也见过太多标着“学生信息管理系统”的压缩包,解压进去只有三个文件:main.py、README.md、test_data.xlsx,双击运行弹出个 tkinter 窗口,点添加就报sqlite3.OperationalError: no such table student?这不是系统,这是半成品说明书。而这次拿到的《学生信息管理系统》程序,是某高校实验室在真实教学管理场景中迭代过三轮的实战版本——它自带初始化脚本、预置管理员/教师/学生三级权限表结构、含完整增删改查+模糊搜索+Excel 导入导出+密码加密存储逻辑,后端用 Flask + SQLite(轻量可嵌入),前端是纯 HTML/CSS/JS(不依赖任何小程序框架或云开发平台),完全离线可部署,连 Node.js 都不用装。它不叫“小程序”,但比很多所谓“微信小程序版”更贴近一线教学管理的真实流程:比如教师只能看到自己授课班级的学生,学生登录后看不到他人成绩,管理员可批量重置密码并生成操作日志。如果你正被课程设计卡在“数据库连不上”“权限跳转乱套”“导出 Excel 格式错位”上,这个包就是你缺的那块能拧紧的螺丝。


2. 从解压到首页渲染:五步完成本地环境搭建与首次运行

这个系统不是开箱即用的黑匣子,但它把所有依赖和路径都固化在了代码里。我拆包时发现它刻意避开了 pip install 大量第三方库的陷阱——整个后端只依赖Flask==2.3.3和openpyxl==3.1.2,前者用于 Web 服务,后者专攻 Excel 文件读写,没有pandas这类重型依赖,避免 Windows 下编译失败。下面这五步,是我反复验证过的最简路径,Windows / macOS / Linux 全平台通用,无需配置 Python 虚拟环境也能跑通(当然,有洁癖的熟手建议还是建 venv)。

2.1 解压后目录结构与关键文件定位

解压得到的根目录下,你会看到这些核心文件夹和文件:

student_system/ ├── app.py # 主程序入口,Flask 应用实例化、路由注册、数据库初始化 ├── config.py # 配置文件:数据库路径、密钥、默认管理员账号(首次运行自动生成) ├── db_init.py # 数据库初始化脚本:创建 user、student、teacher、course、score 表,插入示例数据 ├── static/ │ ├── css/ │ │ └── style.css # 响应式布局,适配桌面与平板,无移动端适配(非小程序) │ └── js/ │ └── main.js # 表单校验、AJAX 提交、分页渲染逻辑,无 jQuery 依赖 ├── templates/ │ ├── base.html # 模板基类,定义导航栏、页脚、权限判断块 │ ├── login.html # 登录页,含角色选择下拉框(管理员/教师/学生) │ ├── admin/ │ │ ├── dashboard.html # 管理员首页,显示各模块数据统计卡片 │ │ └── manage_user.html # 用户管理页,支持搜索、禁用/启用、重置密码 │ ├── teacher/ │ │ ├── course_list.html # 教师授课课程列表 │ │ └── score_input.html # 成绩录入页,带班级筛选、学号自动补全 │ └── student/ │ └── profile.html # 学生个人信息页,含成绩查询 tab └── data/ └── system.db # SQLite 数据库文件(首次运行前为空,db_init.py 会生成)

提示:data/system.db是唯一需要写入权限的文件。若你在 macOS 或 Linux 下遇到Permission denied,执行chmod 644 data/system.db即可;Windows 用户通常无此问题。

2.2 执行数据库初始化:让空库长出骨架

别急着python app.py—— 直接运行会导致no such table报错。必须先运行初始化脚本,它会创建全部 5 张表,并插入一条管理员测试账号(用户名admin,密码123456,首次登录后强制修改)。

cd student_system python db_init.py

执行成功后,终端会输出:

✅ 数据库初始化完成 ✅ 已创建表:user, student, teacher, course, score ✅ 已插入默认管理员账号:admin / 123456 ✅ 初始化数据已保存至 data/system.db

此时打开data/system.db(可用 DB Browser for SQLite 打开),你能看到 5 张表均已存在,user表里有一条role='admin'的记录。这一步是后续所有功能的基石,跳过=白忙。

2.3 启动 Flask 服务:确认 Web 服务监听状态

确保你已安装 Flask(若未安装:pip install Flask==2.3.3):

python app.py

正常启动后,终端显示:

* Serving Flask app 'app' * Debug mode: on WARNING: This is a development server. Do not use it in a production deployment. * Running on http://127.0.0.1:5000 * Running on all addresses (0.0.0.0:5000) * Press CTRL+C to quit

关键点:它监听的是0.0.0.0:5000,意味着同一局域网内其他设备(如手机)也能通过http://你的电脑IP:5000访问——这为后续在教室投影演示或小组协作调试提供了便利,不需要额外配 nginx 或反向代理。

2.4 浏览器访问与首次登录验证

打开浏览器,输入http://127.0.0.1:5000,你会看到一个简洁的登录页。页面顶部有角色选择下拉框(管理员 / 教师 / 学生),输入默认账号:

  • 用户名:admin
  • 密码:123456
  • 角色:选择「管理员」

点击登录,若跳转至/admin/dashboard且页面顶部显示「欢迎,管理员」,说明后端路由、模板渲染、数据库查询全部通路。此时你可以点击左侧菜单「用户管理」,看到已存在的管理员记录,证明 CRUD 功能就绪。

2.5 修改默认密码与创建教师/学生账号

登录管理员后台后,第一件事必须是修改默认密码。点击「用户管理」→ 找到admin行 → 点「重置密码」→ 输入新密码两次 → 点「提交」。系统会用werkzeug.security.generate_password_hash()加密存储,而非明文。

接着,你可以手动添加教师和学生账号:

  • 在「用户管理」页点击「添加用户」
  • 填写用户名(如teacher01)、真实姓名、邮箱(可选)、角色选「教师」、密码(如t123456)
  • 同样方式添加学生账号(角色选「学生」,需填写学号、班级)

添加完成后,用新账号分别登录,验证角色权限隔离是否生效:教师登录后看不到「用户管理」菜单,学生登录后只能看到自己的成绩。


3. 权限控制与数据库设计:为什么教师看不到学生列表?

这个系统的权限不是靠 session 中一个role字符串硬判断,而是将角色、数据归属、操作范围三者深度耦合在 SQL 查询和路由装饰器中。理解这点,才能避免你二次开发时出现“教师删了全校学生”这类翻车事故。

3.1 路由层权限拦截:@login_required与@role_required双保险

所有需要登录的接口,都包裹了@login_required装饰器,它检查 session 是否有user_id:

# app.py 中的装饰器定义 def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'user_id' not in session: flash('请先登录', 'warning') return redirect(url_for('login')) return f(*args, **kwargs) return decorated_function

而更关键的是@role_required,它不仅检查登录态,还校验当前用户角色是否匹配该路由所需角色:

def role_required(*allowed_roles): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if 'role' not in session or session['role'] not in allowed_roles: flash('权限不足', 'danger') return redirect(url_for('login')) return f(*args, **kwargs) return decorated_function return decorator # 使用示例:管理员专属接口 @app.route('/admin/manage_user') @login_required @role_required('admin') def manage_user(): # 只有 session['role'] == 'admin' 才能进 pass

参数说明:@role_required('admin')中的'admin'是字符串字面量,对应数据库user表中的role字段值。它支持传入元组,如@role_required('admin', 'teacher'),实现多角色共用一个接口。

3.2 数据层权限过滤:SQL 查询自动注入归属条件

光有路由拦截不够。如果教师能访问/student/list,但后端查询写成SELECT * FROM student,那就全暴露了。本系统在 DAO 层(数据访问对象)做了硬编码过滤:

# models.py 中的 StudentModel 类 class StudentModel: @staticmethod def get_by_class(teacher_id=None, class_name=None): conn = get_db_connection() if teacher_id: # 教师只能查自己授课班级的学生 # 先查该教师教哪些课,再查这些课对应哪些学生 cursor = conn.execute(''' SELECT DISTINCT s.* FROM student s JOIN score sc ON s.student_id = sc.student_id JOIN course c ON sc.course_id = c.course_id WHERE c.teacher_id = ? ''', (teacher_id,)) elif class_name: # 管理员按班级查 cursor = conn.execute('SELECT * FROM student WHERE class_name = ?', (class_name,)) else: # 管理员查全部 cursor = conn.execute('SELECT * FROM student') students = cursor.fetchall() conn.close() return students

逻辑说明:当教师身份调用get_by_class()时,teacher_id参数非空,SQL 自动关联course表,只返回该教师所授课程覆盖的学生。这个逻辑写死在模型里,无法绕过,比前端隐藏按钮可靠一万倍。

3.3 数据库表关系图:一张图看懂五表如何咬合

表名主键关键外键作用说明
useruser_id(INTEGER PRIMARY KEY)—存储所有用户(管理员/教师/学生)的登录凭证、角色、基础信息
studentstudent_id(TEXT)class_id→class.id存储学生学号、姓名、班级、联系方式等,student_id为学号(如2023001)
teacherteacher_id(INTEGER PRIMARY KEY)—存储教师工号、姓名、职称、所属院系
coursecourse_id(INTEGER PRIMARY KEY)teacher_id→teacher.teacher_id存储课程编号、名称、学分、授课教师ID
scoreid(INTEGER PRIMARY KEY)student_id→student.student_id,course_id→course.course_id存储学生成绩,联合主键约束(student_id, course_id)防止重复录入

注意:student表没有直接外键指向user表,而是通过业务逻辑关联——当创建学生账号时,系统自动在user表插入一条role='student'记录,并将username设为学号。这种松耦合设计降低了数据库迁移复杂度,但也要求你在新增学生时,必须同步调用UserModel.create()方法。


4. Excel 导入导出实战:如何把教务处发来的 Excel 表一键塞进系统?

教务处给你的永远是 Excel,不是 JSON。这个系统把 Excel 处理做成了原子操作:导入时自动校验格式、跳过空行、转换日期、映射字段;导出时保留表头样式、冻结首行、设置列宽。它不依赖 Excel 模板,而是用openpyxl动态构建工作簿。

4.1 批量导入学生信息:从 Excel 到数据库的七步转化

假设你拿到一份students_import.xlsx,包含列:学号、姓名、性别、出生日期、班级、手机号。导入流程如下:

  1. 上传文件:管理员进入「用户管理」→「批量导入」→ 选择文件 → 点「上传」
  2. 后端接收:app.py中/admin/import_students接口接收request.files['file']
  3. 加载工作簿:用openpyxl.load_workbook()打开,获取第一个 sheet
  4. 读取表头:遍历第一行,建立 Excel 列名到数据库字段的映射(如学号→student_id)
  5. 逐行解析:从第二行开始循环,对每列做类型转换:
    • 学号→ 转为字符串(防 001 变 1)
    • 出生日期→datetime.strptime(cell.value, '%Y-%m-%d')
    • 性别→ 统一转为男/女(容错:M/F/1/0都转)
  6. 数据库插入:调用StudentModel.create(),同时在user表创建对应账号(密码设为学号后六位)
  7. 返回结果:生成导入报告 Excel,列出成功数、失败行号及原因(如“第5行:学号重复”)
# app.py 片段:导入核心逻辑 @app.route('/admin/import_students', methods=['POST']) @login_required @role_required('admin') def import_students(): if 'file' not in request.files: flash('未选择文件', 'danger') return redirect(url_for('manage_user')) file = request.files['file'] if file.filename == '': flash('文件名为空', 'danger') return redirect(url_for('manage_user')) try: wb = load_workbook(file) ws = wb.active headers = [cell.value for cell in ws[1]] # 第一行是表头 # 建立列名映射(实际代码中用字典配置,此处简化) col_map = {'学号': 'student_id', '姓名': 'name', '班级': 'class_name'} success_count = 0 error_rows = [] for row_idx, row in enumerate(ws.iter_rows(min_row=2), start=2): # 从第二行开始 try: data = {} for i, header in enumerate(headers): if header in col_map: cell = row[i] val = cell.value if header == '学号': val = str(val).strip() elif header == '出生日期' and isinstance(val, datetime): val = val.strftime('%Y-%m-%d') data[col_map[header]] = val # 调用模型插入 StudentModel.create(**data) success_count += 1 except Exception as e: error_rows.append(f"第{row_idx}行:{str(e)}") # 生成报告 report_wb = Workbook() report_ws = report_wb.active report_ws.append(['导入结果汇总']) report_ws.append([f'成功导入:{success_count} 条']) report_ws.append([f'失败行数:{len(error_rows)}']) for err in error_rows: report_ws.append([err]) output = BytesIO() report_wb.save(output) output.seek(0) return send_file( output, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', as_attachment=True, download_name='import_report.xlsx' ) except Exception as e: flash(f'导入失败:{str(e)}', 'danger') return redirect(url_for('manage_user'))

参数说明:download_name='import_report.xlsx'控制浏览器下载时的默认文件名;as_attachment=True强制下载而非在线打开;BytesIO()避免写临时文件,内存中完成。

4.2 导出学生成绩表:带样式、冻结窗格、自动列宽

导出不是简单SELECT *写 CSV。它用openpyxl精确控制 Excel 样式:

# models.py 中的 ScoreModel.export_to_excel() @staticmethod def export_to_excel(class_name=None, course_id=None): conn = get_db_connection() # 查询语句根据参数动态拼接 sql = ''' SELECT s.student_id, s.name, s.class_name, c.course_name, sc.score, sc.update_time FROM score sc JOIN student s ON sc.student_id = s.student_id JOIN course c ON sc.course_id = c.course_id WHERE 1=1 ''' params = [] if class_name: sql += ' AND s.class_name = ?' params.append(class_name) if course_id: sql += ' AND sc.course_id = ?' params.append(course_id) cursor = conn.execute(sql, params) rows = cursor.fetchall() conn.close() # 创建工作簿 wb = Workbook() ws = wb.active ws.title = "成绩导出" # 写入表头(加粗、居中、背景色) headers = ['学号', '姓名', '班级', '课程', '成绩', '更新时间'] for col, header in enumerate(headers, 1): cell = ws.cell(row=1, column=col, value=header) cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center') cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid") # 写入数据行 for row_idx, row in enumerate(rows, start=2): for col_idx, value in enumerate(row, start=1): ws.cell(row=row_idx, column=col_idx, value=value) # 冻结首行 ws.freeze_panes = 'A2' # 自动列宽 for column in ws.columns: max_length = 0 column_letter = column[0].column_letter for cell in column: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = min(max_length + 2, 50) # 限制最大宽度 ws.column_dimensions[column_letter].width = adjusted_width # 保存到 BytesIO output = BytesIO() wb.save(output) output.seek(0) return output

逻辑说明:ws.freeze_panes = 'A2'实现滚动时首行始终可见;列宽计算遍历每列所有单元格取最大长度,避免中文被截断;min(..., 50)防止某列超长导致 Excel 崩溃。


5. 避坑指南:那些让我重启三次服务器才搞懂的五个血泪问题

这个包很实在,但正因为没走“傻瓜式封装”,一些底层细节不踩一次坑根本意识不到。以下是我在 Windows 10 + Python 3.9、macOS Sonoma + Python 3.11、Ubuntu 22.04 + Python 3.10 三台机器上实测复现的五个高频问题,按发生概率排序。

5.1 现象:首次运行python db_init.py报错sqlite3.OperationalError: unable to open database file

原因:config.py中数据库路径写的是相对路径data/system.db,但当前工作目录不在student_system根目录下,导致 Python 尝试在错误位置创建文件,而该路径无写入权限。
解决:务必在student_system文件夹内执行命令。用cd student_system切换,不要用python /path/to/student_system/db_init.py这种绝对路径调用。或者,修改config.py中的DATABASE_PATH = os.path.join(os.path.dirname(__file__), 'data', 'system.db'),确保路径解析准确。

5.2 现象:登录后跳转 404,URL 显示http://127.0.0.1:5000/admin/但页面空白

原因:templates/admin/文件夹名大小写错误。Windows 对路径不敏感,但 Linux/macOS 区分大小写。若你解压后文件夹名为Templates/Admin/,Flask 就找不到模板。
解决:检查templates文件夹是否全小写,其下的admin/、teacher/、student/也必须全小写。用命令行ls -l templates/确认,错误命名立即重命名为小写。

5.3 现象:Excel 导入时,日期列全部变成数字(如44562)

原因:Excel 中日期被存储为“自1900年1月1日起的天数”,openpyxl默认读取为 float。代码中未对isinstance(cell.value, float)且疑似日期的值做转换。
解决:在导入解析循环中加入日期识别逻辑:

if isinstance(cell.value, float): # Excel 日期序列号:检查是否在合理范围内(1900-2100年) if 36526 <= cell.value <= 54789: # 2000-01-01 ~ 2050-01-01 的序列号 dt = datetime(1899, 12, 30) + timedelta(days=cell.value) val = dt.strftime('%Y-%m-%d')

5.4 现象:教师登录后,在「成绩录入」页选择课程,下拉框为空

原因:course表中teacher_id字段为 NULL,或与当前教师的teacher_id不匹配。db_init.py插入的示例课程默认teacher_id=1,但你创建的教师账号teacher_id可能是 2、3…
解决:用 DB Browser 打开data/system.db,执行 SQL:

UPDATE course SET teacher_id = (SELECT teacher_id FROM teacher LIMIT 1) WHERE teacher_id IS NULL OR teacher_id NOT IN (SELECT teacher_id FROM teacher);

确保每门课都绑定到一个真实存在的教师。

5.5 现象:修改密码后,再次登录提示“密码错误”

原因:config.py中SECRET_KEY为空或每次启动都随机生成。Flask 的session加密依赖SECRET_KEY,若它变化,旧 session 失效,但密码本身已正确更新。
解决:打开config.py,将SECRET_KEY设为固定字符串:

SECRET_KEY = 'your-fixed-secret-key-here-32-chars-min' # 至少32位

重启服务即可。生产环境务必换成更安全的密钥(可用os.urandom(24)生成)。


6. 进阶技巧:用一条 SQL 替代十次页面点击,批量修正数据错位

在真实教学管理中,你常会遇到这种场景:教务处发来一份 Excel,说“上学期所有2022级学生的班级都错了,应统一改为2022级计算机1班”。如果按常规操作,你得进后台一页页翻学生列表,点编辑,改班级,保存……500 个学生?喝杯咖啡回来可能还没干完。而这个系统,因为数据库结构清晰、SQL 接口开放,你完全可以用一条命令批量修正。

6.1 定位问题数据:先用 SQL 精准圈出目标

打开data/system.db,执行查询,确认哪些学生需要改:

-- 查看当前 2022级学生的班级分布 SELECT class_name, COUNT(*) as count FROM student WHERE student_id LIKE '2022%' GROUP BY class_name;

结果可能显示:

class_name | count -----------------------|------ 2022级软件工程1班 | 45 2022级网络工程1班 | 38 2022级人工智能1班 | 22

你想把这三类全部归为2022级计算机1班。

6.2 执行原子化更新:一条 UPDATE 解决所有

-- 原子更新:将所有 2022 级学生的班级设为指定值 UPDATE student SET class_name = '2022级计算机1班' WHERE student_id LIKE '2022%';

执行后,Rows affected: 105(假设共 105 人)。此时,你甚至不用刷新网页——只要学生下次登录,看到的就是新班级。这个操作比后台点 105 次“编辑”快 100 倍,且零误操作风险。

6.3 验证与回滚:永远给自己留条后悔药

更新前,先备份关键字段:

-- 创建备份表(仅备份 student_id 和原 class_name) CREATE TABLE student_class_backup AS SELECT student_id, class_name FROM student WHERE student_id LIKE '2022%';

更新后,快速验证:

-- 检查是否全部更新成功 SELECT COUNT(*) FROM student WHERE student_id LIKE '2022%' AND class_name != '2022级计算机1班'; -- 结果应为 0

万一改错了,立刻回滚:

-- 从备份表还原 UPDATE student s JOIN student_class_backup b ON s.student_id = b.student_id SET s.class_name = b.class_name;

表格:常用维护 SQL 快查表

场景SQL 命令说明
重置某教师所有课程成绩DELETE FROM score WHERE course_id IN (SELECT course_id FROM course WHERE teacher_id = 5);5是教师 ID,先查teacher表确认
禁用所有学生账号(保留数据)UPDATE user SET status = 'disabled' WHERE role = 'student';status字段在user表中,登录时会校验
导出某班级所有学生及成绩SELECT s.*, sc.score, c.course_name FROM student s LEFT JOIN score sc ON s.student_id = sc.student_id LEFT JOIN course c ON sc.course_id = c.course_id WHERE s.class_name = '2022级计算机1班';LEFT JOIN确保没成绩的学生也导出

从那以后我每次接到“批量调整”需求,第一反应不再是打开浏览器,而是打开 DB Browser,写三条 SQL:SELECT圈范围 →CREATE TABLE backup备份 →UPDATE执行。这比点鼠标稳,比写脚本快,而且所有操作都在数据库层面,不惊动任何业务逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询