☰
Python Flask财务管理系统毕业设计实战:从数据库设计到报表可视化
2026/10/4 4:22:13 网站建设 项目流程

毕业设计做财务管理系统,Python方向的话,这个选题可以说是既稳妥又实用。每年都有大量同学选财务类题目,但真正能做出完整度、能顺利通过答辩的其实没那么多。我这个项目从需求分析到编码实现再到论文写作,全程走完了一遍,程序、源码、LW文档(论文)都齐了,今天把完整思路和踩坑经历理一遍,给正在做这个题目的同学当个参考。

先说清楚这个东西到底是什么。Python财务管理系统,简单说就是用一个Web应用或桌面应用,把日常的收入、支出、报销、报表统计这些事情管理起来。和Excel记账最大的区别在于,它有一套完整的用户体系、数据分类体系、权限模型和可视化报表,数据是存进数据库里的,可以做复杂查询和统计,而不是手动在一张表里拉公式。适合的参考人群很明确:计算机相关专业、毕业设计选题涉及Web开发或信息管理系统的同学,尤其是准备用Python方向来完成毕设的。

我用的技术栈是Python Flask + SQLite,前端用的是Bootstrap + jQuery + ECharts,架构上采用MTV模式。之所以选择Flask而不是Django,核心原因是毕业设计的代码需要能够被清晰讲解,Flask的代码量轻、路由逻辑直观、数据库操作可控,答辩时老师问到底层实现,你能把每一个环节讲清楚,这对通过率很有利。

1. 项目整体设计与思路拆解

1.1 技术选型:为什么用Python Flask而不是Django

很多同学一开始就会被「框架选择困难症」卡住。Django功能强大、自带Admin后台,但它的「魔法」太多了,ORM、自动生成的Admin、中间件、信号机制……这些对毕设来说其实是一把双刃剑。你用Django可以很快把网站搭起来,但答辩时老师问「你这个用户登录的Session是怎么管理的」「ORM生成的SQL是什么样的」,你会发现自己答不上来。

Flask则完全不同。它是一个微框架,核心只做路由和视图这两件事。数据库操作要么用Flask-SQLAlchemy,要么直接写原生SQL;表单处理要么用Flask-WTF,要么手动接收请求参数。每一步的逻辑都摊在明面上,你可以「一行代码一行代码」地给老师讲清楚。

我的选型结论:

  • 框架:Flask 2.x,轻量、社区文档多、中文资料丰富
  • 数据库:SQLite 3,文件型数据库,无需单独安装服务端,适合毕设展示和演示
  • ORM:Flask-SQLAlchemy,保留ORM便利性的同时,底层SQL可查、可讲
  • 前端:Bootstrap 4 + jQuery,不需要前端工程化,模板直接渲染,减少调试成本
  • 图表可视化:ECharts 5,财务系统必须有趋势图、占比图,答辩加分项

这套组合的最大优势是:环境好配、代码量适中、逻辑透明。在一台干净的Windows电脑上,从装Python到项目跑起来,最快20分钟就能完成。

1.2 功能模块划分与设计思路

财务管理系统属于典型的信息管理系统(MIS),核心就是增删改查加统计。但直接把需求写成「实现用户的增删改查」肯定拿不到高分,必须把业务场景融进去。

我最终确定的模块划分:

  1. 用户认证模块:登录、注册、退出登录,密码Hash存储
  2. 收入管理模块:记录工资、兼职、奖金等收入项,支持按类别筛选
  3. 支出管理模块:记录餐饮、购物、交通、住房等支出项,支持备注和分类
  4. 财务管理概览:展示总收入、总支出、结余、近期交易记录
  5. 报表统计模块:月度收支趋势、分类占比、自定义日期区间查询
  6. 分类管理模块:内置分类字典,也支持用户自定义分类
  7. 个人中心模块:修改密码、查看个人信息

这里有一个很关键的设计思路:不要做「纯粹的」财务系统,而是做「带权限的」财务系统。也就是说,系统要区分管理员和普通用户,至少要有两个角色。原因很简单,毕设题目里如果只有一张表、一个用户自己在录入数据,功能就撑不起论文的篇幅,答辩时也没有可扩展性可聊。

所以我加了一层「家庭成员」概念:管理员(户主)可以查看所有成员的收支记录,普通成员只能查看和修改自己的记录。这样一来,数据权限过滤就成了系统的核心创新点,也就有了技术深度可写。

1.3 开发环境与工具准备

这一部分看起来基础,但很多同学挂在环境上。Python版本我建议直接用3.10或3.11,不要用太旧的3.6/3.7,Flask新版本对高版本Python支持更好,而且新版Python的报错信息更友好。

