管理系統(tǒng)從設(shè)計到實現(xiàn)全解析)
各位做 Java 后端、或者正在折騰畢業(yè)設(shè)計/個人項目的朋友應(yīng)該對“個人財務(wù)管理系統(tǒng)”這個題目不陌生。它聽起來不復(fù)雜真正動手做的時候才發(fā)現(xiàn)賬目流水怎么設(shè)計才不臃腫、統(tǒng)計報表用什么方案算得又快又準(zhǔn)、預(yù)算提醒怎么做得不雞肋還有“論文怎么寫才能跟代碼匹配上”這些問題一個比一個現(xiàn)實。我最近剛好整完一套基于 Spring Boot 的個人財務(wù)管理系統(tǒng)從數(shù)據(jù)庫建模到接口實現(xiàn)再到論文框架梳理全程自己踩了一遍。這篇文章就把我的完整思路、核心代碼邏輯、碰到過的坑和排查方法都倒出來給準(zhǔn)備做類似系統(tǒng)的你一個能直接參考的底稿。這個系統(tǒng)能做的事情很直白多賬戶管理、收支流水記錄、分類統(tǒng)計、月度/年度報表、預(yù)算超支提醒、賬單導(dǎo)入導(dǎo)出。前端用的是輕量級模板加一點原生交互后端是標(biāo)準(zhǔn)的 Spring Boot 三層架構(gòu)。適合誰看正在做課程設(shè)計、畢業(yè)設(shè)計或者想自己寫個小工具管理錢袋子又不想把技術(shù)棧搞得太重的開發(fā)者都可以照著這份思路往下走。1. 整體設(shè)計與技術(shù)選型為什么是 Spring Boot而不是別的先聊選型。個人財務(wù)管理系統(tǒng)這種項目業(yè)務(wù)不復(fù)雜但涉及“錢”的數(shù)據(jù)對穩(wěn)定性和可追溯性有要求。我當(dāng)時對比過幾套方案最后鎖定了 Spring Boot理由很實際。1.1 技術(shù)棧的橫向?qū)Ρ扰c取舍有人會問這種規(guī)模的系統(tǒng)直接用 Python Flask 或者 Node.js 不更輕快確實論啟動速度和代碼量Flask 半小時能出一個能跑的 demo。但項目往往還掛著“課程設(shè)計”或“論文”的指標(biāo)這意味著你需要一套足夠“規(guī)范”的工程骨架而不是一個腳本式應(yīng)用。Spring Boot 的價值在于它自帶完整的依賴管理、分層約定和生態(tài)Controller-Service-Mapper 的結(jié)構(gòu)讓代碼天然清晰寫論文的時候可以非常順理成章地畫出架構(gòu)圖、數(shù)據(jù)流圖而不需要為了“湊架構(gòu)”硬造層次。另一個關(guān)鍵因素是 Spring Boot 和 MySQL 的組合在就業(yè)市場上最通用答辯時被問到的概率低面試時也容易展開。前端我沒有用前后端分離。原因很簡單個人財務(wù)系統(tǒng)核心是數(shù)據(jù)的增刪改查和統(tǒng)計用 Theymeleaf 模板加 Bootstrap 撐起管理界面足夠。前后端分離意味著要額外處理跨域、Token 認(rèn)證、前端工程化構(gòu)建這些對單人小項目來說是徒增負擔(dān)而且論文里還要多寫一章前端工程化容易分散主線。當(dāng)然如果你想把系統(tǒng)包裝成前后端分離架構(gòu)作為學(xué)習(xí)成果那另說但我會先勸你把后端做厚前端夠用就好。最終的技術(shù)棧清單如下后端Spring Boot 2.7.x、MyBatis-Plus、Spring Security用于登錄鑒權(quán)數(shù)據(jù)庫MySQL 8.0模板引擎Thymeleaf前端組件Bootstrap 5 jQuery ECharts圖表展示工具Lombok、Hutool、EasyExcel導(dǎo)入導(dǎo)出1.2 架構(gòu)分層與設(shè)計模式我按照經(jīng)典的四層結(jié)構(gòu)來組織代碼controller接口層 - service業(yè)務(wù)層 - mapper數(shù)據(jù)訪問層 - entity實體類 / dto傳輸對象 / vo視圖對象這種分層的核心價值不只是“好看”而是把每個動作的職責(zé)釘死。比如你寫一個“新增一筆支出”的接口Controller 只負責(zé)接收參數(shù)和返回結(jié)果Service 負責(zé)校驗金額是否合法、賬戶余額是否夠扣、流水表是否要插入記錄Mapper 只做數(shù)據(jù)庫交互。這樣一旦出問題你順著調(diào)用鏈就能定位到是哪一層的事而不是在一坨面條代碼里翻來覆去。實體類我用了 MyBatis-Plus 的注解風(fēng)格避免寫一堆 XML。數(shù)據(jù)模型里有幾個關(guān)鍵表后面會詳細說。DTO 和 VO 我是嚴(yán)格分開的Controller 層絕不直接暴露實體類給前端因為實體字段可能包含一些內(nèi)部標(biāo)記比如邏輯刪除標(biāo)志、更新時間直接返回會泄露不必要的信息也容易讓前端字段和數(shù)據(jù)庫字段強耦合。這一點建議在寫論文時單獨拎出來講能體現(xiàn)你的工程意識。2. 數(shù)據(jù)庫設(shè)計與核心功能拆解賬本系統(tǒng)的靈魂在表結(jié)構(gòu)個人財務(wù)系統(tǒng)功能看著簡單但表設(shè)計決定了報表能不能跑得動、擴展性好不好。我見過太多人上來就建一張“流水表”一個“用戶表”結(jié)果做到統(tǒng)計一門心思用 SQL 嵌套子查詢數(shù)據(jù)庫累死代碼也難寫。這一節(jié)講清楚我最終表結(jié)構(gòu)是怎么定的以及每一步背后的思考。2.1 核心表結(jié)構(gòu)與字段設(shè)計我一共建了六張業(yè)務(wù)表加一張用戶表。六張業(yè)務(wù)表分別是賬戶表、流水表、分類表、預(yù)算表、賬單導(dǎo)入記錄表、系統(tǒng)配置表。每張表都要說清楚為什么建、字段怎么定。賬戶表account——記錄用戶下的多個資金賬戶比如現(xiàn)金、儲蓄卡、信用卡、微信錢包。關(guān)鍵字段有賬戶名稱、賬戶類型1 現(xiàn)金 2 借記卡 3 信用卡 4 電子錢包、初始余額、當(dāng)前余額、幣種當(dāng)前只做人民幣但留字段以后擴展、狀態(tài)可用/凍結(jié)、用戶 ID、備注、創(chuàng)建時間。特別注意賬戶當(dāng)前余額不要通過流水表實時 sum 去算那樣性能會越來越差。每次發(fā)生收支時在事務(wù)里同時更新賬戶余額這是個人財務(wù)系統(tǒng)的一個基礎(chǔ)優(yōu)化點。流水表transaction——這就是記賬的主表。字段流水類型1 收入 2 支出 3 轉(zhuǎn)賬、金額、賬戶 ID、分類 ID、交易時間、商戶/摘要、備注、關(guān)聯(lián)流水 ID處理轉(zhuǎn)賬時需要成對出現(xiàn)、創(chuàng)建時間。金額我用的是 DECIMAL(12,2)絕不用 DoubleJava 對應(yīng) BigDecimal這是財務(wù)數(shù)據(jù)的常識。交易時間單獨存時間戳而不是用創(chuàng)建時間替代因為用戶可能會補錄前幾天的賬。這一點很重要很多初學(xué)者直接把創(chuàng)建時間當(dāng)交易時間做統(tǒng)計時會出現(xiàn)“明明應(yīng)該是上個月的支出卻算到了這個月”的詭異現(xiàn)象。分類表category——收支分類。包括分類名稱、類型收入/支出、圖標(biāo)標(biāo)識、父分類 ID支持二級分類、是否系統(tǒng)預(yù)置、用戶 ID。系統(tǒng)預(yù)置分類比如餐飲、交通、工資、理財收入用戶還可以自定義。這里注意預(yù)置分類的 user_id 設(shè)置為 0表示全局共享自定義分類的 user_id 是自己的查詢的時候要 OR 一下確保系統(tǒng)分類和用戶分類都能加載。預(yù)算表budget——預(yù)算對象是“分類 周期”。字段用戶 ID、分類 ID、預(yù)算周期1 月 2 季 3 年、預(yù)算金額、起始日期。預(yù)算觸發(fā)邏輯需要配合定時任務(wù)或者實時計算后面展開。賬單導(dǎo)入記錄表import_record——因為要支持 Excel 導(dǎo)入銀行流水需要記錄每次導(dǎo)入的文件名、導(dǎo)入時間、成功條數(shù)、失敗條數(shù)、導(dǎo)入批次號。這個表的最大作用是冪等控制用戶重復(fù)導(dǎo)入同一個文件時通過批次號或者文件名加內(nèi)容哈希判斷是否已經(jīng)導(dǎo)入過避免流水翻倍。系統(tǒng)配置表config——鍵值對結(jié)構(gòu)存一些用戶可調(diào)的閾值比如“單筆支出超過多少時進行提醒”、“預(yù)算超支提醒提前幾天”、“默認(rèn)的統(tǒng)計幣種”。把這類參數(shù)做成配置而不是寫死在代碼里以后調(diào)整不需要重新發(fā)版本。2.2 為什么流水表不直接存“余額”這里有個容易引起討論的點流水表里我存了“發(fā)生后的余額”字段嗎沒有。原因很簡單如果存了一旦用戶刪除或修改某筆歷史流水后續(xù)所有流水的余額就全部錯亂你必須寫一大段更新邏輯去重算。而不存余額余額的實時值始終由賬戶表保存任何流水的增刪改只需要修訂賬戶表的余額即可。但查詢時往往需要展示“這筆交易發(fā)生后的賬戶余額”怎么辦我的做法是默認(rèn)不展示這個值只展示交易時間和金額。如果要看賬戶余額變化曲線就按時間區(qū)間分段計算流水累計金額再疊加賬戶初始余額。這在數(shù)據(jù)規(guī)模個人級下不會慢。對于千萬級大數(shù)據(jù)量的系統(tǒng)另說但個人財務(wù)系統(tǒng)到不了那一步。2.3 分類與預(yù)算的聯(lián)動設(shè)計預(yù)算模塊的思路是預(yù)算掛在某個分類下。月度預(yù)算是最常用的數(shù)據(jù)庫里只需要存“周期類型”和“起始日期”用的時候根據(jù)起始日期和周期類型計算出當(dāng)前周期的起止時間再在這個時間區(qū)間內(nèi)去流水表匯總該分類下所有支出金額對比預(yù)算值。舉個例子你在 3 月 10 日設(shè)置“餐飲”分類的月度預(yù)算為 2000 元周期起始日期是 2025-03-01。那么當(dāng)前周期起止時間是 2025-03-01 到 2025-03-31。系統(tǒng)查詢流水表里分類為“餐飲”、交易時間為該區(qū)間內(nèi)的支出流水總和比如已花了 1800 元剩余預(yù)算 200 元。如果又新增一筆 250 元的餐飲支出預(yù)算立即超支這個時候可以觸發(fā)提醒。這種實時計算的好處是數(shù)據(jù)永遠最新不用等到月底對賬。缺點是每次新增流水都要多查一次預(yù)算對個人系統(tǒng)這完全不是問題甚至可以接受。3. 核心功能模塊實現(xiàn)細節(jié)從登錄鑒權(quán)到統(tǒng)計報表系統(tǒng)功能我拆成了五個模塊來寫用戶與賬戶管理、收支流水管理、統(tǒng)計報表、預(yù)算提醒、賬單導(dǎo)入導(dǎo)出。每個模塊都有一些值得記錄的實現(xiàn)細節(jié)。3.1 登錄鑒權(quán)用 Spring Security 還是自己寫攔截器很多課程設(shè)計項目都在用自己寫的攔截器加 Session不是說不行但 Spring Security 作為業(yè)界成熟的權(quán)限框架寫論文時是一個加分項。我用的是 Spring Security JWT 的無狀態(tài)方案。登錄成功后后端返回一個 Token前端存儲在 localStorage后續(xù)每次請求在請求頭里帶Authorization: Bearer token。Security 配置里比較重要的是定義哪些路徑放行、哪些需要認(rèn)證。比如登錄接口、注冊接口、靜態(tài)資源放行其余全部認(rèn)證。同時定義一個自定義過濾器解析 Token 并設(shè)置用戶上下文。自定義 UserDetailsService 實現(xiàn)從數(shù)據(jù)庫加載用戶信息。這里容易踩的坑是Spring Security 默認(rèn)會開啟 CSRF如果你的接口是無狀態(tài)的 JWT必須禁用 CSRF否則 POST 請求全部會被拒絕。我當(dāng)時就是被這個坑卡了半天一直報 403排查到最后發(fā)現(xiàn)是 CSRF 沒有關(guān)。3.2 收支流水的增刪改查與事務(wù)控制新增流水的接口步驟是這樣的校驗參數(shù)類型、金額、賬戶、分類不能為空金額必須大于 0根據(jù)流水類型處理賬戶余額收入則賬戶余額增加支出則賬戶余額減少轉(zhuǎn)賬則從轉(zhuǎn)出賬戶扣錢、轉(zhuǎn)入賬戶加錢生成流水記錄插入數(shù)據(jù)庫如果是轉(zhuǎn)賬同時生成兩條流水記錄一條類型為轉(zhuǎn)賬-轉(zhuǎn)出關(guān)聯(lián)對方流水 ID一條類型為轉(zhuǎn)賬-轉(zhuǎn)入更新賬戶余額提交事務(wù)。以上每一步都在同一個事務(wù)類里面方法上加Transactional。這是一個非常關(guān)鍵的設(shè)計。假設(shè)轉(zhuǎn)賬扣了 A 賬戶的錢但在插入流水 B 時數(shù)據(jù)庫異常沒有事務(wù)就會導(dǎo)致錢扣了賬沒記上。加了事務(wù)后任何一步失敗前面所有數(shù)據(jù)庫操作都回滾保證數(shù)據(jù)一致性。修改和刪除流水要麻煩一些。修改流水時需要先把舊流水的影響還原再按新流水重新計算。刪除流水時同樣要把賬戶余額恢復(fù)為扣除該筆交易之前的狀態(tài)。這些邏輯必須在事務(wù)里執(zhí)行否則數(shù)據(jù)會越改越對不上。我建議把這部分寫一個獨立的 Service 方法并加詳盡的注釋因為在論文的“系統(tǒng)測試”部分你可以把這幾個邊界場景寫成測試用例展示系統(tǒng)的健壯性。3.3 統(tǒng)計報表ECharts 圖表的背后是聚合查詢報表模塊是個人財務(wù)系統(tǒng)里最能出效果的部分也是用戶在答辯時最容易“哇”一聲的亮點。我使用了 ECharts 展示三類圖折線圖收支趨勢、餅圖分類占比、柱狀圖月度對比。后端查詢這塊我沒有用復(fù)雜的 SQL。MyBatis-Plus 的 QueryWrapper 可以完成大部分條件篩選但涉及到按月分組、按分類分組時要寫自定義 SQL。比如統(tǒng)計某個月份每天的收入和支出總額SQL 大概是SELECT DATE_FORMAT(transaction_time, %Y-%m-%d) AS day, SUM(CASE WHEN type 1 THEN amount ELSE 0 END) AS income, SUM(CASE WHEN type 2 THEN amount ELSE 0 END) AS expense FROM transaction WHERE user_id #{userId} AND transaction_time BETWEEN #{startDate} AND #{endDate} GROUP BY DATE_FORMAT(transaction_time, %Y-%m-%d) ORDER BY day;注意這里的統(tǒng)計日期要基于交易時間而不是創(chuàng)建時間。如果用戶補錄了幾筆上個月的賬統(tǒng)計結(jié)果也要相應(yīng)體現(xiàn)到上個月的報表里這樣報表才符合真實的“個人財務(wù)”視角。還有一種常見的需求查詢某分類下的累計支出占比。SQL 的編寫思路和上面類似用 type2 且分類分組。而在 Service 層用 BigDecimal 做除法計算百分比時一定注意除零異常。用BigDecimal.ZERO.compareTo(total) 0判斷。3.4 預(yù)算提醒定時掃描還是實時觸發(fā)預(yù)算提醒我做了兩種方式。一種是實時提醒每次新增或修改支出流水后立刻查詢當(dāng)前周期的預(yù)算使用情況如果超過預(yù)算就在響應(yīng)里返回一個提醒字段前端彈出提示。這種方式用戶體驗最好但不是所有場景都覆蓋因為用戶可能批量導(dǎo)入賬單根本不會逐條看到提醒。另一種是每日定時任務(wù)用 Spring 的Scheduled在每天 20:00 檢查所有用戶的所有預(yù)算如果觸發(fā)閾值生成一條待辦提醒寫入提醒表用戶登錄后在首頁看到未讀提醒。這種方式更保險。定時任務(wù)記得在啟動類開啟EnableScheduling同時注意定時任務(wù)方法不要忘了做時間邊界上的計算避免由于時區(qū)或者系統(tǒng)時間不一致出現(xiàn)偏差。我最終是“實時觸發(fā) 每日定時”雙保險。論文里你可以把這兩種方式的優(yōu)缺點對比寫出來既是設(shè)計思考也是體現(xiàn)工程權(quán)衡的好素材。3.5 賬單導(dǎo)入導(dǎo)出EasyExcel 的實用細節(jié)支持從 Excel 導(dǎo)入銀行流水是非常實用的功能。我采用的是 EasyExcel它對大數(shù)據(jù)量的寫入做了優(yōu)化個人場景更是游刃有余。導(dǎo)入的模板結(jié)構(gòu)是交易時間、類型、金額、分類、商戶、備注。用戶上傳文件后后端解析每一行做校驗日期格式是否合法、金額是否正數(shù)、分類是否存在。如果某一行失敗記錄失敗原因繼續(xù)解析其他行最后生成一條導(dǎo)入記錄返回成功和失敗條數(shù)。這里要注意文件大小限制我配置了最大 10MB。導(dǎo)出功能相對簡單把查詢到的流水列表直接寫入 Excel同時生成一個匯總 Sheet包含總收入和總支出。導(dǎo)出的時候文件名不要用固定名字要拼上時間戳避免同一個瀏覽器重復(fù)下載相同文件時被緩存。4. 論文怎么寫才能和源碼呼應(yīng)上我的寫作順序和框架建議源碼做完了論文往往是很多人的痛點。代碼寫了一千行卻不知道怎么落到 Word 里。我自己的經(jīng)驗是論文的最佳寫作時間不是代碼寫完以后而是邊寫代碼邊記錄設(shè)計決策和測試過程。如果代碼已經(jīng)完成后再回憶著寫很多細節(jié)容易模糊。4.1 論文整體框架從摘要到結(jié)論的段落設(shè)計標(biāo)準(zhǔn)的軟件系統(tǒng)類論文推薦按照下面的骨架來填充摘要和關(guān)鍵詞摘要要包含四要素背景個人財務(wù)管理的重要性、系統(tǒng)目標(biāo)一個基于 Spring Boot 的個人財務(wù)管理系統(tǒng)、技術(shù)路線Spring Boot MySQL Thymeleaf ECharts、成果實現(xiàn)了多賬戶管理、預(yù)算提醒與統(tǒng)計報表等功能。第一章 緒論研究背景與意義、國內(nèi)外研究現(xiàn)狀、主要工作與論文結(jié)構(gòu)。這一章不用寫太多但要體現(xiàn)出你了解為什么做這個系統(tǒng)。研究現(xiàn)狀不要直接照搬百度百科可以寫傳統(tǒng)手工記賬的弊端、Excel 記賬的不足然后引出信息化方案。第二章 相關(guān)技術(shù)介紹Spring Boot、MyBatis-Plus、MySQL、ECharts、Spring Security。不要寫教科書式的定義要結(jié)合“你在這個系統(tǒng)中用它做了什么”來寫。比如“MyBatis-Plus 用于簡化數(shù)據(jù)持久層開發(fā)通過注解和 Wrapper 構(gòu)造查詢條件”比單純介紹框架特性更有價值。第三章 系統(tǒng)分析可行性分析技術(shù)、經(jīng)濟、操作、需求分析功能性需求和非功能性需求、用例圖、業(yè)務(wù)流程分析。畫用例圖可以用開源繪圖工具論文里放清晰的矢量圖。第四章 系統(tǒng)設(shè)計總體架構(gòu)圖、模塊設(shè)計、數(shù)據(jù)庫設(shè)計ER 圖、表結(jié)構(gòu)說明、接口設(shè)計。這一章是你系統(tǒng)設(shè)計的核心需要和源代碼完全對應(yīng)。第五章 系統(tǒng)實現(xiàn)每個核心功能模塊的界面截圖、核心代碼片段和關(guān)鍵邏輯說明。代碼片段不要貼大段只貼最核心的方法并加上講解。第六章 系統(tǒng)測試測試環(huán)境、測試用例表、功能測試結(jié)果分析、性能測試比如并發(fā)登錄或者大數(shù)據(jù)量查詢的響應(yīng)時間。第七章 總結(jié)與展望說清楚完成了什么、不足是什么、未來可以怎么改進。4.2 如何把源碼和論文對應(yīng)起來避免“文不對碼”我見過大量論文里代碼截屏和實際源碼不一致或者文中描述的功能在代碼里根本沒有。這個問題會造成兩個影響答辯時被問到細節(jié)回答不上來或者查重的時候出現(xiàn)“邏輯斷裂”。我的做法很簡單論文里涉及到的每個功能都要在源碼里能找到對應(yīng)模塊寫出一張“功能-模塊-方法”的對照表。比如“月度預(yù)算超支提醒”這個功能在論文中寫實現(xiàn)邏輯時要明確指出對應(yīng)的 Service 類是 BudgetServiceImpl核心方法是checkBudgetExceeded(Long userId, Long categoryId, BigDecimal amount)。這聽起來像是多余的工作但對答辯非常有利。老師問“你預(yù)算提醒是怎么實現(xiàn)的”你直接說“我在新增流水后調(diào)用 BudgetService 的實時檢查方法同時每天定時掃描所有用戶預(yù)算兩周后生成提醒列表?!比绻阍谡撐睦镆呀?jīng)把流程圖畫出來再配合源碼對照那就是穩(wěn)的。4.3 圖表收集與測試數(shù)據(jù)準(zhǔn)備論文中需要的數(shù)據(jù)圖表不要臨到用時才去找而是在開發(fā)過程中每個功能穩(wěn)定后隨手截圖保存。我建議按模塊建目錄存放截圖登錄、賬戶列表、新增流水、統(tǒng)計報表、預(yù)算設(shè)置、導(dǎo)入導(dǎo)出。測試數(shù)據(jù)也盡量做得真實比如三個賬戶、連續(xù)六個月的數(shù)據(jù)每個月有日常消費的波動。這些數(shù)據(jù)可以幫助你生成漂亮的折線圖和餅圖讓論文版面更有說服力。測試部分我寫了十幾個用例覆蓋核心業(yè)務(wù)正常新增收入、正常新增支出、支出大于余額是不是要攔截注意個人財務(wù)系統(tǒng)不建議攔截因為很多時候信用卡或信用賬戶允許超額但這個決策要在論文里說明、轉(zhuǎn)賬后兩個賬戶余額是否正確、刪除流水后余額是否恢復(fù)、預(yù)算超支是否提醒、導(dǎo)入重復(fù)文件是否冪等、未登錄訪問接口是否返回 401。每個用例包含輸入、執(zhí)行步驟、預(yù)期結(jié)果、實際結(jié)果這樣的測試報告一眼就能看出工作量。5. 常見問題與排查技巧實錄我踩過的坑和解決方案開發(fā)過程中有幾個問題非常典型我?guī)缀趺看巫鐾愴椖慷紩龅竭@里統(tǒng)一列出來并附上我的排查思路大家可以少走彎路。5.1 新增支出后賬戶余額變成負數(shù)剛開始我把余額更新邏輯直接寫在 Service 里支出時balance.subtract(amount)。看起來沒問題但一旦金額字段類型不匹配比如前端傳的是字符串“50”類型轉(zhuǎn)換異?;蛘呔葋G失就會出現(xiàn)余額不符。后來我在 DTO 里對金額字段使用 BigDecimal 類型并在 getter/setter 寫校驗統(tǒng)一在 Service 層做金額格式化。余額負數(shù)本身不算 bug關(guān)鍵是你需不需要限制。多數(shù)個人財務(wù)管理軟件允許負數(shù)比如信用卡透支我的處理是如果賬戶類型是信用卡允許余額為負是借記卡或現(xiàn)金則提示“余額不足”。這種業(yè)務(wù)規(guī)則要寫清楚不然用戶會疑惑。5.2 時間統(tǒng)計相差八小時的問題查詢月度收支時總是出現(xiàn)數(shù)據(jù)偏少。排查后發(fā)現(xiàn)MySQL 連接串里沒有配置serverTimezoneAsia/Shanghai導(dǎo)致 JDBC 時區(qū)使用了默認(rèn)時區(qū)和本地時間差八個小時。在 database url 上加上參數(shù)就能解決。另外Java 后端返回日期給前端的時候如果用的是Date類型JSON 序列化有時會變成時間戳需要在實體字段上用JsonFormat(pattern yyyy-MM-dd HH:mm:ss)明確格式。5.3 JWT 登錄后每次請求 401這個問題的常見原因有兩個。第一是 Token 過期時間設(shè)置太短比如 30 分鐘用戶用著用著就失效了前端沒有做好刷新機制第二是前后端 Token 拼接格式不一致。我在前端封裝了一個 axios 實例統(tǒng)一在請求攔截器里加Authorization頭避免漏加。后端自定義過濾器里如果解析 Token 失敗要返回 JSON 提示而不是默認(rèn)的重定向到登錄頁。5.4 數(shù)據(jù)庫字段與實體類字段映射報錯MyBatis-Plus 默認(rèn)實體類字段駝峰轉(zhuǎn)下劃線但如果數(shù)據(jù)庫字段命名不規(guī)范比如一個字段是budgetCycle數(shù)據(jù)庫中寫成了budget_cycle沒問題如果寫成了budgetcycle那就會映射不上。這種問題報錯不一定明顯數(shù)據(jù)查出來是 null。排查時候把 SQL 日志打開MyBatis-Plus 會在控制臺打出執(zhí)行的 SQL對照字段就能發(fā)現(xiàn)。5.5 導(dǎo)入 Excel 時中文亂碼EasyExcel 本身對 UTF-8 支持很好但如果用戶上傳的 Excel 是舊版的.xls編碼會有點小問題。我的處理方式是強制要求上傳.xlsx格式并在前端限制文件后綴。如果確實要兼容.xls可以讀取流并轉(zhuǎn)碼但會增加復(fù)雜度建議不要為了這種邊緣需求拖累主流程。5.6 定時任務(wù)重復(fù)執(zhí)行部署到服務(wù)器后有時候發(fā)現(xiàn)預(yù)算提醒重復(fù)發(fā)。這可能是因為部署了多個實例例如通過多個端口啟動每個實例都跑定時任務(wù)。個人項目一般只有一個實例但如果用 Docker 擴容時要注意這個問題。解決方案有很多比如使用分布式鎖或者為任務(wù)指定一個固定實例執(zhí)行。論文里如果提到部署方案這個點可以作為一個“系統(tǒng)不足與展望”來寫。6. 代碼層面的幾個實用優(yōu)化讓系統(tǒng)跑得更穩(wěn)更合理除了功能實現(xiàn)我還做了幾處工程優(yōu)化這些細節(jié)雖然不起眼但能讓你寫論文時有更多“干貨”可寫。6.1 統(tǒng)一響應(yīng)結(jié)果封裝后端所有接口統(tǒng)一返回ResultT結(jié)構(gòu)包含 code、message、data 三個字段。前端判斷 code 是否為 200 來決定提示成功還是失敗。這個封裝的好處是無論控制器里面執(zhí)行什么操作前端都可以用一套邏輯處理成功和異常代碼整齊很多。在全局異常處理類里捕獲參數(shù)校驗異常、業(yè)務(wù)異常、未知異常分別返回不同的 code日志記錄異常堆棧。6.2 參數(shù)校驗怎么寫得優(yōu)雅又嚴(yán)謹(jǐn)我用的校驗方式是 Spring 的Validated注解配合 DTO 上的校驗注解。在新增流水接口中DTO 字段上標(biāo)注NotNull、DecimalMin(0.01)、Size等避免在 Service 里手寫一長串 if。手寫校驗容易遺漏而且代碼可讀性差。使用注解校驗后參數(shù)校驗邏輯清晰還會自動把錯誤信息返回給前端。當(dāng)然有些跨字段的校驗比如轉(zhuǎn)賬時轉(zhuǎn)出賬戶不能等于轉(zhuǎn)入賬戶需要放在 Service 層處理因為依賴業(yè)務(wù)上下文。6.3 為什么數(shù)據(jù)庫連接池要配置參數(shù)默認(rèn)的 HikariCP 連接池開箱即用但如果不配置最大連接數(shù)當(dāng)并發(fā)量稍高時可能出現(xiàn)連接獲取超時。我給項目配置了最小連接數(shù) 5、最大連接數(shù) 20連接超時 30 秒。這個配置對于個人系統(tǒng)綽綽有余但論文里的“性能測試”如果拿這個參數(shù)來說明系統(tǒng)能支撐多少并發(fā)會更有說服力。實際要做并發(fā)測試可以用簡單工具模擬 50 個用戶同時調(diào)用查詢流水接口觀察響應(yīng)時間。6.4 接口層面的防重復(fù)提交個人財務(wù)系統(tǒng)的用戶如果手快可能雙擊“保存”按鈕導(dǎo)致同一條流水插入兩次。為了防止這種問題我給“新增流水”接口增加了一個基于 Token 的防重復(fù)提交機制前端在首次請求時申請一個冪等鍵后端在 Redis 里記錄這個鍵過期時間 5 分鐘處理請求前檢查鍵是否存在如果已存在就拒絕重復(fù)提交處理完成后刪除鍵。當(dāng)然沒有 Redis 的課程設(shè)計也可以基于數(shù)據(jù)庫唯一索引或應(yīng)用內(nèi) ConcurrentHashMap 實現(xiàn)后者適合單機場景但不夠規(guī)范。6.5 大數(shù)據(jù)量查詢?nèi)绾伪苊狻奥?SQL”假設(shè)用戶連續(xù)記賬幾年流水表可能有幾萬條數(shù)據(jù)按月統(tǒng)計如果不走索引也會慢。我設(shè)計的索引策略是在transaction表上以user_id transaction_time作為聯(lián)合索引同時transaction_time單獨建索引因為每次統(tǒng)計都要按時間過濾。分類統(tǒng)計的查詢會用到category_id因此也建了索引。這些索引在數(shù)據(jù)量不大時看不出來效果但寫論文時可以在數(shù)據(jù)庫設(shè)計里說明“為了提高報表查詢效率建立了如下復(fù)合索引”這是很標(biāo)準(zhǔn)的加分項。7. 個人經(jīng)驗總結(jié)與擴展想法整套系統(tǒng)從零到完成我前后花了兩周左右的業(yè)余時間其中數(shù)據(jù)庫設(shè)計和預(yù)算模塊的打磨花費的精力最多。數(shù)據(jù)庫設(shè)計功在一時如果你一開始表結(jié)構(gòu)建得不合理后面所有代碼都會受影響預(yù)算模塊則是因為它同時涉及實時、批量、提醒、狀態(tài)管理等多個維度聯(lián)動性最強。如果你也準(zhǔn)備做類似項目我建議先不要急著寫代碼花一個晚上把核心流程捋清楚。怎么算賬先說清楚支出、收入、轉(zhuǎn)賬的業(yè)務(wù)規(guī)則。比如“轉(zhuǎn)賬算不算收入”我這里的定義是轉(zhuǎn)賬不算收入和支出只是賬戶間資金轉(zhuǎn)移。那你把這兩條流水歸到“轉(zhuǎn)賬”類型統(tǒng)計收入支出時要把它們排除。這種業(yè)務(wù)定義在論文的“需求分析”里必須寫明白不然圖表會亂。還有一點做完系統(tǒng)后我強烈建議用一套自己攢的測試數(shù)據(jù)跑一遍從月初到月末的完整流程錄入收入、分多筆支出、調(diào)整某筆大額支出、導(dǎo)出月度報表看整體數(shù)據(jù)是否一致。我當(dāng)時跑出了一個問題分類統(tǒng)計的報表把“轉(zhuǎn)賬”也算成了支出后來在 SQL 里加上了type ! 3條件才解決。這類問題如果不通過整體流程測試很難暴露出來。最后分享一個小技巧項目里可以存放一個docs/目錄把開發(fā)中的截圖、關(guān)鍵設(shè)計的思維導(dǎo)圖、測試用例的表格都放進去。寫論文時直接從這里取材料省去到處找截圖的痛苦。代碼的版本管理建議從第一天就建立我在項目早期因為偷懶沒有用版本管理后來改動分類表字段時差點回不去從此再也不敢不開倉庫。這個系統(tǒng)后續(xù)想繼續(xù)擴展的話方向其實很多。比如接入真實銀行 API 做自動同步賬單或者加入簡單的投資理財賬戶基金、股票來記錄持倉市值。不過這些功能對個人系統(tǒng)來說邊際成本會明顯上升建議先把手頭的記賬和預(yù)算功能做到順手再考慮更復(fù)雜的擴展。我個人的體會是好用的財務(wù)系統(tǒng)核心不在于功能多而在于每一次記賬都干脆利落每一次統(tǒng)計都對得上賬。先把這一點做扎實源碼和論文都會自然立得住。