院掛號(hào)系統(tǒng)全棧開發(fā)實(shí)踐)
做醫(yī)院掛號(hào)系統(tǒng)這事兒看著簡(jiǎn)單真做起來(lái)其實(shí)挺折騰人的??剖?、醫(yī)生、排班、號(hào)源、退號(hào)、就診狀態(tài)業(yè)務(wù)邏輯一環(huán)套一環(huán)前端界面要友好后端并發(fā)和事務(wù)也得拿捏到位。我這兩年一直在搞Java Web相關(guān)的全棧項(xiàng)目SpringBoot2Vue3MyBatis-PlusMySQL8.0這套組合也打磨得比較熟了。這次分享一個(gè)我實(shí)際搭過(guò)、能跑通的醫(yī)院掛號(hào)就診系統(tǒng)從技術(shù)選型思路到數(shù)據(jù)庫(kù)設(shè)計(jì)再到前后端聯(lián)調(diào)和部署把整個(gè)從零到一的過(guò)程攤開聊。這套系統(tǒng)的完整源碼和文檔我整理得挺清楚適合正在做課設(shè)、畢業(yè)設(shè)計(jì)或者想入門全棧開發(fā)的Java同學(xué)拿去參考也適合團(tuán)隊(duì)里想快速搭建醫(yī)療類業(yè)務(wù)原型的人抄作業(yè)。1. 技術(shù)棧選型與整體設(shè)計(jì)思路很多初學(xué)者一上來(lái)就糾結(jié)選什么框架、用什么版本其實(shí)對(duì)于一個(gè)業(yè)務(wù)系統(tǒng)來(lái)說(shuō)技術(shù)選型的核心邏輯就一句話穩(wěn)住開發(fā)效率同時(shí)兼顧后期維護(hù)。這套系統(tǒng)后端用了SpringBoot2前端用了Vue3持久層用MyBatis-Plus數(shù)據(jù)庫(kù)是MySQL8.0這套搭配不是隨便拼出來(lái)的。1.1 前后端分離架構(gòu)的核心優(yōu)勢(shì)前后端分離已經(jīng)被驗(yàn)證過(guò)很多年了尤其對(duì)于醫(yī)院掛號(hào)這種需要同時(shí)面對(duì)患者端和管理端的系統(tǒng)天然適合拆開做。前端跑Vue3單頁(yè)應(yīng)用負(fù)責(zé)頁(yè)面渲染和交互后端只做RESTful API通過(guò)JSON交換數(shù)據(jù)。我在這套系統(tǒng)里把前端拆成了兩個(gè)入口患者端和管理端?;颊叨岁P(guān)注掛號(hào)和就診流程界面不用復(fù)雜信息清晰最重要管理端涉及醫(yī)生排班、科室維護(hù)、號(hào)源統(tǒng)計(jì)組件密集、交互狀態(tài)多。兩個(gè)入口共用同一套后端API只是登錄角色不同權(quán)限控制落在后端前端做路由守衛(wèi)配合按鈕級(jí)校驗(yàn)。前端用到Vue3最新語(yǔ)法和生態(tài)組件包括Vite構(gòu)建工具、Vue Router、Pinia狀態(tài)管理。Vite在開發(fā)環(huán)境啟動(dòng)速度非??旄耐甏a瀏覽器熱更新基本是瞬時(shí)生效比起老的Webpack配置省心太多。Pinia對(duì)比Vuex代碼量少了很多配合Composition API寫起來(lái)非常順手。1.2 SpringBoot2MyBatis-Plus解決什么問(wèn)題選SpringBoot2不是因?yàn)榘姹九f而是因?yàn)樗鷳B(tài)最成熟、資料最多絕大多數(shù)的坑都已經(jīng)有人踩過(guò)并公開了解決方案。SpringBoot2的自動(dòng)配置機(jī)制把這套系統(tǒng)里大量的樣板代碼都省掉了我只需要關(guān)注業(yè)務(wù)本身DataSource、事務(wù)管理、Jackson序列化這些交給框架處理。持久層選MyBatis-Plus是這套系統(tǒng)開發(fā)效率能拉滿的關(guān)鍵。它就是MyBatis的增強(qiáng)工具內(nèi)置了通用的增刪改查方法單表操作完全不用手寫SQL。它的條件構(gòu)造器LambdaQueryWrapper寫起來(lái)非常直觀代碼可讀性也高。比如查詢某個(gè)醫(yī)生某天的排班直接鏈?zhǔn)秸{(diào)用就行。配合分頁(yè)插件寫一個(gè)分頁(yè)接口只需要幾行代碼。通用CRUD服務(wù)這塊我基于MyBatis-Plus的IService接口封裝了一個(gè)BaseService把當(dāng)前登錄用戶ID、創(chuàng)建時(shí)間、更新時(shí)間這些公共字段統(tǒng)一處理每個(gè)業(yè)務(wù)Service只需要繼承它。這樣新增表的時(shí)候Service層幾乎沒什么重復(fù)勞動(dòng)直接復(fù)用公共邏輯。1.3 MySQL8.0為什么是數(shù)據(jù)庫(kù)首選MySQL8.0相比5.7版本窗口函數(shù)、公共表表達(dá)式、默認(rèn)字符集utf8mb4這些能力都很成熟了。醫(yī)院掛號(hào)系統(tǒng)涉及到的排班統(tǒng)計(jì)、掛號(hào)趨勢(shì)分析、號(hào)源消費(fèi)明細(xì)這些查詢?cè)贛ySQL8.0里寫起來(lái)更順手。MySQL8.0對(duì)JSON類型的支持也更好。比如醫(yī)生排班表里可以用JSON字段存某個(gè)時(shí)間段已鎖定的號(hào)位編號(hào)查詢和更新都靈活。用傳統(tǒng)方案要拆關(guān)聯(lián)表用JSON字段則能大幅減少表數(shù)量開發(fā)速度能提上來(lái)。這套系統(tǒng)對(duì)數(shù)據(jù)庫(kù)的依賴很重事務(wù)處理是核心訴求。掛號(hào)操作涉及訂單表和號(hào)源表兩個(gè)核心表的更新必須放在同一個(gè)事務(wù)里處理任何一個(gè)環(huán)節(jié)失敗都得全部回滾。MySQL8.0的InnoDB引擎在事務(wù)隔離級(jí)別和行鎖機(jī)制上表現(xiàn)穩(wěn)定默認(rèn)的REPEATABLE READ級(jí)別完全滿足掛號(hào)類業(yè)務(wù)需求。2. 系統(tǒng)功能模塊設(shè)計(jì)與核心業(yè)務(wù)邏輯醫(yī)院掛號(hào)就診系統(tǒng)的功能模塊比很多人想象的要多。除了最基礎(chǔ)的登錄注冊(cè)和科室醫(yī)生瀏覽還要管好排班、掛號(hào)、退號(hào)、叫號(hào)、就診記錄和后臺(tái)運(yùn)營(yíng)數(shù)據(jù)。模塊劃分得清楚開發(fā)才能不打架。2.1 雙端功能模塊劃分患者端的功能我按就診動(dòng)線來(lái)拆登錄注冊(cè)、首頁(yè)科室導(dǎo)航、醫(yī)生列表和詳情、排班日歷、在線掛號(hào)、訂單管理、退號(hào)申請(qǐng)、就診記錄查詢、個(gè)人信息維護(hù)。管理端則是圍繞運(yùn)營(yíng)和配置來(lái)拆后臺(tái)首頁(yè)的統(tǒng)計(jì)看板科室管理醫(yī)生賬號(hào)管理排班管理號(hào)源查看與鎖定掛號(hào)訂單處理就診狀態(tài)更新待就診、已就診、已過(guò)期退號(hào)審批。這里有個(gè)細(xì)節(jié)值得單獨(dú)說(shuō)患者端和管理端的UI組件是分開的但API層是共用的。前后端聯(lián)調(diào)時(shí)同一套接口患者端走患者身份管理端走管理員身份后端通過(guò)登錄用戶的角色做數(shù)據(jù)權(quán)限隔離。比如“查詢排班列表”這個(gè)接口患者端只能查狀態(tài)為“開放”的排班管理端可以查全部排班包括已關(guān)閉和已過(guò)號(hào)的。2.2 排班和號(hào)源狀態(tài)機(jī)設(shè)計(jì)醫(yī)院掛號(hào)系統(tǒng)最核心的業(yè)務(wù)狀態(tài)其實(shí)是排班和號(hào)源這個(gè)狀態(tài)機(jī)設(shè)計(jì)好后面所有功能都會(huì)順。每個(gè)醫(yī)生每天的排班記錄包含上午時(shí)段、下午時(shí)段每個(gè)時(shí)段又由號(hào)源表來(lái)承載具體的號(hào)位數(shù)量。我設(shè)計(jì)的排班狀態(tài)有四種草稿、開放、鎖定、關(guān)閉。草稿是管理員配置好還沒發(fā)布開放是患者可以正常掛號(hào)鎖定表示號(hào)已滿或者臨時(shí)停診關(guān)閉是當(dāng)天就診結(jié)束或者取消。號(hào)源狀態(tài)則更細(xì)每一位號(hào)都有獨(dú)立狀態(tài)空閑、已鎖定、已掛號(hào)、已就診、已過(guò)號(hào)、已退號(hào)。這六個(gè)狀態(tài)的流轉(zhuǎn)關(guān)系是這樣的空閑到已鎖定患者在提交掛號(hào)訂單時(shí)預(yù)占號(hào)位支付超時(shí)或者主動(dòng)取消則回退到空閑。已鎖定到已掛號(hào)支付成功或后臺(tái)確認(rèn)后訂單正式生效。已掛號(hào)到已就診醫(yī)生端標(biāo)記就診完成。已掛號(hào)到已退號(hào)患者退號(hào)成功。已掛號(hào)到已過(guò)號(hào)到了就診時(shí)段后用戶沒來(lái)系統(tǒng)自動(dòng)標(biāo)記過(guò)期。已退號(hào)的號(hào)位回退到空閑管理員可以重新開放。2.3 基于JWT的登錄認(rèn)證與角色權(quán)限這套系統(tǒng)的登錄認(rèn)證我用的是JWT方案。用戶登錄成功后后端簽發(fā)一個(gè)帶過(guò)期時(shí)間的Token前端存在localStorage里每次請(qǐng)求通過(guò)HTTP請(qǐng)求頭帶過(guò)去后端通過(guò)Spring Security過(guò)濾器鏈校驗(yàn)。JWT的有效期分為兩個(gè)維度Token本身的過(guò)期時(shí)間12小時(shí)Redis里再做一層會(huì)話管理。如果用戶在常用設(shè)備上操作頻繁刷新Token可以延續(xù)登錄態(tài)如果用戶改密碼或管理員強(qiáng)制下線直接刪Redis的會(huì)話鍵就行Token本身失效。權(quán)限控制上我針對(duì)管理端接口和患者端接口分別做了注解校驗(yàn)。比如管理端的排班新增、號(hào)源釋放這類接口PreAuthorize(hasRole(ADMIN))會(huì)攔截非管理員請(qǐng)求患者的個(gè)人信息接口則必須校驗(yàn)當(dāng)前登錄用戶ID和資源歸屬ID一致才行。3. 數(shù)據(jù)庫(kù)設(shè)計(jì)與核心表結(jié)構(gòu)拆解數(shù)據(jù)庫(kù)設(shè)計(jì)是整個(gè)系統(tǒng)的地基。表結(jié)構(gòu)設(shè)計(jì)得合理開發(fā)時(shí)少走彎路上線后性能也不會(huì)有太多幺蛾子。我落地這套系統(tǒng)時(shí)核心表一共8張配合4張字典和關(guān)聯(lián)表完整覆蓋掛號(hào)就診業(yè)務(wù)鏈路。3.1 核心業(yè)務(wù)表及其字段設(shè)計(jì)要點(diǎn)用戶表用的是一張通用設(shè)計(jì)既承載患者也承載醫(yī)生和超級(jí)管理員通過(guò)角色字段區(qū)分。這張表的字段我都做得比較克制用戶名、密碼、手機(jī)號(hào)、姓名、頭像、角色、狀態(tài)沒有過(guò)度設(shè)計(jì)。密碼存儲(chǔ)用的是BCrypt加密不可逆安全Job能抗住字典攻擊??剖冶肀容^簡(jiǎn)單科室名、科室編號(hào)、科室簡(jiǎn)介、排序、狀態(tài)。醫(yī)生表掛在科室表下面一對(duì)一關(guān)聯(lián)用戶表同時(shí)存職稱和擅長(zhǎng)領(lǐng)域。排班表是整個(gè)系統(tǒng)的核心樞紐字段我重點(diǎn)說(shuō)明幾個(gè)doctor_id關(guān)聯(lián)醫(yī)生。schedule_date排班日期。period_type時(shí)段類型上午還是下午。total_count該時(shí)段總的號(hào)源數(shù)量。remain_count剩余可掛數(shù)量。status排班狀態(tài)。號(hào)源表更細(xì)每位號(hào)位一行記錄包含號(hào)位序號(hào)、對(duì)應(yīng)排班ID、就診人ID、狀態(tài)、掛號(hào)訂單號(hào)。這樣做的好處是號(hào)狀態(tài)可以獨(dú)立追蹤也能精準(zhǔn)統(tǒng)計(jì)每個(gè)時(shí)段的號(hào)源消耗情況。掛號(hào)訂單表、退號(hào)申請(qǐng)表、就診記錄表則是業(yè)務(wù)流水側(cè)的三張表。訂單表記錄了掛號(hào)行為本身退號(hào)表記錄了退號(hào)的前后狀態(tài)和原因就診記錄表承載醫(yī)生的診斷結(jié)果、用藥建議和病歷描述。3.2 索引設(shè)計(jì)、唯一約束與關(guān)鍵SQL數(shù)據(jù)庫(kù)表建好了索引和約束必須跟上否則數(shù)據(jù)量一大查詢性能會(huì)肉眼可見地變差。排班表上我建了聯(lián)合索引(doctor_id, schedule_date, period_type)這是排班查詢最核心的檢索條件加上唯一約束可以防止同一醫(yī)生同一天同一時(shí)段重復(fù)配置排班。號(hào)源表上的聯(lián)合索引(schedule_id, status)用來(lái)快速統(tǒng)計(jì)某排班下各狀態(tài)號(hào)位的數(shù)量掛號(hào)時(shí)也要走這個(gè)索引去鎖定空閑號(hào)位。MySQL8.0的窗口函數(shù)我用在了后臺(tái)統(tǒng)計(jì)看板里。比如統(tǒng)計(jì)最近7天每天掛號(hào)量趨勢(shì)一條SQL就能搞定SELECT DATE(create_time) AS create_day, COUNT(*) AS total_count, SUM(CASE WHEN order_status 1 THEN 1 ELSE 0 END) AS paid_count FROM t_hospital_order WHERE create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(create_time)掛號(hào)訂單金額統(tǒng)計(jì)、時(shí)段號(hào)源消耗率排名這類報(bào)表也都能靠著窗口函數(shù)在SQL層面完成完全沒有必要在Java代碼里二次聚合。3.3 事務(wù)邊界與并發(fā)控制實(shí)操并發(fā)掛號(hào)的場(chǎng)景是這套系統(tǒng)的硬骨頭。同一時(shí)刻可能有多個(gè)用戶搶同一個(gè)醫(yī)生同一個(gè)時(shí)段的號(hào)如果不做并發(fā)控制就會(huì)出現(xiàn)超賣問(wèn)題也就是發(fā)出的號(hào)位比實(shí)際多。我用的方案是先樂(lè)觀鎖再事務(wù)。具體來(lái)說(shuō)在排班表加上樂(lè)觀鎖版本號(hào)字段。更新剩余號(hào)數(shù)時(shí)WHERE條件里帶上version 當(dāng)前版本號(hào)。更新成功說(shuō)明拿號(hào)成功更新失敗說(shuō)明號(hào)被搶走了提示用戶號(hào)源緊張。查詢剩余號(hào)數(shù)和生成訂單這兩個(gè)操作放在同一個(gè)事務(wù)里用Transactional注解包裹。事務(wù)里先查號(hào)源狀態(tài)判斷是否空閑再通過(guò)UPDATE語(yǔ)句做實(shí)際的號(hào)位狀態(tài)流轉(zhuǎn)這個(gè)更新自帶行鎖保證同一號(hào)位的并發(fā)操作是串行化的。這套思路下來(lái)即使100個(gè)用戶同時(shí)搶同一個(gè)號(hào)位最終也只有一個(gè)人能成功剩下的99個(gè)人全部拿到樂(lè)觀鎖失敗的提示。4. 后端核心實(shí)現(xiàn)與前端聯(lián)調(diào)實(shí)錄前后端都能跑通、數(shù)據(jù)能串起來(lái)才算真正“做出來(lái)”。這一步最容易出幺蛾子跨域、Token失效、字段格式不統(tǒng)一哪個(gè)環(huán)節(jié)不順都能卡你半天。我把核心實(shí)現(xiàn)的套路和一些實(shí)戰(zhàn)中踩過(guò)的坑整理在下面。4.1 SpringBoot2后端關(guān)鍵代碼落地后端項(xiàng)目結(jié)構(gòu)我建議按照功能模塊分包而不是按技術(shù)層次分包。也就是說(shuō)把醫(yī)生相關(guān)的Controller、Service、Mapper放在同一個(gè)包下而不是所有Controller堆在一個(gè)包、所有Mapper堆在另一個(gè)包。功能分包在多人協(xié)作和后期維護(hù)時(shí)找代碼的成本低很多。登錄接口我推薦引入Sa-Token而不是直接裸寫JWT它的代碼量小文檔也接地氣支持登錄認(rèn)證、權(quán)限認(rèn)證、踢人下線、賬號(hào)封禁這些Spring Security要寫一大坨才能搞定的事?;颊叨撕凸芾矶说牡卿浗涌趶?fù)用同一個(gè)認(rèn)證邏輯登錄成功后在Sa-Token會(huì)話里打上不同的角色標(biāo)識(shí)。MyBatis-Plus的分頁(yè)實(shí)現(xiàn)是配合PaginationInnerInterceptor來(lái)做的。一旦配置好Controller里只需要返回Page類型框架自動(dòng)攔截SQL拼接分頁(yè)參數(shù)。這里有個(gè)特別容易踩的坑分頁(yè)插件必須在MybatisPlusConfig配置類里顯式注冊(cè)否則分頁(yè)查詢返回的total永遠(yuǎn)是0。4.2 Vue3前端請(qǐng)求封裝與動(dòng)態(tài)路由實(shí)現(xiàn)Vue3前端這邊我把Axios請(qǐng)求封裝成了一個(gè)獨(dú)立模塊統(tǒng)一處理BaseURL、請(qǐng)求攔截、響應(yīng)攔截和錯(cuò)誤碼。請(qǐng)求攔截器里自動(dòng)從Pinia Store取Token沒有Token就跳轉(zhuǎn)登錄頁(yè)。響應(yīng)攔截器里統(tǒng)一處理后端返回的code字段非200狀態(tài)碼直接彈出錯(cuò)誤提示不用在每個(gè)頁(yè)面重復(fù)寫錯(cuò)誤處理邏輯。動(dòng)態(tài)路由是這套系統(tǒng)前端的亮點(diǎn)。管理端的路由菜單不是死寫在路由配置文件里的而是登錄后根據(jù)用戶角色動(dòng)態(tài)生成。后端登錄接口返回PermsList前端用addRoute方法動(dòng)態(tài)追加對(duì)應(yīng)用戶的路由表未授權(quán)的頁(yè)面路徑直接404前端路由層面就能攔截一大半越權(quán)訪問(wèn)。表格分頁(yè)、表單彈窗、日歷排班這些高頻組件我先抽成了公共組件患者端和管理端共用。比如排班日歷組件傳入醫(yī)生ID和月份組件內(nèi)部請(qǐng)求排班接口用不同顏色標(biāo)識(shí)可掛號(hào)和已掛滿的狀態(tài)。4.3 前后端聯(lián)調(diào)經(jīng)驗(yàn)與常見報(bào)錯(cuò)處理聯(lián)調(diào)階段最常遇到的坑是跨域和JSON字段命名風(fēng)格不一致??缬騿?wèn)題后端一個(gè)CrossOrigin或者全局CorsFilter就能解決但要注意把允許的請(qǐng)求頭和方法配置完整。字段命名問(wèn)題上我統(tǒng)一在后端JSON序列化配置里開啟了駝峰轉(zhuǎn)下劃線數(shù)據(jù)庫(kù)user_name對(duì)應(yīng)前端userName前端不需要做任何額外處理。另一個(gè)高頻坑是Vue3組合式API下頁(yè)面組件加載后在onMounted鉤子里請(qǐng)求接口但此時(shí)Pinia可能還沒有完成初始化。解決辦法是請(qǐng)求動(dòng)作放到async函數(shù)里await store初始化完成再去請(qǐng)求。用起來(lái)效果很穩(wěn)定。報(bào)錯(cuò)方面403權(quán)限不足多半是后端接口沒匹配上PreAuthorize規(guī)則401是Token過(guò)期或缺失排查時(shí)先用瀏覽器開發(fā)者工具看請(qǐng)求頭里有沒有Token再檢查后端攔截器有沒有放行OPTIONS預(yù)檢請(qǐng)求。這一步不處理好前端跨域請(qǐng)求會(huì)直接卡在瀏覽器端。5. 運(yùn)行環(huán)境部署與文檔使用指南系統(tǒng)能跑起來(lái)只是第一步。很多人在環(huán)境配置和部署環(huán)節(jié)消耗的時(shí)間甚至比寫代碼還多。我把這套SpringBoot2Vue3的部署流程整理成了一套標(biāo)準(zhǔn)作業(yè)流程照著做基本不會(huì)有大問(wèn)題。5.1 Docker一鍵啟動(dòng)MySQL8.0環(huán)境對(duì)于本地開發(fā)我非常推薦用Docker跑MySQL8.0而不是在Windows或Mac上直接安裝數(shù)據(jù)庫(kù)服務(wù)。Docker方案有兩個(gè)優(yōu)勢(shì)環(huán)境隔離隨時(shí)刪掉重建不污染宿主機(jī)系統(tǒng)版本切換方便今天用8.0明天想試5.7換個(gè)鏡像參數(shù)就行。常用啟動(dòng)命令我在項(xiàng)目文檔里寫得很細(xì)。掛載宿主機(jī)目錄解決數(shù)據(jù)持久化問(wèn)題指定MySQL8.0的數(shù)據(jù)目錄權(quán)限初始化SQL腳本通過(guò)docker-entrypoint-initdb.d目錄自動(dòng)執(zhí)行。賬號(hào)號(hào)密碼、數(shù)據(jù)庫(kù)名這些參數(shù)放在啟動(dòng)命令里統(tǒng)一管理團(tuán)隊(duì)里其他人拉取倉(cāng)庫(kù)后一條命令就能把數(shù)據(jù)庫(kù)起起來(lái)。本地開發(fā)時(shí)連接MySQL8.0最容易遇到的坑是認(rèn)證插件問(wèn)題。MySQL8.0默認(rèn)的caching_sha2_password認(rèn)證插件某些老版本的驅(qū)動(dòng)不支持連不上數(shù)據(jù)庫(kù)。解法是在啟動(dòng)命令里顯式指定認(rèn)證插件為mysql_native_password如果你是直接用Docker鏡像而不是自定義配置也可以在Navicat或命令行里執(zhí)行ALTER USER語(yǔ)句修改認(rèn)證方式。5.2 項(xiàng)目本地啟動(dòng)完整流程這套系統(tǒng)的后端是基于Maven構(gòu)建的本地啟動(dòng)鏈路非常清晰。第一步確認(rèn)JDK版本是8或11Maven是3.6以上然后拉取項(xiàng)目代碼。第二步啟動(dòng)MySQL并用初始化腳本建庫(kù)建表。第三步修改application.yml中的數(shù)據(jù)庫(kù)連接信息。第四步運(yùn)行主類啟動(dòng)后端看到日志輸出端口監(jiān)聽成功就說(shuō)明后端起來(lái)了。前端部分按照package.json里的scripts命令操作先npm install安裝依賴安裝失敗時(shí)優(yōu)先檢查Node.js版本和鏡像源配置。dev命令啟動(dòng)Vite開發(fā)服務(wù)器Vite會(huì)默認(rèn)監(jiān)聽5173端口。開發(fā)模式下前端代理配置指向后端8080端口這樣前端請(qǐng)求不會(huì)產(chǎn)生跨域問(wèn)題。需要注意一個(gè)細(xì)節(jié)如果后端啟動(dòng)時(shí)端口被占用優(yōu)先檢查是不是上次啟動(dòng)的后端進(jìn)程沒有退出。Linux下用lsof -i:8080查找進(jìn)程PID之后結(jié)束掉避免兩個(gè)進(jìn)程同時(shí)監(jiān)聽同一端口導(dǎo)致服務(wù)起不來(lái)。5.3 項(xiàng)目文檔里應(yīng)該包含什么這套系統(tǒng)的完整文檔我在交付時(shí)做了兩個(gè)版本一個(gè)給開發(fā)者看的技術(shù)文檔一個(gè)給使用方看的用戶手冊(cè)。技術(shù)文檔部分包含系統(tǒng)架構(gòu)圖、數(shù)據(jù)庫(kù)ER圖、接口文檔、部署手冊(cè)和二次開發(fā)指南。數(shù)據(jù)庫(kù)ER圖我直接用工具從建表SQL自動(dòng)生成標(biāo)注核心字段說(shuō)明后端同學(xué)拿到就能快速理解表關(guān)系。接口文檔用Swagger自動(dòng)生成在線就能調(diào)試還配了一份Postman導(dǎo)出的接口集合。用戶手冊(cè)則是給直接使用系統(tǒng)的人看的包含系統(tǒng)功能清單、操作流程示例和運(yùn)維FAQ。比如管理員第一次登錄后臺(tái)怎么改默認(rèn)密碼、患者怎么在線退號(hào)、醫(yī)生排班滿了怎么臨時(shí)加號(hào)這些高頻操作都配了圖文步驟。6. 常見問(wèn)題現(xiàn)場(chǎng)與排查思路實(shí)錄開發(fā)這套醫(yī)院掛號(hào)系統(tǒng)的過(guò)程中我翻了幾個(gè)車的現(xiàn)場(chǎng)這里把最典型的幾類問(wèn)題和排查邏輯記錄下來(lái)信息密度高直接抄作業(yè)就行。6.1 MySQL8.0時(shí)區(qū)與連接報(bào)錯(cuò)問(wèn)題系統(tǒng)在部署到云服務(wù)器上時(shí)數(shù)據(jù)庫(kù)連接一直報(bào)錯(cuò)Server returns invalid timezone. MySQL連接串里的serverTimezone參數(shù)配置錯(cuò)了。MySQL8.0默認(rèn)的時(shí)區(qū)可能和本地環(huán)境不一致在JDBC連接串后面加上serverTimezoneAsia/Shanghai就能解決。還有一次是密碼包含特殊字符導(dǎo)致連接串解析失敗。密碼里有符號(hào)直接拼在URL里被誤認(rèn)為是參數(shù)分隔符。解決辦法是連接串里對(duì)特殊字符做URL編碼或者干脆把密碼改成一個(gè)不含特殊字符的強(qiáng)密碼。這種問(wèn)題排查起來(lái)花時(shí)間但原因其實(shí)特別簡(jiǎn)單。6.2 MyBatis-Plus分頁(yè)失效和邏輯刪除沖突分頁(yè)插件配置過(guò)程中最容易漏掉的就是Interceptor的注冊(cè)。很多入門教程只寫了引入依賴沒提配置PaginationInnerInterceptor結(jié)果分頁(yè)查詢出來(lái)的total始終是0數(shù)據(jù)也只返回第一頁(yè)的記錄。邏輯刪除功能也遇到過(guò)坑。如果在一個(gè)表上同時(shí)配了邏輯刪除和唯一約束邏輯刪除的記錄仍然占用唯一索引再次插入相同數(shù)據(jù)時(shí)會(huì)違反唯一約束。項(xiàng)目的號(hào)源表就遇到了這種情況解決方法是建立復(fù)合唯一索引把邏輯刪除字段也納入索引。6.3 前后端聯(lián)調(diào)跨域與會(huì)話失效前端在瀏覽器調(diào)試時(shí)模擬患者端和管理端同時(shí)登錄Token會(huì)把后者的Token覆蓋導(dǎo)致A端頁(yè)面的請(qǐng)求全部401。原因是兩個(gè)前端入口共用了同一個(gè)localStorage鍵名。解決方法是把Token按角色分鍵存儲(chǔ)或者后端校驗(yàn)時(shí)直接讀取最新Token。項(xiàng)目文檔里我推薦按角色分鍵存儲(chǔ)簡(jiǎn)單直接。最后分享一個(gè)我在實(shí)際開發(fā)中養(yǎng)成的習(xí)慣每次改完數(shù)據(jù)庫(kù)表結(jié)構(gòu)第一時(shí)間更新ER圖和接口文檔。前后端聯(lián)調(diào)最怕的就是“后端改了字段名前端不知道”這種情況處理一次就能學(xué)會(huì)教訓(xùn)。這套系統(tǒng)的源碼和文檔我已經(jīng)打包整理好了拿到手以后建議先跑通初始化腳本再?gòu)那岸说卿涰?yè)開始走一遍完整流程大概半天時(shí)間就能對(duì)整個(gè)系統(tǒng)的數(shù)據(jù)流轉(zhuǎn)有清晰的認(rèn)知。后面如果你們要在這個(gè)基礎(chǔ)上做二期開發(fā)比如接入在線支付、增加消息推送或者對(duì)接公眾號(hào)預(yù)約核心模塊的擴(kuò)展點(diǎn)我都已經(jīng)留好了。