我实际使用的环境清单:

  • 操作系统:Windows 10/11,64位
  • Python 3.10.11(安装时勾选「Add Python to PATH」)
  • PyCharm Community Edition(社区版免费,够用)
  • Flask 2.2.5、Flask-SQLAlchemy 3.0、Werkzeug 2.3
  • 数据库可视化工具:SQLiteStudio(免费,轻量)或 DBeaver

创建虚拟环境这一步强烈建议执行。命令很简单:

# 在项目根目录打开终端 python -m venv venv # Windows下激活虚拟环境 venv\Scripts\activate # 安装依赖 pip install flask flask-sqlalchemy # 生成依赖清单 pip freeze > requirements.txt

虚拟环境的作用是隔离不同项目的包版本,防止你电脑上之前装过Django或其他版本把环境搞乱。不过要注意的是,requirements.txt里生成的版本号很长,建议手动精简一下,只保留核心依赖。

2. 数据库设计与核心数据结构

2.1 数据表规划:从需求到表结构

财务系统的数据表规划我有体会。一开始我直接设计了一张超大的记录表,所有字段塞在一起,做到后面发现查询越来越乱。实际经验是:先想清楚查询需求,再反过来定表结构。

我的最终表结构是4张表,这个规模对毕设来说是恰到好处的——既不是单表那么单薄,又没有复杂到难以讲解:

  1. users 用户表:存登录账号、密码Hash、角色、昵称
  2. categories 分类表:存收支类型(收入/支出)和分类名称
  3. transactions 交易记录表:存每一笔收支,关联用户和分类
  4. budgets 预算表:存月度预算(可选功能,我后期加入的)

2.2 关键表设计与字段说明

用户的表结构如下:

字段名类型说明
idINTEGER 主键自增用户ID
usernameVARCHAR(50) 唯一登录名
password_hashVARCHAR(128)密码Hash,绝不存明文
nicknameVARCHAR(50)昵称
roleVARCHAR(20)admin / user
created_atDATETIME创建时间

交易记录表结构:

字段名类型说明
idINTEGER 主键自增记录ID
user_idINTEGER 外键关联用户
category_idINTEGER 外键关联分类
amountFLOAT金额,保留2位
typeVARCHAR(10)income / expense
noteVARCHAR(200)备注
trade_dateDATE交易日期
created_atDATETIME创建时间

这里有一个非常重要的设计细节:金额字段用FLOAT还是DECIMAL?财务系统里涉及金钱,原则上应该用DECIMAL避免浮点误差,但SQLite里对DECIMAL的支持并不完美,所以我实际用的是FLOAT,但在前端展示和统计时统一用Python的round()做四舍五入,同时配合页面上的金额校验规则(不允许负数和超过2位小数)。如果你用的是MySQL,建议直接用DECIMAL(10, 2),更严谨。

2.3 数据表创建与初始化

使用Flask-SQLAlchemy创建表非常简单,以下是模型定义的简化示例:

from flask_sqlalchemy import SQLAlchemy from datetime import datetime from werkzeug.security import generate_password_hash, check_password_hash db = SQLAlchemy() class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50), unique=True, nullable=False) password_hash = db.Column(db.String(128), nullable=False) nickname = db.Column(db.String(50)) role = db.Column(db.String(20), default='user') created_at = db.Column(db.DateTime, default=datetime.now) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) class Transaction(db.Model): __tablename__ = 'transactions' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id')) category_id = db.Column(db.Integer, db.ForeignKey('categories.id')) amount = db.Column(db.Float, nullable=False) type = db.Column(db.String(10), nullable=False) note = db.Column(db.String(200)) trade_date = db.Column(db.Date, default=datetime.now) created_at = db.Column(db.DateTime, default=datetime.now)

在项目首次启动时,我会检查数据库文件是否存在,如果不存在就自动创建所有表和默认的几类分类数据。这样做的好处是,答辩现场换了一台电脑,项目也能「干净地」从零跑起来,而不用手动导入SQL文件。

3. 核心功能实现与关键代码解析

3.1 登录与权限控制的实现

用户认证是财务系统的第一道关卡。登录的核心安全检查包括:用户名是否存在、密码Hash是否匹配、Session会话保持、页面访问权限拦截。

我用Flask的session来实现简单的登录状态管理。登录成功后写入用户ID和角色,后续每个需要权限的页面都会先检查session中是否有登录标记,然后再根据角色判断是否可以访问。

