老院管理系統(tǒng)實(shí)戰(zhàn)解析)
前后端分離的養(yǎng)老院管理系統(tǒng)這個(gè)選題其實(shí)挺有代表性的。養(yǎng)老院管理系統(tǒng)屬于典型的“信息管理系統(tǒng)”業(yè)務(wù)場景技術(shù)棧固定、模塊清晰、權(quán)限分明非常適合用來完整走一遍 SpringBoot Vue3 MyBatis MySQL 的實(shí)戰(zhàn)流程。我基于實(shí)際開發(fā)經(jīng)驗(yàn)把這個(gè)項(xiàng)目的源碼結(jié)構(gòu)、模塊設(shè)計(jì)、數(shù)據(jù)庫建模、前后端對接、本地部署啟動(dòng)以及常見問題排查完整梳理一遍希望能給正在做同類項(xiàng)目的同學(xué)提供一份可以抄作業(yè)的參考。無論你是畢業(yè)設(shè)計(jì)需要還是想快速搭建一套可用性強(qiáng)的管理后臺(tái)這篇文章都值得認(rèn)真看完。1. 項(xiàng)目整體設(shè)計(jì)與模塊拆解1.1 養(yǎng)老院管理系統(tǒng)的核心業(yè)務(wù)范疇養(yǎng)老院管理系統(tǒng)說白了就是一套“人、財(cái)、物”三位一體的信息管理平臺(tái)。這里的人不只是老人還有護(hù)工、管理人員和家屬財(cái)涉及床位費(fèi)、護(hù)理費(fèi)、餐飲費(fèi)、醫(yī)療費(fèi)用等多種計(jì)費(fèi)維度物則是床位資源、藥品庫存、設(shè)備的維護(hù)記錄。我在梳理這個(gè)項(xiàng)目時(shí)第一反應(yīng)是把它拆成幾個(gè)核心域長者的基本信息管理、入住與退住流程管理、床位資源管理、護(hù)理任務(wù)與排班管理、健康檔案與生命體征記錄、收費(fèi)與退費(fèi)結(jié)算、家屬溝通記錄、系統(tǒng)用戶與角色權(quán)限。不要一上來就追求大而全的面面俱到而是先把“住得進(jìn)來、管得住人、算得清賬、陪得好老”這四件核心事做扎實(shí)。1.2 用戶角色與權(quán)限模型設(shè)計(jì)與普通電商后臺(tái)不同養(yǎng)老院系統(tǒng)的角色模型更貼近“組織架構(gòu) 崗位職責(zé)”。我建議在設(shè)計(jì)階段就劃分出超級(jí)管理員、院長、護(hù)士長、護(hù)理員、財(cái)務(wù)人員、社工/客服、家屬只讀或有限授權(quán)這些角色對應(yīng)的菜單權(quán)限和數(shù)據(jù)權(quán)限都不同。這個(gè)項(xiàng)目的權(quán)限設(shè)計(jì)重點(diǎn)是“數(shù)據(jù)權(quán)限”。比如護(hù)士長可以看全樓的護(hù)理記錄但普通護(hù)理員只能維護(hù)自己負(fù)責(zé)樓層或自己名下老人的記錄財(cái)務(wù)人員只管費(fèi)用臺(tái)賬不應(yīng)該觸及護(hù)理錄入。在前后端分離架構(gòu)下后端負(fù)責(zé)接口鑒權(quán)前端負(fù)責(zé)菜單路由控制兩者結(jié)合才能避免低權(quán)限用戶繞過頁面直接請求接口。我通常的做法是后端用注解 攔截器統(tǒng)一控制接口訪問權(quán)限前端用動(dòng)態(tài)路由按角色渲染菜單雙保險(xiǎn)最穩(wěn)妥。1.3 為什么堅(jiān)持選擇前后端分離架構(gòu)項(xiàng)目標(biāo)題里明確寫了“前后端分離”這個(gè)選擇是有實(shí)際考量的。養(yǎng)老院的網(wǎng)絡(luò)環(huán)境往往并不理想局域網(wǎng)或低帶寬場景很常見前后端分離能夠降低單次頁面刷新帶來的服務(wù)器壓力同時(shí)方便后續(xù)把前端部署到獨(dú)立的靜態(tài)服務(wù)器后端只提供 JSON 接口天然適合未來拓展 App 或小程序端。另外前后端分離在實(shí)際開發(fā)效率上也更有優(yōu)勢。前端可以并行開發(fā)頁面邏輯后端可以專注設(shè)計(jì) API 和業(yè)務(wù)規(guī)則只要提前約定好接口文檔整個(gè)開發(fā)周期能縮短不少。缺點(diǎn)是前期的工程化配置會(huì)稍微繁瑣一些比如跨域配置、本地代理轉(zhuǎn)發(fā)、Token 鑒權(quán)方案等都需要盡早確定但項(xiàng)目跑順之后帶來的維護(hù)便利性是值得的。2. 核心技術(shù)選型與版本選擇的背后邏輯2.1 后端框架SpringBoot 版本到底選哪個(gè)這個(gè)項(xiàng)目用的是 SpringBoot這是 Java 后端最主流的基礎(chǔ)框架。很多初學(xué)者拿到一份老教程直接照著 2.x 版本的寫法去搭項(xiàng)目結(jié)果發(fā)現(xiàn)依賴下載報(bào)錯(cuò)、yml 配置自動(dòng)提示失效、啟動(dòng)失敗最后把問題歸咎于“版本太新不兼容”。實(shí)際上,SpringBoot 版本的選擇核心是“兼容性 穩(wěn)定性”。我在這個(gè)項(xiàng)目里推薦使用SpringBoot 2.7.x原因很實(shí)際2.7 是 2.x 系列的最后一個(gè)功能分支大多數(shù)社區(qū)資料、培訓(xùn)課程、第三方中間件比如 Druid、PageHelper都對它做了充分適配同時(shí)它又支持 Java 8這對大量還在使用 JDK 8 的機(jī)器非常友好。如果選 3.x 甚至更高版本一方面強(qiáng)制要求 JDK 17另一方面很多老派 MyBatis 相關(guān) starter 的命名和自動(dòng)配置方式都變了新手上手成本明顯增加。如果非要使用 SpringBoot 3.x 版本也不是不行但要注意選擇對應(yīng)的mybatis-spring-boot-starter版本并啟用jakarta命名空間。對于以“快速構(gòu)建管理系統(tǒng)”為目標(biāo)的項(xiàng)目來說我更建議穩(wěn)定優(yōu)先先把業(yè)務(wù)跑通再來升級(jí)換代。2.2 為什么選擇 MyBatis 而不是 JPA現(xiàn)在 Java 圈子做持久層主要就是 MyBatis 和 JPA 兩大派系。這個(gè)項(xiàng)目選型定的 MyBatis我的理由其實(shí)很直白管理系統(tǒng)里報(bào)表和統(tǒng)計(jì)查詢特別多像“統(tǒng)計(jì)各樓層入住率”、按月份匯總護(hù)理工時(shí)、篩選未繳費(fèi)老人名單。這類多表聯(lián)查、動(dòng)態(tài)拼接條件的 SQL用 MyBatis 的 XML 文件維護(hù)起來更加直觀可控。MyBatis 的優(yōu)勢在于“SQL 自由度”。當(dāng)一個(gè)查詢條件多達(dá)五六個(gè)維度時(shí)MyBatis 的動(dòng)態(tài) SQLif、where、foreach寫起來非常趁手而且 SQL 審計(jì)方便DBA 也樂意看。JPA 在單表 CRUD 上確實(shí)省事但涉及復(fù)雜查詢時(shí)要么寫 JPQL要么退回到原生 SQL反而繞了遠(yuǎn)路。2.3 前端選型Vue3 Element Plus 組合Vue3 現(xiàn)在已經(jīng)非常成熟如果你是從零開始做管理系統(tǒng)完全沒必要再回頭看 Vue2。Vue3 的組合式 APIComposition API帶來了更靈活的邏輯組織方式配合 Vite 的開發(fā)調(diào)試體驗(yàn)熱更新速度快到幾乎無感。管理后臺(tái)的前端組件庫我最常用的組合是Vue3 Element Plus。Element Plus 就是 Vue2 時(shí)代 Element UI 的升級(jí)版對表格、表單、彈窗、分頁、樹控件的封裝非常完善和后臺(tái)管理系統(tǒng)的頁面形態(tài)高度契合。表格的分頁、排序、多選彈窗的表單校驗(yàn)這些高頻需求都有現(xiàn)成方案寫起來比從零手搓高效得多。2.4 MySQL 數(shù)據(jù)庫選擇與存儲(chǔ)引擎MySQL 是這個(gè)項(xiàng)目的數(shù)據(jù)庫底座也是標(biāo)題里明確的技術(shù)棧成員。個(gè)人項(xiàng)目或畢業(yè)設(shè)計(jì)場景下使用MySQL 8.0是比較推薦的版本。相比 5.78.0 的窗口函數(shù)、公共表表達(dá)式WITH 子句、默認(rèn)字符集 utf8mb4 都更加好用而且官方持續(xù)維護(hù)安全補(bǔ)丁穩(wěn)定性和性能都有保障。數(shù)據(jù)庫默認(rèn)使用 InnoDB 存儲(chǔ)引擎事務(wù)支持和行級(jí)鎖是硬指標(biāo)。養(yǎng)老院系統(tǒng)里涉及費(fèi)用扣減、床位分配這類數(shù)據(jù)一致性敏感的操作沒有事務(wù)保障很容易出現(xiàn)并發(fā)問題。表結(jié)構(gòu)設(shè)計(jì)上嚴(yán)格遵循“一表一業(yè)務(wù)域”的原則主鍵統(tǒng)一用自增 ID 作為業(yè)務(wù)主鍵同時(shí)給外鍵關(guān)聯(lián)字段加上普通索引這些細(xì)節(jié)在后續(xù)“數(shù)據(jù)庫設(shè)計(jì)”章節(jié)我會(huì)展開講。3. 數(shù)據(jù)庫設(shè)計(jì)核心表結(jié)構(gòu)與關(guān)系分析3.1 長者信息表的設(shè)計(jì)思路老人表是系統(tǒng)的核心主表我一般命名為elder字段上除了姓名、性別、身份證號(hào)、聯(lián)系電話這類基礎(chǔ)信息之外特別建議加上這些容易被忽略的字段老人緊急聯(lián)系人及電話、入住日期、床號(hào)ID、護(hù)理等級(jí)自理/半自理/全護(hù)理、健康狀態(tài)摘要、家屬ID關(guān)聯(lián)家屬表。其中“入住狀態(tài)”字段非常關(guān)鍵建議用 tinyint 標(biāo)識(shí)0表示空床/待入住1表示在住2表示已退住后續(xù)所有統(tǒng)計(jì)都基于這個(gè)字段做過濾。戶籍地址和現(xiàn)居住址也要分開存緊急聯(lián)系人的關(guān)系字段不要只存姓名最好關(guān)聯(lián)到家屬用戶表因?yàn)檫@個(gè)項(xiàng)目里家屬是有登錄入口的雖然權(quán)限受限但查看老人健康記錄、接收賬單通知都需要做關(guān)聯(lián)。3.2 床位與樓層資源管理床位管理是養(yǎng)老院特有的業(yè)務(wù)。我設(shè)計(jì)的是“房間-床位”二級(jí)結(jié)構(gòu)而不是單一的床號(hào)字段。房間表存樓層、房號(hào)、房間類型單人/雙人/多人間、房間定價(jià)床位表掛房間ID額外存床位的當(dāng)前狀態(tài)空閑/已入住/維修中。這樣一個(gè)設(shè)計(jì)帶來兩個(gè)好處一是收費(fèi)規(guī)則可以按房間類型靈活設(shè)定二是樓層和房間可以做成樹形結(jié)構(gòu)展示前端展示體驗(yàn)更好。注意床位編號(hào)和房間號(hào)不建議合并成一個(gè)字符串字段否則后續(xù)要做床位統(tǒng)計(jì)和調(diào)房操作的時(shí)候字符串拆分的痛苦會(huì)讓你想重做表。3.3 健康檔案與生命體征記錄老人健康管理需要兩張表健康檔案主表health_profile和體檢/體征記錄表vital_sign_record。檔案主表存老人的過敏史、既往病史、慢病管理方案、主治醫(yī)生建議等靜態(tài)信息體征記錄表則按時(shí)間維度存血壓、心率、血糖、體溫等動(dòng)態(tài)數(shù)據(jù)每次老人例行體檢或日常測量后追加一條記錄。這兩張表的分工要明確檔案是基線記錄是動(dòng)態(tài)變化。前端展示時(shí)健康檔案頁顯示“最新一次體征記錄 歷史趨勢圖”醫(yī)生端則看完整檔案詳情。每次測量記錄都建議帶上錄入人和記錄時(shí)間方便追溯護(hù)理責(zé)任。3.4 護(hù)理任務(wù)與排班記錄護(hù)理排班在業(yè)務(wù)上比較復(fù)雜因?yàn)轲B(yǎng)老院是按“護(hù)理等級(jí) 老人居住樓層”分配護(hù)理員。護(hù)理任務(wù)表nursing_task存儲(chǔ)每天的護(hù)理任務(wù)包括任務(wù)類型喂藥、翻身、助浴、復(fù)健等、執(zhí)行日期、執(zhí)行狀態(tài)待執(zhí)行/已完成/已跳過。nursing_schedule表則記錄護(hù)理員的排班包括排班日期、早中晚班次、負(fù)責(zé)樓層。我的建議是任務(wù)和排班分開設(shè)計(jì)。排班是“人”的安排任務(wù)是“事”的拆解。如果合并成一張表查詢邏輯會(huì)非?;靵y后續(xù)統(tǒng)計(jì)每個(gè)護(hù)理員的工作負(fù)荷也會(huì)變得很麻煩。實(shí)際項(xiàng)目里我還會(huì)加一個(gè)“老人生日提醒”功能就是從老人表里查近7天生日的數(shù)據(jù)推送給社工做活動(dòng)安排。3.5 費(fèi)用管理與收費(fèi)記錄費(fèi)用模塊我會(huì)拆成三個(gè)表fee_item費(fèi)用項(xiàng)目表床位費(fèi)、護(hù)理費(fèi)、伙食費(fèi)、醫(yī)療費(fèi)、charge_record收費(fèi)記錄表、refund_record退費(fèi)記錄表。計(jì)費(fèi)的思路是每月初自動(dòng)生成待繳賬單根據(jù)老人的房間類型定價(jià)、護(hù)理等級(jí)定價(jià)、當(dāng)月入住天數(shù)自動(dòng)計(jì)算床位費(fèi)和護(hù)理費(fèi)加上伙食費(fèi)和額外醫(yī)療消費(fèi)生成月度賬單。關(guān)于時(shí)間重疊的收費(fèi)計(jì)算說個(gè)設(shè)計(jì)機(jī)巧數(shù)據(jù)庫里存“入住日期”和“退住日期”計(jì)算當(dāng)月費(fèi)用時(shí)先處理入住日期再根據(jù)退住狀態(tài)拆月計(jì)費(fèi)。比如老人15號(hào)入住當(dāng)月只收15號(hào)之后的天數(shù)下月正常收整月。退住當(dāng)月則只收到退住當(dāng)天為止未消費(fèi)的預(yù)繳項(xiàng)走退款流程。這套規(guī)則在代碼里用一個(gè)公共計(jì)算模塊實(shí)現(xiàn)不要散落在各個(gè)業(yè)務(wù)方法中。4. 后端核心實(shí)現(xiàn)與代碼展開4.1 項(xiàng)目初始化與基礎(chǔ)配置后端工程的初始化方式我這里直接用 Spring Initializr 生成基礎(chǔ)骨架或者直接從一個(gè)已有的 clean 模板項(xiàng)目復(fù)制改造。包結(jié)構(gòu)建議按“功能模塊分包”而不是“技術(shù)分層分包”。我之前吃過按 controller/service/mapper 分包的虧項(xiàng)目小還好項(xiàng)目一大了找代碼就是災(zāi)難?,F(xiàn)在更推薦的做法是controller、service、mapper、entity、dto、vo作為頂層包下面按業(yè)務(wù)模塊再分比如controller/elder、service/charge。基礎(chǔ)配置集中在application.yml中數(shù)據(jù)源建議使用 HikariCP這是 SpringBoot 默認(rèn)內(nèi)置的連接池性能表現(xiàn)優(yōu)秀不需要額外引入其他連接池組件。配置示例server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/eldercare_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 hikari: maximum-pool-size: 10 minimum-idle: 5 connection-timeout: 30000 mybatis: mapper-locations: classpath:mapper/**/*.xml type-aliases-package: com.eldercare.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true這個(gè)配置很關(guān)鍵它能讓數(shù)據(jù)庫的elder_name自動(dòng)映射到實(shí)體類的elderName少寫很多冗余的映射配置。StdOutImpl是將 SQL 打印到控制臺(tái)開發(fā)階段建議開啟排查問題的時(shí)候能看到完整的 SQL 語句和參數(shù)值。4.2 統(tǒng)一響應(yīng)體與全局異常處理后端和前端的交互格式一定要統(tǒng)一。我習(xí)慣定義一個(gè)通用返回體ResultT字段包括code狀態(tài)碼、message提示信息、data返回?cái)?shù)據(jù)、timestamp時(shí)間戳。成功時(shí) code 為 200失敗時(shí)按業(yè)務(wù)錯(cuò)誤碼區(qū)分。這樣前端只需要對返回包裝做一次統(tǒng)一攔截處理不用每個(gè)接口單獨(dú)判斷。全局異常處理用RestControllerAdvice來實(shí)現(xiàn)分別捕獲參數(shù)校驗(yàn)異常MethodArgumentNotValidException、業(yè)務(wù)異常自定義BusinessException、未登錄NotLoginException以及數(shù)據(jù)庫異常。不要在 Controller 里寫 try-catch 包業(yè)務(wù)然后把堆棧拋給前端看這種做法既不安全也沒有用戶體驗(yàn)。全局異常處理是前后端分離項(xiàng)目的底線工程。4.3 MyBatis 動(dòng)態(tài) SQL 實(shí)戰(zhàn)查詢老人列表時(shí)搜索條件往往是不固定的按姓名模糊查詢、按護(hù)理等級(jí)精確篩選、按入住狀態(tài)篩選、按樓層篩選還可能要求按入住日期范圍排序。這種場景就是 MyBatis 動(dòng)態(tài) SQL 的主場。以老人列表分頁查詢?yōu)槔齅apper XML 大致長這樣select idselectElderPage resultTypecom.eldercare.entity.Elder SELECT e.*, r.floor_no, r.room_name, b.bed_no FROM elder e LEFT JOIN bed b ON e.bed_id b.id LEFT JOIN room r ON b.room_id r.id where if testkeyword ! null and keyword ! AND (e.elder_name LIKE CONCAT(%, #{keyword}, %) OR e.id_card LIKE CONCAT(%, #{keyword}, %)) /if if testcareLevel ! null AND e.care_level #{careLevel} /if if teststatus ! null AND e.status #{status} /if if testfloorNo ! null AND r.floor_no #{floorNo} /if /where ORDER BY e.create_time DESC /selectLEFT JOIN 比關(guān)聯(lián)子查詢效率更好特別是在大表場景下。模糊查詢使用LIKE CONCAT(%, #{keyword}, %)的方式不用手動(dòng)拼接字符串SQL 注入的防線就在這一層起作用了。4.4 登錄認(rèn)證與權(quán)限控制的落地實(shí)現(xiàn)登錄認(rèn)證我采用的是 JWTJSON Web Token方案這也是目前前后端分離項(xiàng)目最主流的做法。用戶登錄成功后后端簽發(fā)一個(gè)帶過期時(shí)間的 Token前端保存到 localStorage 或內(nèi)存中后續(xù)每次請求在請求頭Authorization字段帶上這個(gè) Token。后端用一個(gè)攔截器HandlerInterceptor統(tǒng)一校驗(yàn) Token 合法性解析出用戶ID、角色信息存入 ThreadLocal 方便后續(xù)業(yè)務(wù)代碼直接獲取當(dāng)前登錄用戶。放行白名單包括登錄接口、驗(yàn)證碼接口和文件下載接口。這里有個(gè)容易忽略的細(xì)節(jié)Token 過期前前端應(yīng)該通過響應(yīng)攔截器統(tǒng)一處理 401 狀態(tài)碼自動(dòng)跳轉(zhuǎn)到登錄頁否則用戶看到的是毫無提示的報(bào)錯(cuò)頁。角色的菜單權(quán)限我是直接用數(shù)據(jù)庫配置菜單表sys_menu存前端路由需要的名稱、路徑、組件、圖標(biāo)、排序、父ID角色菜單關(guān)聯(lián)表存可見菜單集合。登錄時(shí)一次查詢出該角色可見的菜單樹返回給前端前端動(dòng)態(tài)注冊路由這樣后端只需要校驗(yàn)接口權(quán)限前端只展示有權(quán)限的菜單入口體驗(yàn)和安全性兼顧。4.5 文件上傳與圖片預(yù)覽養(yǎng)老院系統(tǒng)里老人生病、體檢、入住合同等等這些場景大多需要上傳圖片或者 PDF 附件做存檔。SpringBoot 處理文件上傳使用MultipartFile接收文件存儲(chǔ)到本地目錄并給文件生成唯一的文件名圖片訪問路徑通過靜態(tài)資源映射暴露出去不要直接把用戶原始文件名保存到數(shù)據(jù)庫里因?yàn)樵嘉募赡軘y帶路徑信息、惡意字符甚至有重名覆蓋的風(fēng)險(xiǎn)。如果是集群部署建議后續(xù)把文件存儲(chǔ)切到對象存儲(chǔ)服務(wù)但在單機(jī)項(xiàng)目階段本地存儲(chǔ)完全夠用。敏感資料比如合同和身份證照片不建議在瀏覽器直接暴露原始訪問鏈接而是通過接口校驗(yàn)權(quán)限后再返回文件流這個(gè)小細(xì)節(jié)直接影響合規(guī)審計(jì)。5. 前端項(xiàng)目的工程化與關(guān)鍵頁面實(shí)現(xiàn)5.1 Vite 創(chuàng)建 Vue3 項(xiàng)目與目錄結(jié)構(gòu)我推薦直接用 Vite 創(chuàng)建項(xiàng)目這是當(dāng)前 Vue3 項(xiàng)目的最佳實(shí)踐。先執(zhí)行一句命令然后按提示選擇 Vue JavaScript 或 Vue TypeScript 模板。項(xiàng)目創(chuàng)建后前端目錄結(jié)構(gòu)我習(xí)慣按類型和功能混合組織src/ ├── api/ // 接口請求模塊 ├── assets/ // 靜態(tài)資源 ├── components/ // 通用組件 ├── layout/ // 布局組件 ├── router/ // 路由配置 ├── store/ // Pinia 狀態(tài)管理 ├── utils/ // 工具函數(shù) ├── views/ // 頁面視圖 ├── App.vue └── main.js組件庫安裝 Element Plus 之后在 main.js 里全局注冊全量引入即可做管理后臺(tái)不需要刻意優(yōu)化打包體積交互優(yōu)先。5.2 動(dòng)態(tài)路由與菜單權(quán)限控制Vue Router 使用addRoute方法動(dòng)態(tài)添加路由這是一套成熟的路由權(quán)限方案。用戶登錄成功后后端返回菜單集合前端根據(jù)菜單數(shù)據(jù)動(dòng)態(tài)生成普通用戶的可訪問路由表并把這些路由通過router.addRoute()注冊到路由實(shí)例中。這里需要注意順序問題刷新頁面時(shí)路由已經(jīng)動(dòng)態(tài)添加完畢但如果用戶直接刷新瀏覽器前端應(yīng)用每次都會(huì)重新執(zhí)行登錄態(tài)檢查和動(dòng)態(tài)路由加載。所以動(dòng)態(tài)路由的加載邏輯要放在全局前置守衛(wèi)中否則刷新后就找不到路由組件頁面白屏。5.3 基于 Axios 的請求封裝與認(rèn)證攔截前端的所有 HTTP 請求通過 Axios 封裝成一個(gè)模塊這樣做的好處是在請求攔截器里統(tǒng)一添加Authorization頭、統(tǒng)一加時(shí)間戳參數(shù)在響應(yīng)攔截器里統(tǒng)一處理錯(cuò)誤碼。比如后端返回 code 200 直接返回?cái)?shù)據(jù)體業(yè)務(wù)碼 500 彈出全局錯(cuò)誤提示401 清除登錄態(tài)并跳回登錄頁。封裝示例邏輯import axios from axios import { ElMessage } from element-plus import router from /router import { getToken, removeToken } from /utils/auth const service axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) service.interceptors.request.use(config { const token getToken() if (token) { config.headers[Authorization] Bearer ${token} } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 請求失敗) if (res.code 401) { removeToken() router.push(/login) } return Promise.reject(new Error(res.message)) } return res }, error { ElMessage.error(error.response?.data?.message || 網(wǎng)絡(luò)異常) return Promise.reject(error) } )VITE_API_BASE_URL通過 Vite 環(huán)境變量配置開發(fā)環(huán)境用代理轉(zhuǎn)發(fā)到本地后端生產(chǎn)環(huán)境直接指向網(wǎng)關(guān)地址。這里最不能省的是 401 的全局處理邏輯我在實(shí)際開發(fā)中見過太多“Token 過期后頁面卡死無反應(yīng)”的慘案根源基本都在這里。5.4 核心管理頁面實(shí)現(xiàn)思路老人管理頁面是最典型的 CRUD 頁面采用“搜索區(qū) 表格區(qū) 分頁區(qū) 彈窗表單”結(jié)構(gòu)。搜索區(qū)使用el-form的inline屬性支持姓名、護(hù)理等級(jí)、狀態(tài)的組合篩選表格區(qū)展示老人列表關(guān)鍵字段操作列放“詳情”“編輯”“入住/退住”“查看健康檔案”“記賬”這些按鈕。彈窗表單里用el-form的rules做表單校驗(yàn)提交前統(tǒng)一校驗(yàn)再調(diào)用接口。表格分頁參數(shù)和查詢參數(shù)建議用響應(yīng)式對象管理const queryParams reactive({ pageNum: 1, pageSize: 10, keyword: , careLevel: , status: })后端 PageHelper 或手寫 LIMIT 分頁都可以但不要自行拼接 SQL規(guī)范做法是 Mapper 接口傳遞Param索引參數(shù)XML 里用 LIMIT 完成分頁。5.5 ECharts 可視化大屏與統(tǒng)計(jì)數(shù)據(jù)養(yǎng)老院管理系統(tǒng)中管理者需要直觀看到關(guān)鍵運(yùn)營指標(biāo)總床位數(shù)、在住老人數(shù)、入住率、男女比例、護(hù)理等級(jí)分布、月度費(fèi)用收入趨勢、樓層入住情況。這些圖表通過 ECharts Vue3 封裝實(shí)現(xiàn)常放在首頁作為儀表盤。ECharts 圖表組件化很關(guān)鍵避免每個(gè)圖表都獨(dú)立 init 和 setOption可以封裝一個(gè)通用的ChartCard.vue接收optionprop 和heightprop內(nèi)部完成 init、setOption、resize 監(jiān)聽。注意在組件卸載時(shí)調(diào)用dispose釋放實(shí)例否則路由切換后會(huì)出現(xiàn)內(nèi)存泄漏或圖表實(shí)例重復(fù)創(chuàng)建的問題。6. 本地部署啟動(dòng)與數(shù)據(jù)庫初始化6.1 MySQL 環(huán)境安裝與數(shù)據(jù)庫建庫如果你本地還沒有 MySQL第一步是下載對應(yīng)版本的 MySQL 并安裝。安裝完成后打開命令行或 MySQL Workbench創(chuàng)建一個(gè)專用數(shù)據(jù)庫CREATE DATABASE eldercare_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE eldercare_system;注意 mysql 8.0 默認(rèn)字符集已經(jīng)是 utf8mb4但顯式聲明還是更為穩(wěn)妥。項(xiàng)目根目錄下通常附帶sql/init.sql或eldercare_system.sql直接用source命令或者在圖形化工具中執(zhí)行全部腳本即可完成建表。執(zhí)行完初始化腳本后可以執(zhí)行SHOW TABLES;確認(rèn)表都建出來了。同時(shí)建議順便插入一條測試管理員賬號(hào)登錄后先看看頁面能不能正常拉取數(shù)據(jù)。6.2 后端啟動(dòng)與接口自測后端啟動(dòng)很簡單在 IDEA 中直接運(yùn)行主啟動(dòng)類。啟動(dòng)成功后日志中會(huì)看到端口監(jiān)聽比如Tomcat started on port(s): 8080。這時(shí)候用瀏覽器訪問http://localhost:8080/api/...測試接口如果配置了 SpringDoc / Swagger也可以直接訪問 swagger-ui 頁面調(diào)試接口。我用得比較多的方式是用 Apifox 或 Postman 建一個(gè)登錄請求獲取 Token 后帶 Token 請求業(yè)務(wù)接口這樣能快速驗(yàn)證 Token 鑒權(quán)鏈路是否通暢。6.3 前端啟動(dòng)與跨域配置前端要執(zhí)行依賴安裝和啟動(dòng)命令npm install npm run dev如果依賴下載緩慢建議臨時(shí)使用國內(nèi)鏡像源配置。啟動(dòng)成功后默認(rèn)端口一般是 5173瀏覽器會(huì)自動(dòng)打開開發(fā)地址。開發(fā)環(huán)境下前后端跨域是必須處理的典型問題。我這里推薦在 Vite 配置里添加開發(fā)代理用/api前綴標(biāo)識(shí)后端接口server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }配置代理后前端請求的 baseURL 即可設(shè)置為/api這個(gè)階段既避免了跨域問題也方便生產(chǎn)環(huán)境用 Nginx 或網(wǎng)關(guān)統(tǒng)一轉(zhuǎn)發(fā)接口具體地址不在前端代碼里寫死維護(hù)成本更低。6.4 生產(chǎn)環(huán)境構(gòu)建與部署思路開發(fā)調(diào)試完成之后前端構(gòu)建靜態(tài)資源使用npm run build產(chǎn)物輸出在dist目錄。部署時(shí)把dist目錄扔到 Nginx 或者任何靜態(tài)文件服務(wù)器里用 Nginx 同時(shí)承擔(dān)靜態(tài)資源服務(wù)和 API 反向代理server { listen 80; server_name yourdomain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files這一行是單頁應(yīng)用歷史路由模式部署的必備配置如果漏掉用戶刷新頁面就會(huì) 404。這個(gè)坑幾乎每個(gè)前端部署都會(huì)踩一次我特意放在這里提醒。6.5 前后端聯(lián)調(diào)的關(guān)鍵節(jié)點(diǎn)梳理聯(lián)調(diào)階段最容易卡住的是字段不一致問題。聯(lián)調(diào)前先看后端的接口文檔或?qū)嶓w類明確字段名稱、類型、嵌套結(jié)構(gòu)。比如時(shí)間和日期字段在前端是否需要格式化、枚舉狀態(tài)是數(shù)字還是字符串、分頁返回對象的具體結(jié)構(gòu)這些都要在開始寫代碼前對齊。我習(xí)慣的聯(lián)調(diào)步驟是先調(diào)通登錄接口拿到 Token再用 Token 調(diào)通一個(gè)列表查詢接口驗(yàn)證分頁參數(shù)、搜索條件、數(shù)據(jù)格式無誤后再繼續(xù)推進(jìn)剩余模塊。如果一開始就把接口全部寫死到頁面里聯(lián)調(diào)發(fā)現(xiàn)問題時(shí)再逐個(gè)排查費(fèi)時(shí)費(fèi)力。7. 常見問題與排查技巧實(shí)錄7.1 數(shù)據(jù)庫連接失敗的多種可能數(shù)據(jù)庫連不上是項(xiàng)目啟動(dòng)最常見的問題。先看報(bào)錯(cuò)信息如果是Access denied for user說明用戶名或密碼不對或者該用戶沒有對應(yīng)數(shù)據(jù)庫的訪問權(quán)限如果是Communications link failure多半是數(shù)據(jù)庫服務(wù)沒啟動(dòng)或者端口不是默認(rèn)的 3306時(shí)區(qū)報(bào)錯(cuò)則在 JDBC URL 里加serverTimezoneAsia/Shanghai。如果本機(jī)裝了多個(gè)版本的 MySQL用 3306 端口沖突導(dǎo)致服務(wù)起不來這也是我踩過的坑。建議保留一個(gè)服務(wù)實(shí)例或者給不同實(shí)例指定不同的端口避免無意義的排查時(shí)間消耗。7.2 MyBatis 映射文件路徑與綁定異常啟動(dòng)時(shí)報(bào)Invalid bound statement (not found)十有八九是 Mapper 接口和 XML 文件沒有正確對應(yīng)。檢查三個(gè)點(diǎn)application.yml里mybatis.mapper-locations是否掃描到 XML 路徑XML 文件的namespace是否完整等于 Mapper 接口的全限定名XML 中的 statement id 是否與接口方法名一致。這個(gè)報(bào)錯(cuò)幾乎不會(huì)自動(dòng)消失每次排查按這個(gè)順序來就好。另外Maven 項(xiàng)目要注意 XML 文件如果在src/main/java下面修改pom.xml的resources配置讓 XML 文件打進(jìn) classpath否則運(yùn)行打包后的 Jar 會(huì)同樣報(bào)找不到映射。7.3 前端頁面空白或數(shù)據(jù)渲染異常前端頁面空白查看瀏覽器控制臺(tái)最常見的就是路由匹配不到組件也就是No match found for location說明動(dòng)態(tài)路由添加邏輯有問題——刷新后路由丟失。另一個(gè)常見情況是接口 404 或 500前端沒有做錯(cuò)誤攔截提示頁面看起來就白屏了。調(diào)試時(shí)先打開 Network 標(biāo)簽頁看具體請求狀態(tài)碼再逐層排查。字段渲染異常大多是變量名對不上或者后端返回的數(shù)據(jù)結(jié)構(gòu)嵌套層級(jí)深了模板里路徑寫錯(cuò)。在拿到接口真實(shí)響應(yīng)結(jié)構(gòu)后先手動(dòng)把假數(shù)據(jù)替換成真實(shí)數(shù)據(jù)結(jié)構(gòu)再調(diào)試。7.4 跨域請求被攔截問題本地開發(fā)時(shí)瀏覽器報(bào) CORS 錯(cuò)誤如果你已經(jīng)在前端配置了 Vite 代理那大概率是請求路徑?jīng)]走代理而是直接請求了http://localhost:8080這個(gè)完整地址導(dǎo)致沒有命中代理規(guī)則。檢查請求 URL 是否以/api開頭別在 Axios 的 baseURL 里硬編碼端口號(hào)。如果在后端單獨(dú)加了CrossOrigin或全局跨域配置同時(shí)前端也配置了代理兩套機(jī)制疊加反而會(huì)出問題。我的建議是開發(fā)環(huán)境只配前端代理生產(chǎn)環(huán)境用 Nginx 轉(zhuǎn)發(fā)后端盡量不要開全局跨域從源頭避免安全隱患。7.5 JWT Token 失效與登錄狀態(tài)丟失排查Token 失效跳轉(zhuǎn)邏輯沒生效優(yōu)先排查響應(yīng)攔截器的 401 判斷是否寫對。還要檢查后端是否對每個(gè)請求都正確校驗(yàn)了Authorization頭。以下幾點(diǎn)常見錯(cuò)誤前端 Token 存儲(chǔ)的 key 和后端解析時(shí)不一致Token 過期時(shí)間設(shè)置太短導(dǎo)致開發(fā)時(shí)頻繁掉線時(shí)間服務(wù)器時(shí)間不對導(dǎo)致 JWT 的時(shí)間戳校驗(yàn)失敗。開發(fā)階段可以把過期時(shí)間適當(dāng)調(diào)長比如 24 小時(shí)避免頻繁登錄干擾開發(fā)節(jié)奏。8. 擴(kuò)展思路與項(xiàng)目復(fù)盤總結(jié)養(yǎng)老院管理系統(tǒng)的核心價(jià)值不在于代碼有多炫而在于它完整呈現(xiàn)了一個(gè)業(yè)務(wù)管理系統(tǒng)從數(shù)據(jù)庫設(shè)計(jì)、后端接口開發(fā)、前端頁面搭建到部署上線的全鏈路能力。做完這套項(xiàng)目你基本能夠掌握 Java 全棧開發(fā)的常見流程往后再接到任何類似的“某行業(yè)管理系統(tǒng)”需求都能快速遷移。擴(kuò)展方向上這個(gè)項(xiàng)目可以繼續(xù)演進(jìn)引入 Redis 做緩存把熱點(diǎn)數(shù)據(jù)查詢的響應(yīng)時(shí)間降下來接入 WebSocket 實(shí)現(xiàn)老人異常生命體征的實(shí)時(shí)告警增加移動(dòng)端 H5 或微信小程序給家屬使用引入工作流引擎處理請假、用章、審批等內(nèi)部流程。這些后續(xù)迭代都以當(dāng)前的基礎(chǔ)版本為底座架構(gòu)設(shè)計(jì)時(shí)留的余地越大后期演進(jìn)就越從容。我個(gè)人實(shí)際操作這個(gè)項(xiàng)目最大的體會(huì)是不要小看“管理系統(tǒng)”這四個(gè)字背后的復(fù)雜度。真正的難點(diǎn)從來不是 CRUD 怎么寫而是業(yè)務(wù)規(guī)則的梳理、表結(jié)構(gòu)的反復(fù)推敲、異常場景的補(bǔ)全。把這些基本功練扎實(shí)了框架和組件庫再怎么換代你都能穩(wěn)穩(wěn)接住。最后再說個(gè)實(shí)用技巧寫代碼前多花一小時(shí)把數(shù)據(jù)庫表和接口列表理清楚比寫代碼中途返工改表省下的時(shí)間要多得多。這個(gè)經(jīng)驗(yàn)適用于任何一次項(xiàng)目開發(fā)推薦你先從這次的養(yǎng)老院管理系統(tǒng)開始體會(huì)。