基于Android记账本毕设全解析:从架构设计到避坑指南
1. 选题分析和需求梳理一个记账本凭什么能成为毕设常青树如果去翻最近五年的计算机毕业设计题目清单“记账本”这个词几乎每隔几页就会出现一次。很多同学第一反应是“这也太简单了吧”——无非就是记录收入和支出做个列表算个总账听起来根本撑不起一篇毕设论文。但真正动手做过的人都知道记账本属于典型的“看起来简单做起来全是大坑”的选题。它麻雀虽小五脏俱全涉及Android开发的核心技术栈Activity生命周期管理、自定义布局、SQLite数据库读写、日期时间处理、图表统计、权限适配、甚至通知提醒。把这些全部做扎实足以覆盖一篇本科毕设的全部技术要求而且源码可展示性强答辩时不用靠嘴硬直接跑功能就行。“基于Android记账本的设计与实现”这个题目我在毕业季前后帮不少学弟学妹看过类似的工程自己也完整复现过一套源码包79383里的项目结构基本涵盖了一个典型记账本的完整形态。这篇内容就把整个项目的设计思路、模块拆分、核心代码实现、还有那些文档里不会写的坑一次讲透。先说需求。记账本的核心用户是谁最典型的是个人用户尤其是消费习惯比较随性、月底想搞清楚钱花哪儿去的年轻人。他们的核心诉求很简单记录每一笔收支、按类别归类、按月查看汇总、最好还有图表能直观看出钱都流向了哪里。至于账单云同步、多人协同、家庭成员共享这类功能对本科毕设来说属于过度设计做了反而容易露怯因为涉及网络通信和后端业务逻辑后工作量会翻好几倍。所以我在做选题分析时把需求收敛成三层基础层是账目的增删改查这是无法绕开的地基中间层是分类管理和统计报表这是记账本的灵魂没有统计的记账本就是一个换皮的备忘录增强层是预算提醒、图表可视化、数据导出这些用来体现项目的完整度和细节功力。三层都做扎实了答辩时每一个功能点都能展开讲三五分钟完全撑得起场面。这个需求梳理过程有一个容易被忽略的点异常场景的处理。比如用户输入金额时写成了负数怎么办分类为空怎么办日期跨月、跨年怎么统计账单编辑时数据校验怎么做这些问题在你的开题报告里可能只是几行功能描述但落到代码里全部是边界判断。我后面会逐个展开说。2. 技术选型Java、Kotlin还是别的技术选型这一步很多人抄了个项目就开始写代码却完全说不出来为什么选这套技术栈。答辩导师最喜欢问的问题之一就是“你为什么用XX技术而不是用YY技术”这个问题答不好印象分会掉得很快。记账本这个项目主流的选型方案有四种我直接对比。方案语言数据库适用场景优劣势方案一Java SQLiteSQLite本科毕设主流资料多、教程全、兼容性好但偏向传统代码量大方案二Kotlin RoomRoom有Android基础类型安全高、代码简洁、Google官方主推但学习门槛稍高方案三Java RoomRoom想用ORM又担心Kotlin上手Room帮你管数据库但Java写起来还是啰嗦方案四Kotlin SQLiteSQLite想用新语言但不想学Room语言新、但数据库手写麻烦组合不太推荐源码79383包里用的是方案一Java SQLite。为什么不选Kotlin核心原因是稳定性和参考资料。本科毕设阶段你的主要精力应该放在把业务流程跑通、把论文写清楚而不是花两周时间跟Kotlin的协程、扩展函数和空安全语法较劲。网上Java版记账本的源码、答疑帖、报错解决方案一抓一大把遇到问题能快速找到答案这是Java在这个场景下的绝对优势。另外很多导师自己的技术栈就是Java答辩时你用Java写的代码导师看代码的效率会高很多沟通成本也低。数据库层面SQLite是Android系统内置的轻量级关系型数据库不需要单独装服务、不需要配置连接字符串、不需要账号密码直接通过SQL语句操作本地文件。这种“开箱即用”的特性和记账本这种单机本地应用天然匹配——记账数据量级撑死几千条SQLite绰绰有余。相比之下如果引入MySQL你还得在项目里写网络线程、处理连接超时、做断网重连这些工作量对记账本来说纯属自找麻烦。UI层面我没用当时最火的Jetpack Compose因为那是声明式UI和传统的XML布局是两种完全不同的思维方式。对一个以快速完成、稳定跑通为第一目标的毕设项目XML布局 RecyclerView是最稳的组合。Compose固然是未来但如果你用Compose写一方面导师可能不熟悉另一方面Compose的学习曲线会占用大量本该用来打磨功能的宝贵时间。3. 项目整体架构与模块划分一个合格的地基是项目开始前把架构想清楚。记账本源码79383的架构模式是传统的MVC风格Activity负责界面交互控制数据库操作集中到DatabaseHelper/DAO层账号数据的实例对象用Java Bean承载。MVC虽然被说成老古董但对记账本这种小体量项目反而是最直白的——逻辑清晰没有那么多抽象层每一步干了什么一眼就能看出来答辩讲代码时也不会绕晕自己。整个App的功能模块拆成了五个核心部分3.1 账目记录模块这是记账的入口负责收入/支出的新增、编辑、删除。新增时用户需要输入金额、选择分类、选择日期、填写备注。设计时核心难点是支出和收入的分类要分开因为一个人日常消费分类可能包含“餐饮、交通、购物、娱乐、居住、医疗、教育”等但收入分类往往只有“工资、兼职、理财、红包、其他”这么几条。如果共用一套分类选择收入类别时划拉半天全是支出类体验很差。所以这个模块在设计时分类表里用一个type字段区分收支类型前端根据当前记账模式动态加载对应分类列表。编辑场景容易漏掉的是状态回显。用户点一条账单进入编辑页你不仅要回显金额和日期还要把这条账单对应的分类在分类列表里定位到并且高亮选中。这里有一个经典问题如果用户当初选的分类后来被删了编辑时怎么办我的处理方式是数据库表结构里账单只存category_id编辑时做一次左连接查询查不到分类名就显示“其他”并把“其他”分类高亮。3.2 分类管理模块这个模块的功能是管理收支分类的增删改查。用户可能不满足于默认分类想自己加一个“宠物开销”“游戏充值”之类的自定义分类这个模块就是干这个的。做得好的分类管理还有一层隐藏逻辑分类和账单之间存在外键引用关系。如果没有设计好用户删掉一个分类全表账单里的category_id就变成了一个悬空引用统计报表时会统计不出来。源码79383的处理方式是在删除分类前先查一遍该分类下的账单数量如果还有账单引用就不允许直接删而是提示用户“该分类下有X条账单记录无法删除”这样从源头杜绝了数据脏引用。这个细节很小但实际开发里特别重要我在分类模块翻过车最开始没做这个限制导致统计页出现过一堆空白分类后来才补上。3.3 账单统计模块统计模块是整个记账本的灵魂。消费记录完不统计用户压根不知道自己钱花哪了。这里我做了两个维度的统计时间维度上按日、按月、按年汇总收入支出总额。默认选中当月用户可以通过月份切换器前后翻月份。月度统计页要显示三个核心数字本月收入、本月支出、本月结余结余收入-支出负数用红色标注因为用户最关心的就是这个月还剩多少。分类维度上统计每个分类在本月的支出总额以及占比。这个数据是给后面的图表页用的饼状图可以直观展示哪类消费占比最高。占比计算时要注意精度问题——直接用整数相除10除以60结果是0你就只能看到全是0%所以必须用float/double计算并且把结果转成百分比保留一位小数。3.4 图表可视化模块图表这个模块我用的是MPAndroidChart这个开源库。网上一些项目会把图表做得特别花哨但是毕设项目的核心原则是功能第一样式第二。我实际做的是两个图表账单分类支出占比环形图和一个简单折线图用来展示最近一周或者一个月的支出走势。饼图能看出来钱花到哪了折线图能看出来花钱趋势两个配合起来统计页就非常有说服力。用MPAndroidChart时踩过一个坑低版本库在AndroidX环境下的包名冲突问题。如果你用的MPAndroidChart版本比较老引入后可能会报找不到android.support库的错误。解决办法是用3.1.0以上版本并且在build.gradle里添加对AndroidX的支持。源码包里用的是v3.1.0配置一次就能干净跑起来。3.5 预算提醒模块这个模块属于加分项可选但推荐做。功能很简单用户设置月度总预算系统按本月已记录的支出去计算预算剩余量。预算额度低于某个百分比时在首页显示一个进度条和提示文案。热搜词里为什么会有“android进度条”这个词就是因为预算模块必须用进度条来表现支出占比不然只显示文字“本月已用1234元预算2000元”用户根本感知不到压力。进度条不仅好看而且信息传达效率远高于数字。进度条的处理逻辑是当前进度 本月支出 / 预算总额。需要注意边界情况预算是0或者尚未设置预算时进度条应该隐藏而不是显示一个满血的0%。每月1号要自动重置进度这个用定时任务不好做更稳的方案是把当前月份作为判断条件在首页加载时判断当前月份和上次记录的月份是否一致不一致就自动重置统计状态逻辑简单、零成本不需要写AlarmManager。4. 数据库设计与核心实现数据库是整个项目的地基。记账本的数据库表设计我用了三张表账目表bill、分类表category、预算表budget。三张表的关系极其简单账目belongs_to分类预算表只有一条记录——因为做的是单月度单预算模式没必要搞复杂。4.1 表结构设计账目表的核心字段如下字段名类型说明_idINTEGER PRIMARY KEY AUTOINCREMENT主键自增category_idINTEGER分类外键typeINTEGER收支类型1收入0支出amountREAL金额注意存分还是存元的问题dateTEXT账单日期格式yyyy-MM-ddremarkTEXT备注允许为空金额这块是我重点想提醒的联网支付的精度问题是毕设里最常见的隐藏扣分项。直接用float/double存金额在下一次计算时可以算出9.9999这样丑陋的结果写死还挺难排查。我采用的是以“分”为单位存整数显示时再除以100转换成元。这样既避免了精度问题又让统计时sum的结果也是整数分没有尾巴。如果嫌切换单位麻烦用REAL存两个小数位的元也够用但一定要在写入前做四舍五入不然累计几千条对不上一毛钱是常有的事。分类表的结构就四个字段_id、分类名称、type1收入0支出、是否默认分类。is_default这个字段用来标记系统预置的分类——预置分类不允许删除自定义分类可以删这个用代码逻辑控制。预算表最简单_id、budget_amount、budget_month格式yyyy-MM。每次新增一条记录就会把旧的覆盖掉相当于同一时间只有一条预算生效。4.2 SQLiteOpenHelper与DAO层项目里数据库操作不是直接在Activity里写的那样会导致Activity类膨胀到一千行后期维护时自己看了都崩溃。我是用SQLiteOpenHelper负责建表建库再用一个单独的DBManager类封装所有业务SQL操作。SQLiteOpenHelper的onCreate方法里执行建表SQL注意Android里SQLite的版本升级问题——如果你在开发过程中改了表结构必须把数据库版本号加1然后在onUpgrade里执行ALTER TABLE或者DROP TABLE重建。很多初次做毕设的同学改了表结构以后发现运行还是老样子就是因为App安装后旧库还在版本号没变onUpgrade根本不会执行。遇到这种情况最快的调试办法是卸载App重新安装但这个方法只适用于开发期真正交给答辩老师的时候版本化更新逻辑必须是对的。这里给一段DAO层插入账单的核心代码直接看逻辑public long insertBill(Bill bill) { SQLiteDatabase db this.getWritableDatabase(); ContentValues values new ContentValues(); values.put(category_id, bill.getCategoryId()); values.put(type, bill.getType()); values.put(amount, bill.getAmount()); // 单位分 values.put(date, bill.getDate()); values.put(remark, bill.getRemark()); long id db.insert(bill, null, values); db.close(); return id; }ContentValues本质上就是一个Map用put方法将列名和值一一对应。insert方法返回的自增id很重要数据库里你插入了一条新记录你要拿到这个id来刷新界面的RecyclerView新项但更关键的是让用户知道自己刚才的操作成功了——插入失败返回-1前端要包装成Toast提示用户“保存失败请重试”。查询语句中最常用的是按月汇总public double getMonthTotal(int type, String month) { SQLiteDatabase db this.getReadableDatabase(); Cursor cursor db.rawQuery( SELECT SUM(amount) FROM bill WHERE type? AND date LIKE ?, new String[]{String.valueOf(type), month %}); double total 0; if (cursor.moveToFirst()) { total cursor.getDouble(0); } cursor.close(); db.close(); return total; }这里date LIKE 2024-06%是利用存储的yyyy-MM-dd格式带前缀的特点把某个月的数据筛出来。这种字符串匹配在数据量小的时候完全没问题不需要为了性能做复杂的日期范围函数。cursor用过之后一定要close在循环遍历Cursor时尤其要注意否则频繁打开数据库容易引发内存泄漏界面卡顿的罪魁祸首往往在这里。想更稳一点可以用try-with-resources或者在finally块里统一关闭。4.3 RecyclerView适配器与列表展示首页账单列表用的是RecyclerView需要自己手写Adapter继承RecyclerView.Adapter。绑定数据时我用了ViewHolder模式这块有个新手常见的卡顿问题如果在onBindViewHolder里频繁调用findViewById会拖慢滚动性能。标准做法是ViewHolder在onCreateViewHolder里就把所有View引用缓存下来onBindViewHolder只负责给View设置数据。RecyclerView列表还有一个细节收入支出行的颜色区分。我在每行布局左侧做了一个圆形背景的小图标收入用暖色支出用冷色数字也带“”“-”前缀用户扫一眼就能分清哪笔是进账哪笔是出账。列表按日期倒序排列同一天的账单按照创建时间倒序这样才能保证最新记录排在最上面而不是被旧数据挤下去。列表滑动删除这种交互属于进阶功能源码包里的版本没有做而是通过长按弹出操作菜单实现删除和编辑。这么做虽然不那么炫酷但胜在逻辑简单直白、触发明确——按键触发的删除操作在误删时更好拦截可以在删除前弹出确认框。用ItemTouchHelper做滑动删除也不是不行但误触后用户直接滑没了心里会打鼓确认逻辑做不好反而扣分。5. 使用Android Studio跑通项目的完整路径获得源码包之后第一件事不是看代码而是把环境跑通。每一步都可能有坑我按实际操作的顺序列一遍。5.1 环境准备JDK、SDK、Gradle源码79383是基于Android Studio开发的配套用的是常见版本强烈建议不要用自己的新版AS直接开老项目然后一路点升级——很可能升着升着代码里用的API已经在新版本SDK里被废弃了。我实践下来的稳定组合是JDK版本1.8。虽然Oracle JDK 8已经很老了但Android生态的老项目对它兼容性最好。新版JDK高版本Java特性在Android的编译链里可能不支持跑出莫名其妙的错误很常见。Android SDKAPI 30Android 11及以下版本因为源码里targetSdkVersion写的30如果你的电脑只装了API 34高版本只会触发兼容性警告代码逻辑不受影响但如果你把targetSdkVersion改成34就要适配分区存储、前台服务类型限制等一堆新变化费力不讨好。Gradle版本建议使用AS自动推荐的版本。项目里有gradle-wrapper.properties这个文件里面指定了Gradle的下载版本。如果Gradle下载特别慢手动把distributionUrl里的地址改成国内镜像能省下一个晚上的时间。5.2 导入项目与同步依赖打开Android Studio选择Open定位到源码根目录即包含settings.gradle文件的目录等待Gradle同步完成。同步的过程中AS会下载所有远程依赖MPAndroidChart就是这个时候拉下来的。如果同步报错看到“Could not find com.github.PhilJay:MPAndroidChart:v3.1.0”大多是网络问题——因为MPAndroidChart是发布在JitPack仓库里的如果你在build.gradle里的repositories里只写了google()和mavenCentral()当然找不到JitPack上的库。需要在根目录build.gradle的allprojects里加上maven { url https://jitpack.io }然后重新同步。同步成功后连上Android手机或者启动模拟器点Run按钮。第一次构建会比较慢因为要做代码编译、资源合并、APK打包和签名耐心等就好。真机调试时记得打开开发者选项并且开启USB调试。5.3 APK安装与常见启动闪退问题安装成功但启动直接闪退九成是SQLite路径问题——数据库没有正常创建Activity在获取可写数据库时抛出异常。排查办法是查看Logcat里面的红色报错日志sqlite相关报错会把关键信息直接打印出来。其余最常见的是AndroidManifest.xml里没有给MainActivity加intent-filter的MAIN/LAUNCHER声明导致App安装后图标都没出现或者主Activity的layout资源在setContentView时引用了一个不存在的id编译时就该暴露。我建议拿到源码后不要急着改功能先在模拟器上完整跑通一遍原始版本确认“拿到就能用”再逐步分析代码改自己的需求。这样排错时你至少有把握是“原项目正常我改挂的”而不是“原项目本身就是坏的我还在原地修”排查范围天差地别。6. 功能实现中的关键细节与避坑指南6.1 账目添加的数据校验逻辑添加账目的Activity里我写了四个校验规则金额不能为空且必须大于0。用户可能输入0或负数前者没意义后者会污染统计数据。金额输入框的键盘类型设置为decimal方便用户输入小数点但校验不能只依赖键盘必须在代码里再查一遍。分类必须已选择Android进度条设计细节日期默认取当天但允许用户手动修改以支持补记前几天的情况。日期选择器用DatePickerDialog。校验通过后再执行insertBill不通过就弹Toast不进入数据库操作。这块不能偷懒——数据库层只负责存取业务规则必须在Activity层或者Model层做约束否则一条负数账单进入数据库后统计模块算出来的数据没人敢信。6.2 金额输入框的实时格式化金额输入框是一个EditText用户输入的过程如果完全不管会出现小数点后好多位、甚至多个小数点的混乱情况。我用了一个TextWatcher来做实时格式化只允许一个小数点小数点后最多两位。实现逻辑是监听文本变化发现不合规字符就删除并且保留光标位置。这是我在实际使用中最推荐的务实写法虽然Real-time input filter用正则也能做但处理光标跳转时特别容易出bug用户在输入框中间插入数字时不做处理的分位逗号反而会跳错位置。做毕设的话不格式化也没关系存成什么格式显示什么格式就是没那么体面。想要体面一点就用TextWatcher做规整化注意别在afterTextChanged里重复赋值触发下一次监听造成死循环就行。6.3 Android权限适配与FileProvider问题热搜词里出现了好几个content://开头的字符串什么com.baidu.searchbox.fileprovider、com.tencent.wework.fileprovider这些不用慌它们和你的项目没关系其实是其他App在系统共享目录里的FileNameProvider路径。但“Android权限汇总”这个热搜词是真的跟你有关。记账本要读写SQLite数据库数据库文件存在应用私有目录下默认是不需要任何权限的。但在Android 6.0以上的系统里做数据导出功能时要往公共存储目录写文件就涉及WRITE_EXTERNAL_STORAGE运行时权限。Android 10以上还有分区存储机制直接用绝对路径写公共目录会失败。最稳妥的做法是数据导出文件写到应用的专属外部目录getExternalFilesDir里这个目录既不需要申请权限又不会暴露隐私用户通过文件管理器访问时也能看到。如果一定要导出到下载目录用MediaStore API来写不要再用绝对路径File那一套老方法。另外一个跟权限绑定的痛点是FileProvider。如果你的项目导入了图片选择相关的第三方库或者在代码里用FileProvider做私有目录文件共享就可能出现Manifest合并失败报authorities冲突之类的错误。解法是修改你的provider声明里的authorities把它改成你自己的包名加.fileprovider确保唯一性类似“com.example.accountbook.fileprovider”。理解FileProvider的原理其实不复杂——它就是允许其他App通过content://协议访问你App私有目录下指定的文件避免直接暴露文件路径的高风险方式。6.4 数据库升级策略与用户数据保护开发阶段改表结构简直是家常便饭。每次改表把数据库版本号DATABASE_VERSION加1然后在onUpgrade里判断oldVersionOverride public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion 2) { db.execSQL(ALTER TABLE bill ADD COLUMN budget_id INTEGER); } }这里有个大坑很多人在onUpgrade里直接执行DROP TABLE重建开发期无感但正式交给用户后你一次升级用户之前记了几年的账就全清了。用户会在评论区骂娘这是必然的。所以哪怕只是毕设级的项目也建议用ALTER TABLE的方式追加字段或新建表而不是无脑DROP。这一条如果能写进你的论文的设计与实现章节里会是一个很加分的亮点至少说明你考虑到了数据迁移的问题。6.5 图标库选型与应用图标设计热搜词里有“android图标”、“android动态图标主题”这提醒了我图标在毕设中的印象分占比远超你的想象。一个记账本如果自带的应用图标还是那个绿底白色小机器人头像答辩老师第一印象就很业余并且会暴露你整套App没有做过桌面端适配和品牌设计。我使用的是Material Design内置图标库直接在布局里用android:srcdrawable/ic_income这种方式引用系统自带的矢量图不需要额外下载图标包系统自带一组很全的基础图标包括加号、删除、编辑、箭头、日历等完全够用。应用Logo则用一个简单的“¥”符号配合纯色背景用ShapeDrawable生成几行代码搞定不依赖外部图片资源省去了找图的麻烦。7. 统计模块的图表实现与MPAndroidChart集成7.1 饼状图的配置与数据绑定统计页面加载时会先查询数据然后把数据组装进PieChart。核心代码如下PieChart pieChart findViewById(R.id.pie_chart); ArrayListPieEntry entries new ArrayList(); // categoryList是分类名称amountList是对应金额 for (int i 0; i categoryList.size(); i) { entries.add(new PieEntry(amountList.get(i), categoryList.get(i))); } PieDataSet dataSet new PieDataSet(entries, 分类占比); dataSet.setColors(new int[]{...}); // 自己定一组颜色 PieData data new PieData(dataSet); pieChart.setData(data); pieChart.invalidate();图表数据要做好对空数据的处理——当月没有任何账单时显示一个空白饼图意义不大应该改为显示一张图片或者一行字“本月暂无数据快去记一笔吧”。这里使用if-else判断一下就行但很多项目里确实忘了做。MPAndroidChart的README里默认的渲染结果其实并不好看中文标签挤成一团所以建议适当调整图例位置、字体大小和百分比精度。实测来说把图例放在饼图右侧把百分比标签只保留整数显示效果会干净很多。7.2 折线图的日期坐标处理折线图展示最近一周的支出趋势。X轴不直接显示时间戳而是显示“周一”“周二”这样的友好名称实际数据点对应的值是那天的支出总额。这个转换逻辑在getXAxisFormatter里处理。注意如果你用连续日期作为X轴坐标记得坐标值的int类型转换要对齐不然索引错位后图形会和实际数据对不上排查特别费劲。7.3 自定义View还是第三方库也许有人会想自己用Canvas画一个饼图。我能理解这种冲动但当老师问“为什么用MPAndroidChart”时你有一个漂亮的回答MPAndroidChart是Github上高Star的成熟开源库减少了业务开发中重复造轮子的成本让自己的精力聚焦在核心业务逻辑上。这本身就是技术选型能力的一种体现。技术选型的目标不是证明你会造所有轮子而是证明你会选最合适的轮子。这句话可以写进论文里。8. 常见问题与排查技巧实录回忆一下我实际开发中遇到过的、也是很多同学大概率会撞上的几个问题整理成一个速查表碰到同类问题直接照方抓药。问题现象根本原因排查与解决Gradle同步报错JitPack找不到缺少maven { url https://jitpack.io }根build.gradle增加JitPack仓库安装后点击图标闪退SQLite路径异常 / 主Activity布局引用错误看Logcat的FATAL EXCEPTION栈信息定位列表数据刷新后不显示忘记notifyDataSetChanged数据变更后必须调用adapter.notifyDataSetChanged()金额统计出现漏掉一笔月份匹配格式不对如date存了yyyy-MM-dd HH:mm:ss统一日期格式LIKE匹配时注意月份字符串前补0删除分类崩溃该分类下有账单外键悬空删除前先查该分类账单数量有账单则禁止删除数据库升级不生效版本号没递增onUpgrade未触发修改DATABASE_VERSION递增1图标模糊位图在不同屏幕密度下放大失真使用VectorDrawable或提供mipmap各尺寸图标数据丢失事故的记录。某个版本修改过程中我在页面切换时直接调用了db.close()结果切回列表页再操作时数据库句柄已关闭整个页面一操作就崩。这是新手最容易犯的错误SQLiteOpenHelper的实例应该是全局单例贯穿整个App生命周期不要在Activity的onPause或onStop里关闭数据库。关了一次后面所有操作都得重建反而更容易出问题。要省资源控制Cursor的开关就够了数据库本身一直保持打开状态才是常态。另一个印象深刻的坑是分类数据的统计排序。当时查询SQL写的是SELECT category_id, SUM(amount) FROM bill WHERE date LIKE 2024-06% GROUP BY category_id结果饼状图展示的分类顺序每次都和数据库表的顺序对不上看了半天才明白是GROUP BY的排序不稳定导致的。解决方法是增加ORDER BY明确指定的排序规则不要依赖数据库默认行为ORDER BY total_amount DESC把金额最大的分类放在最前面饼图的效果也更好——第一眼就能看到最大的开销分类。9. 项目管理与论文撰写的经验源码只是毕设的一部分论文同样重要。很多同学代码写得很溜写在论文里的设计描述却干巴巴的。这里分享三个实用经验。第一论文的“需求分析”章节不要写空话。不要写“本系统采用了先进的技术架构”而要写“用户通过点击首页右下角的悬浮加号按钮可以快速完成一笔支出记录整个流程控制在三步以内”。论文讲究的是系统性描述但凡是具体的数据流和界面流转句子都非常加分。第二数据库设计章节一定要展示表结构和字段说明表。表格里不仅能列字段名和类型还要标注每个字段的含义和是否可空让导师不看代码也能知道你数据库的设计思路这一节数据完整性很能体现你的工作量。第三测试章节不要只写“测试通过”。写清楚每个模块的测试用例、输入数据、预期结果、实际结果。比如测试金额边界值时输入0、负数、小数、极大数分别验证校验逻辑和数据库写入结果。一份记录完整的测试表格比你在答辩时吹十句“我的代码没bug”都有说服力。10. 写在最后的经验这个项目做完之后的几点体会。结余计算的显示细节可以再加一层如果当月是负数即超支除了把数字标红还可以在旁边加一个文字提示“已超支X元”让人一眼就紧张起来。这类情感化设计不增加多少工作量但对用户体验提升明显。记账本的扩展方向其实也很多将来可以加数据导出CSV文件再用电脑Excel打开、加指纹/密码锁定保护隐私、加图标日历热力图显示每日消费强度、甚至可以加数据云备份。但扩展功能的前提是核心链路跑通了再谈锦上添花。我自己的习惯永远是先把基础记录、统计、编辑删除这几个闭环整理干净多出来的精力再去做炫技功能。最后说一句在做毕设的过程中保存版本一定要用Git建立项目的第一天就git init。这个记账本前后改了十几个版本要不是每一步都有Git记录很多改崩的地方根本回不到上一个能跑的版本。给源码包编号79383说明作者至少在版本管理上有这个意识希望你做项目时也有。