from flask import session, redirect, url_for, flash, render_template, request from functools import wraps # 登录装饰器 def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'user_id' not in session: return redirect(url_for('auth.login')) return f(*args, **kwargs) return decorated_function # 管理员权限装饰器 def admin_required(f): @wraps(f) def decorated_function(*args, **kwargs): if session.get('role') != 'admin': flash('需要管理员权限', 'warning') return redirect(url_for('main.index')) return f(*args, **kwargs) return decorated_function

这个装饰器是整个系统的核心安全机制。我在写论文时,重点讲解了装饰器的工作机制:请求进来先去session里检查登录标记,没有就重定向到登录页;这个逻辑统一复用,防止了每个视图函数里重复写判断代码。

这里有个很常见的坑:Flask的session默认使用签名Cookie,不要在session里存敏感数据,比如用户明文密码。只存user_id和role就够了,这是安全实践的基本要求。

3.2 记账功能:收入支出与分类管理

记账功能的本质是「往transactions表插入一条记录,同时带上用户ID、分类、金额、日期、备注」。但看似简单,实际涉及到几个交互细节:

  1. 收入与支出共用一张表还是分开两张表?——我共用一张表,用type字段区分。这样统计时用SUM结合GROUP BY就能同时算出总收入、总支出,不用做表连接。
  2. 金额合法性校验——前端做一次、后端再做一次。后端校验是必须的,不能只依赖前端。

代码示例,后端处理记账表单:

@main.route('/transaction/add', methods=['POST']) @login_required def add_transaction(): amount = request.form.get('amount') category_id = request.form.get('category_id') type = request.form.get('type') note = request.form.get('note', '').strip() trade_date = request.form.get('trade_date') # 后端校验 try: amount = float(amount) if amount <= 0: raise ValueError('金额必须大于0') if round(amount, 2) != amount: raise ValueError('金额最多保留2位小数') except ValueError as e: flash(str(e), 'danger') return redirect(request.referrer) txn = Transaction( user_id=session['user_id'], category_id=int(category_id), amount=amount, type=type, note=note, trade_date=datetime.strptime(trade_date, '%Y-%m-%d') ) db.session.add(txn) db.session.commit() flash('记账成功', 'success') return redirect(url_for('main.index'))

我在这个部分有一个体会:日期处理一定要统一格式。前端传过来的是字符串,后端统一用strptime转成Date类型再入数据库。如果你不转换直接存字符串,后续按月统计时会非常痛苦。

3.3 数据统计与可视化报表

数据可视化是毕业设计最容易拿分的地方,因为它「肉眼可见」地体现了系统的实用性。我选用了ECharts,通过前后端分离的方式动态加载数据。

核心接口返回JSON格式的统计结果:

@main.route('/api/stats') @login_required def api_stats(): user_id = session['user_id'] # 当月收入统计 income = db.session.query( func.coalesce(func.sum(Transaction.amount), 0) ).filter( Transaction.user_id == user_id, Transaction.type == 'income', func.strftime('%Y-%m', Transaction.trade_date) == datetime.now().strftime('%Y-%m') ).scalar() expense = db.session.query( func.coalesce(func.sum(Transaction.amount), 0) ).filter( Transaction.user_id == user_id, Transaction.type == 'expense', func.strftime('%Y-%m', Transaction.trade_date) == datetime.now().strftime('%Y-%m') ).scalar() balance = income - expense return jsonify({ 'income': round(income, 2), 'expense': round(expense, 2), 'balance': round(balance, 2) })

ECharts在前端通过Ajax获取数据后渲染折线图和饼图。折线图展示近6个月的收支趋势,饼图展示支出分类占比。

这里有个经验:SQLite的日期函数和MySQL不一样。SQLite用strftime('%Y-%m', trade_date),MySQL用DATE_FORMAT(trade_date, '%Y-%m')。如果你论文里用的是MySQL,代码就要跟着变。很多同学数据库随便换来换去,最后代码跑不通,基本都是这里出了问题。

4. 实操过程与项目部署

4.1 从零搭建项目骨架

我的项目目录结构如下,这个结构对毕设来说很清晰,老师打开项目一目了然:

finance_system/ ├── app.py # 程序入口 ├── config.py # 配置文件 ├── requirements.txt # 依赖清单 ├── models.py # 数据模型 ├── views/ # 蓝图(路由) │ ├── __init__.py │ ├── auth.py # 登录注册 │ ├── main.py # 记账与首页 │ └── stats.py # 统计报表 ├── templates/ # HTML模板 │ ├── base.html │ ├── index.html │ ├── login.html │ └── ... ├── static/ # 静态文件 │ ├── css/ │ ├── js/ │ └── echarts/ └── database.db # SQLite数据库(运行时自动生成)

