简介:基于Python的简易记账软件设计源码,面向个人及小型企业用户,解决日常收支记录与基础财务统计问题,适合Python开发者和前端初学者作为综合练习项目。压缩包共37个文件,约848KB,其中20个Python脚本负责业务逻辑与数据访问,4个HTML配合4个CSS构建界面,5个JavaScript脚本增强页面交互,另附说明文档、图标及Git忽略规则文件;源码按controller、dao、service分层组织,从入口文件到各模块调用链清晰,覆盖登录、首页、添加记录、历史查询等页面。目前已有717人学习/下载,项目体量适中,便于快速通读与二次开发。读者可从中学习分层架构思想、Python数据库操作、基于Bootstrap的页面搭建、JavaScript交互增强等技能,也能直接运行源码作为简易记账工具,或在其基础上扩展多用户、报表统计等功能,为同类财务软件开发提供有益参考。
1. 记账软件源码:拿到手先别急着跑,先把这张“地图”读懂
个人记账这件事,听起来简单,真正做起来比想象中麻烦得多。Excel 表格用久了卡顿,手机 App 数据又不在自己手里。这份基于 Python 的简易记账软件源码,属于典型的“教科书式工程结构落地”项目——29 个文件,后端的 controller、service、dao 分层清晰,前端的 HTML、CSS、JavaScript 也都给你配齐了。它的价值不在于功能多花哨,而在于代码组织方式完全照搬了企业级项目的分层思路,适合拿来练手、二次开发,或者作为 Python 课程设计的参考模板。
很多人拿到源码第一反应是直接跑 test.py,结果报错一堆,其实问题多半出在对项目结构缺少整体认知。这篇笔记我会从目录结构讲起,拆到每个文件干什么、层与层之间怎么调用、数据库操作怎么写,最后把常见的运行坑逐个点名。整个流程走完,你能达到的水平是:不仅能跑通,还能自己动手加功能。
2. 拆解目录结构:29 个文件背后的分层逻辑
2.1 从上到下认识整个项目骨架
先整体看一眼目录树。整个项目分成六个核心包:controller(控制层)、service(业务层)、dao(数据访问层)、model(实体模型)、html(前端页面)、filter(过滤器),再加上 static 静态资源和一个 test.py 入口。
upload.zip ├── main.py ├── controller/ │ ├── __init__.py │ ├── usercontroller.py │ └── ordercontroller.py ├── service/ │ ├── __init__.py │ ├── userservice.py │ └── orderservice.py ├── dao/ │ ├── __init__.py │ ├── userdao.py │ ├── orderdao.py │ └── dbhelper.py ├── model/ │ ├── __init__.py │ ├── order.py │ └── userinfo.py ├── html/ │ ├── login.html │ ├── index.html │ ├── add.html │ └── history.html ├── filter/ │ ├── __init__.py │ └── filter.py └── static/ ├── js/ ├── css/ └── images/这个结构不是一个摆设,它对应的是后端开发中经典的三层架构:controller 不直接碰数据库,service 不写 SQL,dao 只做数据读写,model 定义表结构对应的实体类。分层的意义在于:每一层只干自己那一件事,改任何一层都不影响其他层。
2.2 controller、service、dao 三层各自扮演什么角色
学过 Spring MVC 的朋友看到这三个文件夹会相当亲切。controller 负责接收请求、调用 service、返回页面或数据;service 层负责业务逻辑,比如登录校验、余额计算;dao 层负责和数据库打交道,包括增删改查。main.py 是程序入口,启动服务后所有请求先经过 main.py 分发,再到 controller。
从包名上看,usercontroller 和 ordercontroller 就是两个核心业务的入口。用户管理一个,订单(记账记录)管理一个。这个命名习惯在真实项目里非常常见——按业务模块划分 controller,而不是按功能划分,比如按功能拆的话你可能会写出 logincontroller 和 addcontroller,但按业务拆就是 usercontroller 和 ordercontroller,后者更利于扩展。项目虽小,但在设计思路上已经向主流的软件工程实践看齐。
2.3 model 和 dbhelper:被很多人忽略却最关键的两个文件
model 文件夹里放着 order.py 和 userinfo.py,这两个文件在 Java 项目里叫 POJO 或 Entity,在 Python 里就是普通类,定义了用户和订单的数据结构。dao/dbhelper.py 是数据库辅助工具,负责建立连接、执行 SQL、关闭连接。好多同学拿到项目后直接去看 controller 或 service,跳过 model 和 dbhelper,结果后面越看越糊涂,因为不知道数据从哪来、字段叫什么。
提示:读源码的顺序建议是 model → dbhelper → dao → service → controller。从底层往上读,调用关系会非常清楚,因为每上一层就是一次方法的调用,而不是在高层代码里不停地猜数据从哪来。
3. 把核心代码拆开看:从登录校验到账单分页的完整链路
3.1 userdao 与 usercontroller:先搞清楚用户登录是怎么走通的
用户相关的核心代码链路是这样:login.html 页面提交表单到 usercontroller,controller 调用 userservice,service 调用 userdao 里的查询方法,dao 通过 dbhelper 查询数据库,最后把结果一层层返回,决定登录成功还是失败。
我以“登录校验”为场景,手写一遍这个链路的典型实现,方便你对照源码阅读:
# dao/userdao.py import sqlite3 class UserDao: def __init__(self, db_path): self.db_path = db_path def find_by_username(self, username): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() sql = "SELECT id, username, password FROM userinfo WHERE username = ?" cursor.execute(sql, (username,)) row = cursor.fetchone() cursor.close() conn.close() return row这段代码的逻辑不复杂,但两个细节值得记住:第一,SQL 使用的是参数化查询(username 用 ? 占位),这是防止 SQL 注入的基本做法,新手写代码很容易用字符串拼接 SQL,源码里这种用法值得学习;第二,每次连接用完就关闭,避免连接泄漏,在 Python 的 sqlite3 模块里,不主动 close 的话连接会一直占用文件句柄,程序跑久了就会报“database is locked”。dao 层只负责查询,不判断密码对不对,判断逻辑交给了 service 层。
# service/userservice.py import hashlib class UserService: def __init__(self, user_dao): self.user_dao = user_dao def login(self, username, password): row = self.user_dao.find_by_username(username) if row is None: return None # 注意:源码里的密码存储方式请以实际为准,这里演示常见做法 hashed_password = hashlib.sha256(password.encode('utf-8')).hexdigest() if row[2] == hashed_password: return {'id': row[0], 'username': row[1]} return Noneservice 层做了一个重要的事:把 data 层的原始查询结果转换成业务层需要的字典对象,同时承担密码校验逻辑。这样 controller 拿到的就是一个干净的登录结果,不需要关心数据库字段的索引位置。参数说明:username 是登录表单传来的账号,password 是明文密码,经过哈希后和数据库存储的密文比较,返回字典时只携带 id 和 username,敏感信息不下传。
3.2 orderdao 的增删改查与分页查询实现
订单(账单)模块是记账软件的核心,orderdao 里应该有新增、删除、修改、按用户查询账单、分页查询等方法。记账软件特有的需求是“按用户 ID 查所有账单”和“按时间范围筛选”。以前端页面 history.html 展示账单历史为例,查询逻辑的大致样子如下:
# dao/orderdao.py class OrderDao: def __init__(self, db_path): self.db_path = db_path def find_by_user_id(self, user_id, page, page_size): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() offset = (page - 1) * page_size sql = """ SELECT id, amount, category, note, create_time FROM orders WHERE user_id = ? ORDER BY create_time DESC LIMIT ? OFFSET ? """ cursor.execute(sql, (user_id, page_size, offset)) rows = cursor.fetchall() cursor.close() conn.close() return rows这里的分页查询是典型做法:LIMIT 控制一页显示多少条,OFFSET 控制跳过前面多少条。page 从 1 开始,第 1 页 OFFSET 为 0,第 2 页 OFFSET 为 page_size。这是一个顺序表查询的经典实现,也是真实项目中列表页最常见的翻页逻辑。源码中 orderdao 的核心方法大概率包括 find_all、save、delete、update 这几个基础方法,找到它们对照读就能理清整个模块。
3.3 filter.py 做登录拦截:源码里一个容易被忽略的“守门员”
filter 包下的 filter.py 在项目里负责登录状态的检查。访问 index.html、add.html、history.html 这类页面之前,filter 会先确认当前会话是否已登录,没有登录就跳回 login.html。这个机制在 Flask 中通常用 before_request 钩子实现,在纯 Python 的 socket 项目中可能是手动在分发前做判断。作用都是同一个:防止未登录用户直接通过 URL 访问内部页面。
它的工作流程是:浏览器请求 → main.py 接收 → filter 检查 session → 放行或重定向。你看到所有内部页面都可以在浏览器地址栏直接输入 URL 访问,就是因为 filter 这个“拦截器”兜底。带过项目的人都知道,这种机制一开始没有的话,后面补非常麻烦。因此拿到手的第一件事,其实建议先看 filter.py 的实现细节,确认 session 存储的位置和过期策略,这决定了整个项目的安全边界。
4. 前端页面的配合逻辑:四个 HTML 是怎么和 Python 后端对上的
4.1 login.html、index.html、add.html、history.html 的功能分工
前端虽然只有四个 HTML 文件,但覆盖了记账软件最核心的完整路径。login.html 是登录页,负责收集用户名和密码;index.html 是主面板,展示统计摘要、近期账单;add.html 是记账表单页,填写金额、分类、备注;history.html 是历史账单列表,支持分页查看。这套页面设计覆盖了一个记账工具的刚需闭环:“登录 → 看总览 → 记一笔 → 翻历史”。
对应的交互路径也很清晰:登录成功后跳 index.html,从 index.html 点“新增”跳 add.html,点“历史记录”跳 history.html。每个页面提交后的去向又不同——add.html 提交表单新增订单,提交完成后回到 index.html 刷新数据;history.html 的分页点击会触发查询请求,带 page 参数重新拉数据。所以四个页面不是孤立的,它们通过 controller 接口串成了一条完整的业务流。
4.2 静态资源 mapping:bootstrap、jquery 文件被谁调用了
static 目录下面放了 bootstrap.min.css、bootstrap.min.js、jquery.min.js 和几个工具类 JS。这部分文件是典型的“拿来即用”型资源,前端页面在 HTML 中通过相对路径引用它们。项目里 js 文件夹中的 ie10-viewport-bug-workaround.js 是一个老经典——专门修复 IE10 在 Bootstrap 3 下的 viewport 兼容问题,看到这个文件大概能推断出前端是基于 Bootstrap 3 构建的。
理解静态资源配置的关键在于:页面上的样式、交互效果、响应式布局,全部由 static 目录下的文件支撑。如果你准备换主题,只需要替换 css 文件并保持文件名不变,页面就能自动生效,不用改动 HTML 结构。如果遇到页面完全没样式的情况,十有八九是静态资源路径没配对,报错会在浏览器控制台的 Network 标签里显示为 404。
4.3 前后端数据是怎么传的:form 表单和 URL 参数的配合
这个项目是典型的前后端分离初级阶段——HTML 负责页面展示,Python 后端负责逻辑处理,数据交换通过 HTTP 请求完成。登录表单、记账表单通过 POST 方法提交数据,分页、筛选这类轻量操作通过 GET 参数传递。比如 history.html 的分页链接通常长这样:
<a href="/order/list?page=2&page_size=10">下一页</a>后端通过解析请求对象拿到 page 和 page_size 参数,传给 orderdao 的分页方法。
新手最容易困惑的是:为什么有的页面跳转是在 Python 代码里完成,而不是在 HTML 里用 a 标签? 因为登录、新增这类操作需要先经过后端校验,校验通过后才能跳转,这个跳转动作叫做重定向(redirect)。重定向由后端返回一个 302 状态码加 Location 头信息,浏览器收到后自动访问新地址。这种方式保证了业务逻辑不被绕过。
提示:在后端代码里搜索 redirect 或者 url_for 之类的关键词,能快速定位页面跳转逻辑,比满项目找 HTML 页面的互相引用高效得多。
5. 避坑与排查:从拿到源码到跑通页面的那些血泪经验
5.1 坑一:运行报错 No module named 'controller'
现象:在项目根目录直接执行python main.py,立刻报错显示找不到 controller 模块。原因:Python 的模块导入路径默认是当前目录,如果你的命令是在 upload 目录的上一层执行的,解释器就找不到 controller 这个包。这也是最经典的 Python 新手翻车点。解决:先cd upload进入项目根目录再执行python main.py,或者用python -m main的方式运行。验证方法是在 Python 交互环境里执行import controller,不报错就是路径对了。
5.2 坑二:页面能打开但是没样式,所有排版乱成一团
现象:浏览器访问页面后内容是出来了,但完全是纯文本排列,没有表格线、没有按钮样式。原因:HTML 正确加载,但 css 文件 404,通常是静态文件路径配置错误。在本地开发环境跑纯 Python 项目时,static 目录往往需要手动配置为可访问的静态路径,缺一行配置就全部失效。解决:打开浏览器开发者工具(F12),切到 Network 标签刷新页面,找红色 404 请求,看具体是哪个 css 文件加载失败。然后检查 main.py 里的静态目录指向是否与文件夹名一致,常见错误是代码里写的是static/但目录名实际叫public/,或者大小写不一致。
5.3 坑三:登录报错提示数据库不存在
现象:输入任何账号密码都提示无法连接或表不存在。原因:dbhelper 里的数据库文件路径是相对路径,如果你不在项目根目录运行程序,它会在当前目录创建一个空的新数据库文件,里面没有 userinfo 表,于是报错。解决:检查项目里是否有 .db 或 .sqlite 后缀的数据文件。如果有,确认 dbhelper.py 里连的是不是这个文件名;如果没有,请先执行项目里建表相关的 SQL 脚本。解决:更稳妥的做法是把数据库路径和文件名的定义放在 main.py 顶部统一配置,避免每次都去代码里挖路径。
5.4 坑四:添加账单后刷新页面数据丢失
现象:add.html 里成功添加了一条记录,跳回 index.php 能看到,但重启程序后再看,数据没了。原因:数据被写入了内存或临时文件,而不是持久化到数据库文件。解决:检查 service 层是否有 save 方法的调用,确认 add 流程走的是controller → service → dao完整链路,而不是临时把数据存在了 session 或全局变量里。另一个可能是数据库文件路径不一致,程序启动时用的库和运行时写入的库不是同一个文件。在 dao 层打印实际使用的 db_path 可以很快定位问题。
5.5 坑五:新增功能后页面跳转报 500
现象:往项目里加了代码,运行到某个接口时报 500 Internal Server Error。原因:多半是代码运行异常但没有被捕获,Python 默认会输出完整错误堆栈到控制台。解决:切回终端看报错信息,定位到具体的行号。最常见的问题是 SQL 语句里表名字段名写错,其次是函数参数个数不对。如果控制台没有输出,检查 main.py 里的异常处理逻辑是否把错误吞掉了,可以临时在 controller 入口处加一个 try-except 把异常信息打出来。
6. 进阶玩法:在现有结构上快速加一个“月度统计”功能
理解了分层逻辑之后,你会发现自己动手加功能其实并不难。我以“月度收支统计”为例,手把手演示如何在现有源码上扩展新功能,这是你在任何课程作业答辩中都能拿出手的亮点。
先确定功能需求:在 index.html 首页上显示本月的总收入、总支出和结余。按照分层结构来加,需要改动 model 层加一个统计结果的数据结构、dao 层加一个聚合查询方法、service 层加业务计算、controller 层加接口、最后改 index.html 展示。
# dao/orderdao.py 新增方法 def summary_by_month(self, user_id, year, month): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() sql = """ SELECT SUM(CASE WHEN type = 'income' THEN amount ELSE 0 END) as total_income, SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END) as total_expense FROM orders WHERE user_id = ? AND strftime('%Y-%m', create_time) = ? """ cursor.execute(sql, (user_id, f"{year}-{month}")) row = cursor.fetchone() cursor.close() conn.close() return {'income': row[0] or 0, 'expense': row[1] or 0}这里用了 SQL 的条件聚合,一行查询同时算出收入和支出合计。strftime('%Y-%m', create_time)是 SQLite 里把时间字段格式化成“年-月”字符串的写法,这样无论 create_time 存的是 datetime 还是字符串类型,都可以统一匹配。SQL 里CASE WHEN是条件判断,符合条件就累加金额,否则记为 0。or 0是为了防止返回 None 导致后续计算报错。
# service/orderservice.py 新增方法 def get_monthly_summary(self, user_id, year, month): summary = self.order_dao.summary_by_month(user_id, year, month) summary['balance'] = summary['income'] - summary['expense'] return summaryservice 层在拿到 dao 查询结果后,做一次减法得到结余。这样做的好处是:dao 层保持“只做查询”的纯粹性,业务计算放在 service 层,以后如果要加“可用余额 = 结余 - 预算”之类的逻辑,只需要改 service 一个地方。
然后在 controller 层加一个/summary接口,接收 user_id 和月份参数,调用 service 方法返回 JSON 数据。前端 index.html 在页面加载时通过 JavaScript 发起一次 fetch 请求,把统计数字填充到页面对应的 DOM 元素上。
整个功能的改动量,在四人分队里你一个人最多半天就能完成,而且完全没有破坏原有的三层结构。从那以后,我每次接到一个新功能需求,都强制自己走“确认表结构 → 写 dao → 写 service → 接 controller → 改页面”这条流程,而不是随手写一个函数一把梭。这套流程对个人开发者可能显得繁琐,但一旦项目长大到 50 个文件以上,你会感谢当初的结构克制。希望这份拆解笔记帮到你,源码里的细节远比我写出来的多,拿着目录边读边跑,收获会更快。
本文还有配套的精品资源,点击获取