開發(fā)實(shí)戰(zhàn))
java-ssm366疫苗接種管理系統(tǒng)vue-springboot說到這類項(xiàng)目我腦子里第一個蹦出來的關(guān)鍵詞就是java、ssm、vue、springboot。凡是做過JavaWeb畢業(yè)設(shè)計(jì)或CRUD實(shí)戰(zhàn)項(xiàng)目的人看到這個標(biāo)題基本都能猜到七七八八——這是一個典型的前后端分離項(xiàng)目后端可以用SSM框架組合Spring SpringMVC MyBatis也可以用Spring Boot MyBatis Plus快速搭建前端走Vue生態(tài)整體做一套疫苗接種管理系統(tǒng)。這個項(xiàng)目能解決什么問題說白了就是把線下疫苗接種的登記、庫存、預(yù)約、記錄這些繁瑣流程搬到線上。社區(qū)衛(wèi)生院、疾控中心的工作人員不用再拿Excel記賬居民也不用排隊(duì)填單子數(shù)據(jù)一查就能出統(tǒng)計(jì)報(bào)表。如果你是Java初學(xué)者、正在準(zhǔn)備畢業(yè)設(shè)計(jì)、或者想搞一個完整的前后端分離實(shí)戰(zhàn)項(xiàng)目練手這套系統(tǒng)的思路和代碼結(jié)構(gòu)都挺值得拆一拆的。我也做過不少同類系統(tǒng)這篇文章就把我實(shí)際開發(fā)中踩過的坑、琢磨過的設(shè)計(jì)決策盡量原原本本講給你聽。1. 項(xiàng)目全景這套系統(tǒng)到底在解決什么問題1.1 從需求側(cè)看疫苗接種管理的核心痛點(diǎn)很多同學(xué)拿到“疫苗接種管理系統(tǒng)”這個題目第一反應(yīng)是這不就是增刪改查嗎疫苗表加個增刪改查用戶表加個增刪改查不就完事了如果你真的這么想那做出來的東西大概率只是一個“表格管理系統(tǒng)”而不是業(yè)務(wù)系統(tǒng)。我自己的體會是疫苗接種管理至少有四個繞不開的核心痛點(diǎn)第一是疫苗批次與庫存的強(qiáng)關(guān)聯(lián)。疫苗不是普通商品它有批號、有效期、生產(chǎn)廠家一個批次的疫苗可能分散到不同接種點(diǎn)接種點(diǎn)之間還可能調(diào)撥。如果庫存表只記錄“還剩多少支”而不記錄批次一旦某個批次出問題要召回你根本查不到哪些人打了這批疫苗。第二是預(yù)約與庫存的聯(lián)動。居民線上預(yù)約接種本質(zhì)上就是在鎖定某一天的疫苗庫存。如果預(yù)約功能只管預(yù)約不管庫存扣減那就有可能出現(xiàn)在線約了20個人、現(xiàn)場只剩5支疫苗的尷尬場面。這個聯(lián)動關(guān)系處理不好系統(tǒng)上線第一天就會被投訴淹沒。第三是接種記錄的可追溯性。每一針疫苗打給誰、哪個醫(yī)生打的、哪個批次的苗、接種時(shí)間是什么這些數(shù)據(jù)不僅是業(yè)務(wù)數(shù)據(jù)還是衛(wèi)生監(jiān)管的數(shù)據(jù)基礎(chǔ)。系統(tǒng)設(shè)計(jì)時(shí)如果不考慮“每一條接種記錄都能回溯到疫苗批次和接種人員”后面補(bǔ)起來會非常痛苦。第四是多角色權(quán)限隔離。系統(tǒng)至少有管理員、接種醫(yī)生、普通用戶三種角色。管理員管庫存和統(tǒng)計(jì)醫(yī)生管登記和錄入用戶管預(yù)約和查詢?nèi)叩臄?shù)據(jù)邊界必須清晰。很多項(xiàng)目在這塊偷懶結(jié)果就是“用戶能看庫存數(shù)據(jù)”、“醫(yī)生能刪用戶賬號”這種安全事故。理解了這四點(diǎn)你再看這個項(xiàng)目的標(biāo)題就能明白它不是一個簡單的CRUD練習(xí)而是一個需要考慮業(yè)務(wù)約束、角色權(quán)限、數(shù)據(jù)追溯的中小型管理系統(tǒng)。1.2 技術(shù)選型背后的取舍SSM還是SpringBoot MyBatis Plus標(biāo)題里同時(shí)出現(xiàn)了ssm、springboot這其實(shí)是很多同學(xué)糾結(jié)的地方。我直接說結(jié)論如果這個項(xiàng)目是你自己從零寫選Spring Boot MyBatis Plus會舒服很多如果課程要求必須用SSM那也別慌思路完全一樣只是配置方式更繁瑣。為什么我更推薦Spring Boot原因有三第一Spring Boot把Spring、SpringMVC的配置都自動裝配掉了。SSM時(shí)代你要自己配web.xml、spring-mvc.xml、mybatis-config.xml任何一個XML寫錯項(xiàng)目啟動就是一片紅。Spring Boot里一個application.yml就能搞定大部分事情省下來的時(shí)間足夠你把業(yè)務(wù)邏輯想清楚。第二MyBatis Plus對單表CRUD的殺傷力太大了。疫苗接種管理這類系統(tǒng)80%的數(shù)據(jù)庫操作都是單表增刪改查加條件查詢。用MyBatis Plus的BaseMapper你連SQL都不用寫直接selectList、insert、updateById就能完成基本操作。只有查報(bào)表、做復(fù)雜關(guān)聯(lián)查詢的時(shí)候才需要手寫XML。這種開發(fā)體驗(yàn)對新手極其友好。第三項(xiàng)目部署更省心。SSM打war包要配TomcatSpring Boot直接mvn package打一個可執(zhí)行jar服務(wù)器上java -jar就完了。我自己第一次部署SSM項(xiàng)目被ClassNotFound折磨了一整天換成Spring Boot之后部署時(shí)間壓縮到十分鐘以內(nèi)。那SSM還有沒有存在的價(jià)值有。如果你是計(jì)算機(jī)專業(yè)學(xué)生SSM是理解Spring生態(tài)底層原理的一條好路徑——手動配過XML的人才能真正理解Spring Boot的“約定優(yōu)于配置”到底幫你省了什么。但從“快速交付一個能用的系統(tǒng)”角度Spring Boot明顯是更理性的選擇。至于前端標(biāo)題里的vue是主流解法。我建議用Vue 2 Element UI的經(jīng)典組合原因很簡單Element UI的中文文檔全、組件全、案例多表格、表單、彈窗、分頁這些后臺管理高頻組件開箱即用對新手非常友好。Vue 3 Element Plus也完全可以但如果你參考的現(xiàn)成代碼大多是Vue 2硬上Vue 3反而會踩很多遷移的坑。2. 核心業(yè)務(wù)模塊拆解2.1 用戶端預(yù)約登記與接種記錄查詢用戶端主要面向普通居民核心功能就兩塊預(yù)約接種、查詢記錄。預(yù)約接種的流程看起來簡單——選疫苗、選日期、提交——但實(shí)際操作中的數(shù)據(jù)流轉(zhuǎn)值得仔細(xì)設(shè)計(jì)。以我做的系統(tǒng)為例預(yù)約記錄表里至少要存這些字段用戶id、疫苗id、預(yù)約日期、預(yù)約時(shí)間段上午/下午、狀態(tài)待接種/已完成/已取消、創(chuàng)建時(shí)間。這里有個關(guān)鍵決策預(yù)約時(shí)要不要立刻扣減庫存我的做法是預(yù)約時(shí)鎖定庫存但不扣減。什么意思我的疫苗庫存表里有一個locked_stock字段用戶提交預(yù)約時(shí)available_stock先減去1locked_stock加上1。等到用戶真正完成接種接種記錄生成再把locked_stock減掉變成實(shí)際出庫。如果用戶取消預(yù)約就把a(bǔ)vailable_stock加回來locked_stock對應(yīng)減掉。這樣做的好處是預(yù)約和庫存是兩個獨(dú)立操作但數(shù)據(jù)始終保持一致不會出現(xiàn)庫存顯示有苗、實(shí)際預(yù)約超賣的情況。查詢記錄這塊需要注意分頁。用戶的接種記錄會隨著時(shí)間增長一次性全查出來不僅慢前端渲染也卡。我用的是MyBatis Plus自帶的分頁插件前端傳pageNum和pageSize兩個參數(shù)后端返回total和records配合前端的分頁組件就能實(shí)現(xiàn)流暢翻頁。這里有一個容易犯的錯分頁查詢一定要記得加ORDER BY我見過不少系統(tǒng)因?yàn)椴樵兘Y(jié)果沒有排序翻頁到后面數(shù)據(jù)就亂了這個坑踩一次就長記性了。2.2 管理端疫苗庫存、批次臺賬與數(shù)據(jù)統(tǒng)計(jì)管理端是系統(tǒng)最重的部分也是能拉開項(xiàng)目質(zhì)量差距的部分。我強(qiáng)烈建議管理端至少包含三個模塊疫苗管理、批次管理、統(tǒng)計(jì)報(bào)表。疫苗管理是基礎(chǔ)檔案對應(yīng)vaccine表包括疫苗名稱、生產(chǎn)廠家、適用年齡段、劑次說明、價(jià)格等靜態(tài)信息。這里要提醒一句疫苗名稱和廠家不要直接存在接種記錄表里用id關(guān)聯(lián)就行。很多新手圖省事直接在接種記錄表里存字符串名稱結(jié)果后期疫苗改名或者錄入不規(guī)范統(tǒng)計(jì)報(bào)表里全是臟數(shù)據(jù)。批次管理是疫苗領(lǐng)域的特色功能對應(yīng)vaccine_batch表。每個批次關(guān)聯(lián)一個疫苗id記錄批號、生產(chǎn)日期、有效期、入庫數(shù)量、剩余數(shù)量。批號必須做唯一約束因?yàn)樗且呙缱匪莸年P(guān)鍵索引。有效期字段建議用MySQL的DATE類型到期預(yù)警的SQL一眼就能寫出來WHERE expire_date DATE_ADD(CURDATE(), INTERVAL 30 DAY)。統(tǒng)計(jì)報(bào)表是體現(xiàn)系統(tǒng)價(jià)值的功能。我這里做了兩個核心數(shù)據(jù)一個是在庫疫苗的批次有效期分布另一個是每日/每周接種量趨勢。有效期分布用GROUP BY加COUNT就能搞定接種趨勢用DATE_FORMAT(create_time, %Y-%m-%d)做日期分組然后計(jì)數(shù)。如果覺得還不夠可以再做一個“疫苗利用率 已接種數(shù) / 入庫總數(shù)”的指標(biāo)。這三個統(tǒng)計(jì)到手管理端的實(shí)用性立刻就出來了匯報(bào)也好答辯也好都拿得出手。2.3 數(shù)據(jù)庫設(shè)計(jì)那幾張表才是這套系統(tǒng)的底座數(shù)據(jù)庫設(shè)計(jì)直接決定系統(tǒng)的上限。我按這套系統(tǒng)的業(yè)務(wù)把核心表的關(guān)系梳理如下user用戶表存賬號密碼、姓名、身份證號、手機(jī)號、角色字段。vaccine疫苗檔案表存疫苗名稱、廠家、規(guī)格、說明。vaccine_batch疫苗批次表關(guān)聯(lián)vaccine表存批號、有效期、入庫量、剩余量。appointment預(yù)約表關(guān)聯(lián)user和vaccine存預(yù)約日期、時(shí)間段、狀態(tài)。inoculation_record接種記錄表關(guān)聯(lián)user、vaccine_batch和醫(yī)生用戶id存接種時(shí)間、接種部位、不良反應(yīng)備注。這五張表構(gòu)成了系統(tǒng)的核心骨架。我特別想強(qiáng)調(diào)兩個設(shè)計(jì)細(xì)節(jié)第一user表里的角色字段我用的是role字段加整數(shù)類型1表示管理員、2表示醫(yī)生、3表示普通用戶。有些同學(xué)喜歡用String類型存admin、doctor查詢倒是好理解但代碼里到處都是魔法字符串不如整數(shù)加常量類管理清爽。當(dāng)然如果項(xiàng)目規(guī)模更大角色權(quán)限會升級成role和permission多對多表結(jié)構(gòu)但以這個項(xiàng)目的體量來說單字段角色完全夠用。第二inoculation_record表一定要關(guān)聯(lián)vaccine_batch而不是只關(guān)聯(lián)vaccine這一點(diǎn)最容易被忽略。假設(shè)一個疫苗有A、B兩個批次醫(yī)生錄入時(shí)選了A批次接種記錄里就必須能定位到A批次否則萬一A批次出問題需要召回你根本篩不出來誰打了這批苗。我見過很多所謂“疫苗系統(tǒng)”忽略了這層關(guān)系只能說他們沒做過真正的追溯需求。具體的建表SQL一般通過MyBatis Plus的代碼生成器或者數(shù)據(jù)庫工具生成后面我會在實(shí)操一節(jié)詳細(xì)演示。3. 后端實(shí)現(xiàn)的關(guān)鍵細(xì)節(jié)3.1 登錄鑒權(quán)與Token過期處理管理系統(tǒng)必須有登錄鑒權(quán)這一步幾乎繞不開。我的方案很簡單實(shí)用后端用JWT生成token前端登錄后把token存到localStorage每次請求通過axios攔截器在header里帶上Authorization: token后端用一個攔截器統(tǒng)一校驗(yàn)。JWT的好處是服務(wù)端不用存session天然適合前后端分離。我用的是jjwt這個庫生成token時(shí)把用戶id和角色塞進(jìn)claims里過期時(shí)間設(shè)置為24小時(shí)。攔截器校驗(yàn)通過后把用戶信息放到ThreadLocal里這樣在controller里隨時(shí)可以拿到當(dāng)前登錄用戶。這里有幾個細(xì)節(jié)必須注意第一攔截器要放行登錄接口和注冊接口不然用戶連登錄都進(jìn)不去。第二token快過期時(shí)前端會請求失敗我的做法是在axios的響應(yīng)攔截器里判斷HTTP狀態(tài)碼401然后清空本地token并跳轉(zhuǎn)登錄頁同時(shí)給出“登錄已過期請重新登錄”的提示。第三密碼存儲絕對不能明文我用的是Spring Security自帶的BCryptPasswordEncoder一次性密碼加鹽哈希數(shù)據(jù)庫里存的永遠(yuǎn)不是原始密碼。如果你不想引入Spring Security全家桶也可以用jbcrypt這個輕量級庫效果一樣。3.2 庫存扣減與預(yù)約沖突的并發(fā)處理疫苗庫存是典型的臨界資源兩個人同時(shí)預(yù)約最后一支疫苗系統(tǒng)必須保證只有一個人成功這就是并發(fā)控制要做的事。新手最容易犯的錯誤是“先查再改”——先select看一下庫存大于0再update減1。在并發(fā)場景下兩個請求可能同時(shí)查到庫存是1然后同時(shí)執(zhí)行減1操作結(jié)果變成-1。解決方案有幾層從簡單到復(fù)雜第一層利用數(shù)據(jù)庫的行鎖。MyBatis Plus的update語句可以寫成UPDATE vaccine_batch SET remain_stock remain_stock - 1 WHERE id ? AND remain_stock 0MySQL在更新時(shí)會對這一行加鎖后面的事務(wù)必須排隊(duì)等。通過判斷受影響行數(shù)是否為1就知道本次扣減是否成功。這是最簡單也最可靠的做法推薦優(yōu)先使用。第二層在預(yù)約表上做唯一約束。比如限制同一個用戶同一天不能重復(fù)預(yù)約同一疫苗可以給appointment表建一個(user_id, vaccine_id, appointment_date)的三列唯一索引。這樣一來即使兩個請求并發(fā)進(jìn)來數(shù)據(jù)庫也會強(qiáng)制只讓一個成功另一個直接拋重復(fù)鍵異常。第三層才是Redis分布式鎖但說實(shí)話對于這個體量的項(xiàng)目用Redis鎖屬于過度設(shè)計(jì)。單機(jī)MySQL的原子更新加唯一約束已經(jīng)完全夠用而且實(shí)現(xiàn)簡單、不用額外引入中間件、排查問題也方便。我一直強(qiáng)調(diào)一個原則先夠用再優(yōu)化為不存在的并發(fā)量提前設(shè)計(jì)復(fù)雜方案是自找麻煩。3.3 接口設(shè)計(jì)RESTful風(fēng)格與統(tǒng)一返回結(jié)構(gòu)前后端分離項(xiàng)目接口規(guī)范直接影響對接效率。我建議統(tǒng)一用RESTful風(fēng)格并對返回結(jié)構(gòu)做一個全局包裝。拿預(yù)約接口舉例GET /api/appointment/list分頁查詢預(yù)約列表。POST /api/appointment/add新增預(yù)約。PUT /api/appointment/cancel/{id}取消預(yù)約。GET /api/appointment/detail/{id}預(yù)約詳情。返回結(jié)構(gòu)我用一個統(tǒng)一的Result類包裝字段就三個code、msg、data。code200表示成功code500表示業(yè)務(wù)失敗data是具體返回?cái)?shù)據(jù)。這里有一個實(shí)用技巧后端的Result.ok(data)和Result.error(msg)可以做成靜態(tài)方法controller里一行代碼就返回了代碼觀感會好很多。攔截器里如果校驗(yàn)失敗也要走統(tǒng)一返回結(jié)構(gòu)而不是直接拋一堆堆棧信息給前端。另外關(guān)于接口路徑再提一句。很多人習(xí)慣寫/api/selectAppointmentList這種動詞式路徑雖然也能跑通但不符合RESTful語義。我自己的項(xiàng)目里已經(jīng)全部改成資源名詞加HTTP方法的方式前端對接的人一眼就能看懂團(tuán)隊(duì)協(xié)作時(shí)效率提升不是一點(diǎn)點(diǎn)。4. 前端Vue的落地經(jīng)驗(yàn)4.1 Vue環(huán)境配置的幾個坑Vue環(huán)境配置是很多人的第一道坎我在這里浪費(fèi)過不少時(shí)間先把結(jié)論寫出來。如果你用Vue 2直接npm install -g vue/cli裝全局腳手架然后vue create 你的項(xiàng)目名初始化項(xiàng)目。如果你用Vue 3npm create vuelatest是官方推薦的Vite方式。這里最需要注意的就是Node.js版本Vue 2的腳手架要求Node版本別太高Vue 3的Vite要求Node 16及以上。我遇到過無數(shù)次npm install報(bào)錯查到最后都是Node版本和依賴庫不兼容導(dǎo)致的。node_modules安裝慢的問題建議直接把npm鏡像切到國內(nèi)源npm config set registry https://registry.npmmirror.com裝依賴的速度能快好幾倍。如果裝一半失敗先刪掉node_modules和package-lock.json再重新裝很多時(shí)候比逐個排查依賴沖突更有效率。還有那種“項(xiàng)目從別處拷過來本地怎么跑起來”的場景。第一步是確認(rèn)對方用的Node版本第二步是在項(xiàng)目根目錄執(zhí)行npm install第三步是看package.json里的scripts一般就是npm run serve或者npm run dev。如果啟動報(bào)tsconfig相關(guān)錯誤多半是Vue 3的TypeScript項(xiàng)目沒有正確安裝vue/tsconfig補(bǔ)一下依賴就能解決。4.2 頁面組件劃分與路由守衛(wèi)前端項(xiàng)目一開始就要做好頁面和組件的劃分不然后期全是面條代碼。按這套系統(tǒng)的頁面量我建議把views目錄按角色分文件夾views/admin放管理端的疫苗管理、批次管理、統(tǒng)計(jì)報(bào)表views/user放預(yù)約、記錄查詢views/common放登錄、注冊。每個頁面內(nèi)部如果有重復(fù)模塊再抽組件到components目錄比如疫苗信息卡片、分頁工具欄這些。路由守衛(wèi)是Vue里一個必須掌握的點(diǎn)。我用Vue Router的beforeEach全局前置守衛(wèi)實(shí)現(xiàn)權(quán)限控制判斷目標(biāo)路由是否需要登錄需要的話檢查localStorage里有沒有token有token再判斷路由的meta.role和本地存的角色是否匹配不匹配就跳403頁面。這樣做的好處是即使有人手動修改前端路由地址也進(jìn)不了無權(quán)訪問的頁面。當(dāng)然真正的安全還得后端接口鑒權(quán)兜底前端路由守衛(wèi)只是用戶體驗(yàn)層面的攔截。4.3 axios封裝與交互細(xì)節(jié)axios封裝我每次做項(xiàng)目都會寫因?yàn)椴环庋b的代碼后期改起來想死。我在utils/request.js里統(tǒng)一定義axios實(shí)例設(shè)置baseURL指向后端地址加上請求攔截器自動帶token響應(yīng)攔截器統(tǒng)一處理HTTP錯誤和業(yè)務(wù)錯誤碼。具體交互細(xì)節(jié)上前端所有的頁面狀態(tài)變化loading、提交按鈕禁用都跟接口狀態(tài)綁定比如“提交預(yù)約”按鈕點(diǎn)擊后就禁用等后端返回成功再跳轉(zhuǎn)避免用戶重復(fù)提交產(chǎn)生多條預(yù)約記錄。還有一個經(jīng)常踩的坑是跨域問題。開發(fā)環(huán)境我在Vue的根目錄下建vue.config.js配置devServer.proxy把/api代理到http://localhost:8080前端代碼里就寫相對路徑/api/...從而避免開發(fā)時(shí)的CORS報(bào)錯。生產(chǎn)環(huán)境我在后端用一個配置類統(tǒng)一處理CORS跨域允許指定域名訪問。這兩件事不做前端和后端永遠(yuǎn)聯(lián)調(diào)不通。5. 從本地到上線的部署實(shí)操5.1 打包前必須做的三件事開發(fā)環(huán)境跑得再歡到了部署階段如果不做檢查照樣會翻車。我每次打包前固定做三件事第一數(shù)據(jù)庫初始化腳本重新執(zhí)行一遍。很多數(shù)據(jù)庫表結(jié)構(gòu)是在開發(fā)過程中慢慢加字段改出來的本地?cái)?shù)據(jù)庫已經(jīng)是最新結(jié)構(gòu)但生產(chǎn)環(huán)境還是初始狀態(tài)。我用MyBatis Plus的代碼生成器或者直接執(zhí)行SQL腳本建表優(yōu)先保證create table語句是完整可執(zhí)行的并提前把幾條必要的基礎(chǔ)數(shù)據(jù)比如管理員賬號寫進(jìn)腳本。第二后端配置文件改成生產(chǎn)環(huán)境。application.yml里的數(shù)據(jù)庫地址、密碼、端口、文件上傳路徑全部要換成生產(chǎn)服務(wù)器的值。開發(fā)時(shí)連的localhost數(shù)據(jù)庫部署到服務(wù)器還連localhost那肯定是連不上的。實(shí)際環(huán)境我一般用MySQL 5.7或8.0注意時(shí)區(qū)配置加上serverTimezoneAsia/Shanghai不然日期字段會出現(xiàn)各種詭異的偏移。第三前端構(gòu)建前檢查API地址環(huán)境變量。如果是Vite/Vue CLI項(xiàng)目我通常在.env.production里配置VITE_API_BASE_URL指向后端實(shí)際部署的域名或IP。構(gòu)建命令是npm run build產(chǎn)物在dist目錄里部署的時(shí)候把這個目錄上傳到服務(wù)器的靜態(tài)資源目錄即可。5.2 Spring Boot項(xiàng)目打包與可執(zhí)行Jar啟動后端打包我推薦用Maven的package命令生成一個xxx.jar文件上傳到服務(wù)器用java -jar運(yùn)行。這里有兩個細(xì)節(jié)打包前要確認(rèn)pom.xml里配置了spring-boot-maven-plugin并且mainClass指向你的啟動類。否則打出來的jar不包含內(nèi)嵌Tomcat運(yùn)行時(shí)會報(bào)ClassNotFound。我就見過有人把沒打完整依賴的jar拿上來部署折騰半天最后發(fā)現(xiàn)是打包插件沒配全。啟動命令不建議用java -jar app.jar這種裸命令因?yàn)镃trlC一按進(jìn)程就沒了。我習(xí)慣用nohup java -jar app.jar app.log 21 后臺啟動日志寫到app.log里排查問題直接tail -f app.log。如果需要更規(guī)范的服務(wù)化管理可以寫一個systemd服務(wù)文件配置ExecStart/usr/bin/java -jar /opt/app/app.jar這樣開機(jī)自啟、崩潰重啟都有保障。5.3 寶塔面板部署前端項(xiàng)目的實(shí)操記錄很多同學(xué)的服務(wù)器是Linux用寶塔面板管理網(wǎng)站。我自己的部署流程是這樣的打開寶塔面板先裝好Nginx和MySQL。然后創(chuàng)建站點(diǎn)把dist目錄的內(nèi)容上傳到站點(diǎn)根目錄再配置Nginx的location /api把請求反向代理到后端http://127.0.0.1:8080。這樣用戶訪問站點(diǎn)域名看到的頁面是前端靜態(tài)資源請求接口時(shí)Nginx幫助轉(zhuǎn)發(fā)到后端進(jìn)程。這里要提醒一個坑后端接口路徑的反向代理要帶proxy_set_header配置否則后端拿到請求里的Host是Nginx而不是真實(shí)域名有些業(yè)務(wù)邏輯會受影響。另外權(quán)限控制方面不要在寶塔里關(guān)閉Nginx的防火墻規(guī)則太激進(jìn)至少保證80端口和443端口放行。由于這個項(xiàng)目本身不需要任何額外通道訪問外網(wǎng)部署起來體感上比那些依賴境外服務(wù)的項(xiàng)目順暢太多。6. 高頻問題排查速查我把這個項(xiàng)目開發(fā)過程中最常出現(xiàn)的問題整理成一個排查表你可以直接當(dāng)成備查手冊。問題現(xiàn)象可能原因解決方案前端接口請求403token未帶或token失效檢查axios請求攔截器是否從localStorage取token過期則引導(dǎo)重新登錄CORS跨域報(bào)錯后端未配置跨域或前端未走代理開發(fā)環(huán)境用devServer.proxy生產(chǎn)環(huán)境給后端加CORS配置類連接數(shù)據(jù)庫報(bào)時(shí)區(qū)錯誤MySQL連接串少了serverTimezone參數(shù)在JDBC連接URL加上serverTimezoneAsia/Shanghai中文亂碼數(shù)據(jù)表編碼不是utf8mb4或后端未配置字符編碼建表時(shí)指定CHARSETutf8mb4Spring Boot配置characterEncodingutf-8預(yù)約顯示庫存沒扣減庫存更新邏輯放在預(yù)約接口外或缺少事務(wù)在服務(wù)層方法上加Transactional保證預(yù)約與扣減同事務(wù)提交分頁數(shù)據(jù)重復(fù)/缺失分頁查詢?nèi)鄙貽RDER BY給查詢條件補(bǔ)上排序字段最好按主鍵或創(chuàng)建時(shí)間倒序Vue項(xiàng)目啟動報(bào)tsconfigVue 3 TS項(xiàng)目缺少vue/tsconfig依賴安裝依賴npm install -D vue/tsconfignpm install很慢未切換鏡像源設(shè)置npmmirror源后重新安裝打包后接口404Nginx未配置api反向代理在站點(diǎn)配置中添加location /api的proxy_pass指向后端端口數(shù)據(jù)統(tǒng)計(jì)報(bào)表顯示0日期格式或SQL分組粒度不對用DATE_FORMAT統(tǒng)一日期格式再按天、周、月分組這是我整理出來的一套組合拳項(xiàng)目完成度直接體現(xiàn)在這些細(xì)節(jié)里。每次把表格里的對應(yīng)項(xiàng)排掉系統(tǒng)基本就穩(wěn)定了。寫在最后的一點(diǎn)體會開發(fā)完這套疫苗接種管理系統(tǒng)我自己最深的感觸是這類項(xiàng)目真正的分水嶺從來不是框架用得花不花哨而是業(yè)務(wù)邏輯有沒有閉環(huán)。預(yù)約能不能和庫存對上賬、接種記錄能不能追溯到批次、報(bào)表能不能給管理者真正的決策依據(jù)——這些才是拿得出手的東西??蚣苤皇潜磉_(dá)業(yè)務(wù)的一種工具Spring Boot也好、SSM也好它們存在的意義不是讓你寫出更長的代碼而是讓你把業(yè)務(wù)里的關(guān)系理清、把復(fù)雜的事情變簡單。如果你正在上手類似系統(tǒng)老老實(shí)實(shí)把業(yè)務(wù)閉環(huán)打通把并發(fā)扣庫存這種硬骨頭啃下來你會發(fā)現(xiàn)自己對面向?qū)ο蠛蛿?shù)據(jù)庫的理解又深了一層。