从零搭建的时候,我建议按这个顺序来:先建项目目录和虚拟环境,再安装Flask和Flask-SQLAlchemy,然后写models.py定义数据模型,再写最简单的「hello world」路由,确认基础能跑通,最后才逐步叠加登录、记账、统计等功能。千万不要一开始就想着把所有页面都写完,连路由还没跑通就写前端页面,那是浪费时间。

4.2 页面设计:Bootstrap模板与交互细节

前端我用了Bootstrap的Dashboard模板风格。整体布局是左侧边栏导航、右侧内容区。侧边栏放「首页概览」「记一笔」「收支明细」「统计报表」「分类管理」「个人中心」这些入口,顶部栏显示当前登录用户和退出按钮。

页面设计的核心是让每一笔账的录入在3秒内完成。所以「记一笔」的界面我做成了表单卡片:支出/收入切换按钮、金额输入框、分类下拉框、日期选择器、备注输入框、提交按钮。这个流程极短,演示时体验很好。

日期选择器我用了原生HTML的input type="date",不需要引入第三方插件,兼容性好,演示也不会出错。

收支明细列表使用分页显示,每页10条,附上「编辑」「删除」操作按钮。编辑和删除都是基于URL传参实现:

@main.route('/transaction/edit/<int:txn_id>', methods=['GET', 'POST']) @login_required def edit_transaction(txn_id): txn = Transaction.query.filter_by(id=txn_id, user_id=session['user_id']).first() if not txn: flash('记录不存在或无权操作', 'danger') return redirect(url_for('main.index')) # 处理POST更新逻辑...

这段代码还有一个细节值得注意:查询的时候同时带了id和user_id两个条件。这意味着普通用户就算手动改URL里的记录ID,也只能操作自己的账目,这是一个数据越权的防护点。我在论文里专门讲了这个「水平权限校验」的设计,老师反馈说这个点是加分项。

4.3 项目打包与演示环境准备

毕设演示最怕的是现场环境翻车。我的做法是准备了两套运行方案:

方案A:源码直接运行。这是最常用的。在项目根目录执行:

python app.py

然后浏览器访问 http://127.0.0.1:5000 。

方案B:使用已有的venv虚拟环境。如果换了一台电脑,没有安装依赖,那就先激活虚拟环境再运行,没有虚拟环境的话手动装依赖:

pip install -r requirements.txt python app.py

如果运行时报错找不到flask,多半是没激活虚拟环境,或者pip安装到了系统Python而不是当前环境的Python。这个排查思路非常重要,我后面会在常见问题里展开。

数据库初始化方面,我没有用额外的SQL脚本。项目启动时如果检测到database.db不存在,就自动创建表和默认分类数据。这样做的好处是换电脑演示时不需要拷贝数据库文件,项目一键可用。

5. 常见问题与排查技巧实录

5.1 数据库相关报错与处理

跑毕设项目,数据库是翻车重灾区。我整理了三类高频问题:

第一类:数据库文件锁死。SQLite在Windows下偶尔会出现「database is locked」报错。这通常是因为有另一个进程(比如SQLiteStudio)打开了同一个.db文件,同时Flask也在写入。解决办法是关闭所有外部数据库工具,或者在代码中配置连接超时:

app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'connect_args': {'timeout': 15} }

第二类:users表已经存在。这是重复运行create_all导致的。如果你修改了models.py里的字段,但旧的数据库文件还在,新代码不会自动同步表结构。此时最干脆的处理方式就是删除database.db让它重新生成,或者用迁移工具Flask-Migrate(但毕设没必要)。

第三类:外键约束失效。SQLite默认不启用外键约束,所以代码里写了db.ForeignKey但实际可能不生效。如果你需要级联删除效果,必须在连接后执行PRAGMA foreign_keys = ON。我在代码里用了before_request钩子来统一处理:

from sqlalchemy import event @event.listens_for(Engine, 'connect') def set_sqlite_pragma(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() cursor.execute('PRAGMA foreign_keys=ON') cursor.close()

这里想提醒一点:外键约束在毕设里其实不是很关键的功能,但如果你在论文里写了「数据库设计了外键关联」,那代码里就真的要保证外键生效,否则就是理论与实现脱节,答辩时会很尴尬。

5.2 中文编码乱码问题

Windows环境下运行Flask,中文乱码一般有两个原因。

第一个原因是Python文件没有声明UTF-8编码。Python 3默认就是UTF-8,所以现在通常不会因为文件编码而乱码。真正容易出问题的是控制台输出。如果你在终端里print中文,Windows的GBK编码有时候会报UnicodeEncodeError。解决方案是在程序入口处加一行:

import sys import io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

第二个原因是浏览器显示乱码。Flask的模板文件只要在HTML里有<meta charset="utf-8">,基本不会乱。如果发现了乱码,优先检查数据库里的数据本身是否是乱码——用SQLiteStudio打开看一下就知道。如果库里就是乱码,那是写入的时候出了问题,跟浏览器无关。

5.3 答辩前必须检查的细节清单

这部分内容是基于我的血泪教训整理的,建议你在答辩前逐项打钩:

  • [ ] 注册新用户后能正常登录并记账
  • [ ] 用管理员账号能看到所有用户的数据
  • [ ] 修改密码后旧密码不能再登录
  • [ ] 删除一笔记录后有确认提示,避免误删
  • [ ] 金额输入负数、0、小数超过2位后系统会报错
  • [ ] 统计报表的「近6个月趋势图」在不同月份数据显示正确
  • [ ] 所有页面在浏览器缩放状态下布局不塌
  • [ ] 数据库文件不在桌面演示时被意外改动

我吃过一个亏:答辩前发现统计图的日期显示错位。原因是前端格式化日期时用了JS的Date对象,而JS的月份是从0开始计的(0代表1月)。如果你在代码里也遇到了类似问题,记得检查一下月份是否要加1。

6. LW文档写作与毕业答辩经验

6.1 LW文档的结构拆解与写作技巧

LW文档是毕业设计的论文部分,很多同学代码做完了卡在写论文上。我总结了一套逻辑,按这个结构写,行文会很顺畅:

  1. 绪论:背景、意义、国内外研究现状、主要工作
  2. 相关技术介绍:Python、Flask、SQLite、ECharts
  3. 系统分析:可行性分析、需求分析、用例图、数据流图
  4. 系统设计:总体架构、功能模块设计、数据库设计
  5. 系统实现:每个模块的界面截图和核心代码说明
  6. 系统测试:测试用例设计、测试结果
  7. 总结与展望

这里有一个关键写作思路:每个功能模块的「实现」小节,不要只贴代码,要写「为什么这么做」。比如你写了用装饰器做登录校验,就要解释装饰器相比在每个视图里复制粘贴判断代码的优势;你写了用ECharts而不是Highcharts,就要说ECharts的文档和中文社区更友好,对国内用户更友好。

论文里插入的每个截图都要配上文字说明,不能光秃秃一个图。格式方面,图和表格要有编号,表格用三线表,参考文献要按学校要求格式排列。

6.2 答辩演示的流程与常见问题应答

答辩演示时,我实际走了一遍这个流程,效果很稳:

  1. 先花2分钟介绍项目背景和技术栈
  2. 然后演示登录(最好注册一个新账号现场演示)
  3. 录入一笔支出、一笔收入
  4. 展示收支概览和图表统计
  5. 展示管理员视角的数据
  6. 最后总结项目亮点和不足

答辩老师最常问的问题,我提前整理了答案:

问:为什么选SQLite而不是MySQL? 答:SQLite是嵌入式关系型数据库,零配置、单文件存储,适合轻量级单机应用和课程设计场景;如果未来扩展到多用户高并发场景,可以通过SQLAlchemy的适配层平滑切换到MySQL或PostgreSQL。

问:密码安全是怎么做的? 答:密码通过Werkzeug库的generate_password_hash进行PBKDF2加盐哈希存储,数据库中不存明文密码;登录时通过check_password_hash校验,即使数据库泄露,口令原文也不会直接暴露。

问:系统有什么不足? 答:目前只实现了基础的记账和统计,未来可以增加预算管理、定期账单提醒、数据导出Excel、多币种支持等功能。用「未来改进」来展示你的思考深度。

这样的应答策略帮了大忙。老师问的问题并不深,关键是你得对自己的代码每一层都心里有数,回答时不要吞吞吐吐,尽量结合代码中的具体实现来讲。

最后再分享一个我在实际开发中的经验:做这种信息管理系统,真正拉长时间的不是写代码,而是反复调试的边界情况。比如金额校验、日期格式、权限拦截这些都是细节,但恰恰是这些细节决定了一个毕设看起来像「应付交差」还是「认真完成」。如果你时间紧张,优先把登录、记账、统计三个核心链路打磨顺,然后保证代码能跑、论文能讲清楚,这个题目的完成度就不会低。

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

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

立即咨询