問(wèn)卷調(diào)查系統(tǒng):架構(gòu)、部署與排坑實(shí)戰(zhàn))
剛拿到這套“在線(xiàn)問(wèn)卷調(diào)查系統(tǒng)”源碼的時(shí)候我其實(shí)沒(méi)抱太大期望。畢竟這類(lèi)管理系統(tǒng)在Gitee和GitHub上一搜一大把質(zhì)量參差不齊很多都是“能跑但沒(méi)法用”的Demo級(jí)作品。但這套SpringBoot后端Vue前端MySQL的組合實(shí)測(cè)下來(lái)確實(shí)給了我一個(gè)不小的驚喜結(jié)構(gòu)干凈、功能完整最關(guān)鍵的是——真的能直接跑起來(lái)。這個(gè)項(xiàng)目能做什么用戶(hù)管理、問(wèn)卷創(chuàng)建、在線(xiàn)答題、數(shù)據(jù)統(tǒng)計(jì)、結(jié)果導(dǎo)出一套標(biāo)準(zhǔn)問(wèn)卷調(diào)查系統(tǒng)該有的功能基本都覆蓋了。后端用SpringBoot整合MyBatis-Plus前端用Vue寫(xiě)頁(yè)面數(shù)據(jù)庫(kù)用MySQL存數(shù)據(jù)典型的前后端分離架構(gòu)也是目前JavaWeb項(xiàng)目里最主流的組合。它的價(jià)值不僅僅在于幫你快速搭一個(gè)問(wèn)卷系統(tǒng)更在于提供了一個(gè)完整的學(xué)習(xí)樣本如果你是準(zhǔn)備畢設(shè)、練手或者想了解企業(yè)級(jí)項(xiàng)目結(jié)構(gòu)的學(xué)生能從里面看到控制器、服務(wù)層、Mapper是怎么協(xié)同工作的如果你是想快速部署一個(gè)內(nèi)部調(diào)研系統(tǒng)的開(kāi)發(fā)者直接改一改數(shù)據(jù)庫(kù)配置就能用。我后續(xù)會(huì)從架構(gòu)拆解、數(shù)據(jù)庫(kù)設(shè)計(jì)、運(yùn)行實(shí)戰(zhàn)和排坑經(jīng)驗(yàn)四個(gè)維度把整個(gè)項(xiàng)目講透。1. 整體架構(gòu)與設(shè)計(jì)思路拆解1.1 為什么選SpringBootVue這套組合做問(wèn)卷系統(tǒng)市面上其實(shí)有幾種技術(shù)路線(xiàn)可選。一種是純后端模板渲染像SpringBoot加Thymeleaf寫(xiě)起來(lái)快但頁(yè)面交互體驗(yàn)一般還有一種是用現(xiàn)成的第三方問(wèn)卷平臺(tái)功能強(qiáng)大但數(shù)據(jù)全在別人那里沒(méi)法二次開(kāi)發(fā)再一種就是這套項(xiàng)目采用的前后端分離方案。SpringBoot負(fù)責(zé)提供RESTful接口Vue負(fù)責(zé)頁(yè)面渲染兩者之間通過(guò)JSON格式的數(shù)據(jù)交互。這種組合的好處是職責(zé)清晰后端專(zhuān)注業(yè)務(wù)邏輯和數(shù)據(jù)存儲(chǔ)前端專(zhuān)注交互效果和視覺(jué)呈現(xiàn)。問(wèn)卷系統(tǒng)天然就有“管理端”和“填答端”兩種完全不同的界面需求——管理端需要的是復(fù)雜的信息展示和操作反饋填答端需要的是流暢簡(jiǎn)潔的答題體驗(yàn)——用前后端分離的方式開(kāi)發(fā)兩邊的開(kāi)發(fā)效率都會(huì)高很多。另一個(gè)現(xiàn)實(shí)因素是這套技術(shù)棧的職業(yè)通用性。在我接觸過(guò)的中小型項(xiàng)目中SpringBootVueMySQL的出鏡率非常高學(xué)會(huì)這套體系后續(xù)在招聘市場(chǎng)或接私活的時(shí)候能力可以直接平移學(xué)習(xí)成本不會(huì)被浪費(fèi)。1.2 核心模塊劃分一套系統(tǒng)里的三條主線(xiàn)打開(kāi)這套源碼你會(huì)發(fā)現(xiàn)它的后端代碼結(jié)構(gòu)非常規(guī)整按功能可以拆成三條主線(xiàn)。第一天線(xiàn)是用戶(hù)體系。包含用戶(hù)注冊(cè)、登錄、信息維護(hù)、權(quán)限控制。問(wèn)卷系統(tǒng)的用戶(hù)分為管理員和普通用戶(hù)管理員負(fù)責(zé)創(chuàng)建問(wèn)卷、查看數(shù)據(jù)普通用戶(hù)負(fù)責(zé)填答問(wèn)卷。權(quán)限的控制方式采用的是攔截器加角色判斷夠用且易于理解。第二天線(xiàn)是問(wèn)卷管理。從創(chuàng)建問(wèn)卷、編輯題目、發(fā)布問(wèn)卷、關(guān)閉問(wèn)卷到刪除問(wèn)卷整個(gè)生命周期管理都在這個(gè)模塊里。這是整個(gè)系統(tǒng)最核心的部分涉及到問(wèn)卷狀態(tài)的多態(tài)變化和題目的動(dòng)態(tài)增刪。第三條線(xiàn)是答卷管理。用戶(hù)提交答卷后數(shù)據(jù)要落庫(kù)系統(tǒng)要能夠?qū)Υ鹁頂?shù)據(jù)進(jìn)行清洗、統(tǒng)計(jì)、可視化展示。這套源碼里用了一些很聰明的統(tǒng)計(jì)技巧比如通過(guò)SQL聚合函數(shù)直接計(jì)算選項(xiàng)分布避免在Java內(nèi)存里做大量循環(huán)統(tǒng)計(jì)。1.3 這套設(shè)計(jì)的取舍亮點(diǎn)我在實(shí)際跑項(xiàng)目的時(shí)候發(fā)現(xiàn)一個(gè)特別好的設(shè)計(jì)細(xì)節(jié)問(wèn)卷和題目的關(guān)系用的是主從表設(shè)計(jì)。問(wèn)卷主表存問(wèn)卷的基本信息標(biāo)題、狀態(tài)、創(chuàng)建時(shí)間題目表存每一道題的具體內(nèi)容通過(guò)外鍵關(guān)聯(lián)。這個(gè)設(shè)計(jì)看起來(lái)簡(jiǎn)單但很多人首次做管理系統(tǒng)時(shí)會(huì)踩進(jìn)“把所有信息塞進(jìn)一張大表”的坑里導(dǎo)致后期擴(kuò)展和統(tǒng)計(jì)都非常痛苦。這種主從表設(shè)計(jì)的優(yōu)勢(shì)打個(gè)比方就很好理解問(wèn)卷就像一本書(shū)題目是書(shū)里的每一頁(yè)二者分開(kāi)存想改第幾頁(yè)就改哪頁(yè)想統(tǒng)計(jì)整本書(shū)的頁(yè)數(shù)也不用把書(shū)拆了數(shù)。如果全賽在一張表里每次查問(wèn)卷都要帶出一坨題目性能和數(shù)據(jù)結(jié)構(gòu)的清晰度都會(huì)受影響。2. 數(shù)據(jù)庫(kù)表設(shè)計(jì)與核心實(shí)現(xiàn)解析2.1 數(shù)據(jù)表全景六張表?yè)纹鹨粋€(gè)系統(tǒng)這套項(xiàng)目的數(shù)據(jù)庫(kù)設(shè)計(jì)非??酥坪诵木土鶑埍砦艺砹艘粋€(gè)簡(jiǎn)表方便你快速瀏覽。表名職責(zé)描述核心字段user用戶(hù)信息id、username、password、role、create_timequestionnaire問(wèn)卷主表id、title、description、status、creator_id、create_timequestion題目表id、questionnaire_id、type、content、sort_orderoption選項(xiàng)表選擇題用id、question_id、content、sort_orderanswer答卷主表id、questionnaire_id、user_id、submit_timeanswer_detail答卷明細(xì)表id、answer_id、question_id、option_id、content細(xì)看這套表結(jié)構(gòu)有幾個(gè)地方設(shè)計(jì)得很有門(mén)道。第一題目類(lèi)型字段type用了整數(shù)枚舉而不是字符串1代表單選、2代表多選、3代表填空。這樣存儲(chǔ)占用小、查詢(xún)效率高Java代碼里再定義對(duì)應(yīng)的枚舉類(lèi)做轉(zhuǎn)換兼顧了性能和可讀性。第二選項(xiàng)表單獨(dú)拆出來(lái)而不是在question表里搞一個(gè)逗號(hào)分隔的文本字段。這一點(diǎn)尤其重要——如果選項(xiàng)是用逗號(hào)拼在一個(gè)字段里統(tǒng)計(jì)每個(gè)選項(xiàng)被選了多少次的時(shí)候就只能靠子串匹配不僅寫(xiě)起來(lái)痛苦跑起來(lái)也慢。拆成獨(dú)立的option表后統(tǒng)計(jì)可以用JOIN加GROUP BY效率和優(yōu)雅程度完全是兩個(gè)檔次。第三answer和answer_detail兩表分離這個(gè)設(shè)計(jì)很見(jiàn)功力。answer表記錄“哪個(gè)人在哪次提交了哪份問(wèn)卷”answer_detail則是明細(xì)賬。將來(lái)要做分析時(shí)可以先從answer表按條件過(guò)濾再去關(guān)聯(lián)detail不用每次都在明細(xì)表里全表掃描。2.2 問(wèn)卷狀態(tài)機(jī)掌握問(wèn)卷生命周期的最關(guān)鍵邏輯問(wèn)卷表里的status字段承擔(dān)著整個(gè)系統(tǒng)的業(yè)務(wù)核心邏輯。這套源碼采用的是經(jīng)典的三態(tài)設(shè)計(jì)1代表草稿、2代表發(fā)布中、3代表已關(guān)閉。這三個(gè)狀態(tài)的流轉(zhuǎn)規(guī)則我直接說(shuō)結(jié)論草稿狀態(tài)下問(wèn)卷可以被編輯和刪除發(fā)布后問(wèn)卷只讀用戶(hù)可答題創(chuàng)建者不可改題關(guān)閉后問(wèn)卷歸檔不能答題但可查看統(tǒng)計(jì)結(jié)果。很多剛接觸者容易忽略“發(fā)布后不可改題”這層規(guī)則其實(shí)這是很多成熟問(wèn)卷系統(tǒng)的通用約定。想象一下問(wèn)卷已經(jīng)發(fā)給200個(gè)人作答了這時(shí)候創(chuàng)建者偷偷改了一個(gè)題目選項(xiàng)那么改之前和改之后的數(shù)據(jù)混在一起統(tǒng)計(jì)結(jié)果就失真了。這套源碼對(duì)發(fā)布狀態(tài)加了這個(gè)業(yè)務(wù)約束是充分理解行業(yè)需求后才能做出來(lái)的設(shè)計(jì)。2.3 前端答題頁(yè)的幾個(gè)實(shí)現(xiàn)細(xì)節(jié)Vue前端部分答題頁(yè)的實(shí)現(xiàn)我建議你重點(diǎn)閱讀。它采用了動(dòng)態(tài)表單渲染的思路后端把問(wèn)卷的題目數(shù)據(jù)以JSON形式傳給前端Vue拿到數(shù)據(jù)后通過(guò)v-for動(dòng)態(tài)循環(huán)渲染每種類(lèi)型的題目組件。這個(gè)方案避免了寫(xiě)死頁(yè)面結(jié)構(gòu)的問(wèn)題。傳統(tǒng)做法是同一套代碼的頁(yè)面就那么幾個(gè)固定題目每次換個(gè)問(wèn)卷就要改前端代碼。而這套系統(tǒng)的做法是頁(yè)面是通用的問(wèn)卷內(nèi)容從接口拉取數(shù)據(jù)驅(qū)動(dòng)渲染。新建一份問(wèn)卷前端一行不動(dòng)頁(yè)面自動(dòng)跟著變。還有一個(gè)值得注意的細(xì)節(jié)是答題進(jìn)度的實(shí)時(shí)校驗(yàn)。每完成一道必答題前端會(huì)更新本地答題進(jìn)度記錄未完成的題目用星號(hào)標(biāo)記提交時(shí)再統(tǒng)一校驗(yàn)。這種交互模式對(duì)填答者的體驗(yàn)很友好能有效降低因?yàn)槁┐鸲鴮?dǎo)致的提交失敗率。3. 運(yùn)行環(huán)境準(zhǔn)備與部署實(shí)操記錄3.1 環(huán)境最低要求清單跑這套項(xiàng)目環(huán)境配置沒(méi)有特別苛刻的要求。我列出實(shí)測(cè)可行的版本組合你按這個(gè)準(zhǔn)備不會(huì)出問(wèn)題。依賴(lài)項(xiàng)推薦版本說(shuō)明JDK1.8實(shí)測(cè)JDK8和JDK11都能跑不建議直接上17Maven3.6后端依賴(lài)管理必備N(xiāo)ode.js14前端構(gòu)建環(huán)境MySQL5.7新版需要8.0視driver配置調(diào)整Redis不需要這套源碼沒(méi)用到別被其他項(xiàng)目經(jīng)驗(yàn)誤導(dǎo)這里多說(shuō)一句JDK版本的問(wèn)題。很多初學(xué)者上來(lái)就裝最新的JDK17甚至21結(jié)果一跑Java代碼就拋各種奇怪異常。原因在于Spring Boot 2.x系列官方支持到JDK8和11到JDK17會(huì)出現(xiàn)反射訪(fǎng)問(wèn)限制等問(wèn)題。這個(gè)項(xiàng)目用的是Spring Boot 2.x所以老老實(shí)實(shí)用JDK8或11能少踩很多坑。3.2 數(shù)據(jù)庫(kù)初始化的正確姿勢(shì)這套源碼自帶一個(gè)數(shù)據(jù)庫(kù)初始化腳本通常在項(xiàng)目的sql目錄下文件名類(lèi)似questionnaire.sql。但直接導(dǎo)入之前有兩個(gè)關(guān)鍵步驟必須調(diào)整。第一建庫(kù)語(yǔ)句要改。打開(kāi)腳本文件把開(kāi)頭的CREATE DATABASE IF NOT EXISTS后面跟著的數(shù)據(jù)庫(kù)名改成你想要的庫(kù)名推薦用英文小寫(xiě)字母比如survey_db。我之前有用戶(hù)環(huán)境里默認(rèn)庫(kù)名帶中劃線(xiàn)后面配置文件里所有連接串都要跟著改非常麻煩還是開(kāi)始就統(tǒng)一好。第二確認(rèn)MySQL的字符集。腳本里如果沒(méi)有設(shè)置DEFAULT CHARSETutf8mb4建議手動(dòng)在庫(kù)和表上補(bǔ)上。問(wèn)卷系統(tǒng)百分之百會(huì)涉及用戶(hù)輸入的中文內(nèi)容如果建庫(kù)時(shí)默認(rèn)charset是latin1問(wèn)卷標(biāo)題就會(huì)變成問(wèn)號(hào)。這個(gè)坑我在別的項(xiàng)目里見(jiàn)過(guò)太多次確保utf8mb4是你的數(shù)據(jù)庫(kù)默認(rèn)字符集這是所有中文項(xiàng)目運(yùn)行前的必修課。導(dǎo)入命令用這一條即可mysql -u root -p questionnaire.sql導(dǎo)入完成后用mysql命令看一眼表清單確認(rèn)六張核心表都建好了再進(jìn)行下一步。3.3 后端配置的修改點(diǎn)與啟動(dòng)驗(yàn)證后端啟動(dòng)前必須改配置。項(xiàng)目配置文件一般是src/main/resources下的application.yml核心要改的是數(shù)據(jù)庫(kù)連接。如果只是本地跑最穩(wěn)的配置寫(xiě)法是直接用你自己數(shù)據(jù)庫(kù)的用戶(hù)名密碼。具體打開(kāi)application.yml找到spring.datasource.url這一段確認(rèn)jdbc:mysql后面的地址、端口和庫(kù)名是否與你的環(huán)境一致。如果MySQL裝在本機(jī)、端口還是默認(rèn)的3306一般只需要改庫(kù)名和你自己的用戶(hù)名密碼。啟動(dòng)后端有一個(gè)很好的自檢方式不只是看“BUILD SUCCESS”要看控制臺(tái)日志里的端口監(jiān)聽(tīng)信息。Spring Boot啟動(dòng)完成后會(huì)打印一行Tomcat started on port(s): 8080看到這個(gè)才說(shuō)明后端真的起來(lái)了。如果只看到BUILD SUCCESS但沒(méi)有端口監(jiān)聽(tīng)日志多半是啟動(dòng)過(guò)程中出錯(cuò)了這時(shí)候要拉上面的Exception堆棧信息看真正原因。后端接口的自測(cè)也很簡(jiǎn)單。瀏覽器或接口調(diào)試工具里直接訪(fǎng)問(wèn)http://localhost:8080/api/user/list之類(lèi)的接口如果返回JSON數(shù)據(jù)說(shuō)明接口鏈路通暢。這一步做完再配前端就心里有底了。3.4 前端環(huán)境的搭建與跨域配置前端代碼在項(xiàng)目里一般是單獨(dú)一個(gè)目錄比如vue-frontend或front。進(jìn)入這個(gè)目錄后先裝依賴(lài)npm install這一步在國(guó)內(nèi)網(wǎng)絡(luò)環(huán)境下可能比較慢有耐心的可以用默認(rèn)源。如果是著急調(diào)試把registry切換成國(guó)內(nèi)鏡像源也行npm config set registry https://registry.npmmirror.com依賴(lài)裝完之后啟動(dòng)開(kāi)發(fā)服務(wù)器npm run serveVue的調(diào)試服務(wù)器默認(rèn)跑在8080端口但后端接口也占了8080兩個(gè)會(huì)沖突。這里有兩種解法推薦先看后端的配置。后端在application.yml中如果配了server.port8080前端開(kāi)發(fā)服務(wù)器的默認(rèn)端口就可以改成8090或其他。這個(gè)項(xiàng)目如果先啟動(dòng)了后端前端本身會(huì)提示端口沖突選擇換一個(gè)新端口就好。如果前端和后端不在同一個(gè)端口下運(yùn)行就涉及跨域問(wèn)題。這套源碼大概率在后端的WebConfig或CorsConfig里已經(jīng)配置了跨域放行如果沒(méi)有最簡(jiǎn)單的辦法是在Vue項(xiàng)目的vue.config.js里配置devServer的proxy代理把接口請(qǐng)求全部proxy轉(zhuǎn)發(fā)到后端端口。前端頁(yè)面用相對(duì)路徑請(qǐng)求/api就不會(huì)再有跨域煩惱。這一套邏輯不只是本項(xiàng)目的經(jīng)驗(yàn)凡是前后端分離的項(xiàng)目都是同樣操作模式。3.5 前后端聯(lián)調(diào)的標(biāo)準(zhǔn)驗(yàn)收清單全部啟動(dòng)后不要急著說(shuō)部署成功建議按下面的清單走一遍完整流程任何一步卡住都能快速定位問(wèn)題的歸屬端。我將這個(gè)項(xiàng)目驗(yàn)證通過(guò)的檢查項(xiàng)整理成了一張快速驗(yàn)收表檢查項(xiàng)預(yù)期結(jié)果歸屬端訪(fǎng)問(wèn)前端首頁(yè)頁(yè)面正常渲染無(wú)白屏報(bào)錯(cuò)前端注冊(cè)一個(gè)新用戶(hù)注冊(cè)成功數(shù)據(jù)庫(kù)user表多一條記錄后端數(shù)據(jù)庫(kù)登錄后進(jìn)入后臺(tái)跳轉(zhuǎn)正常能展示當(dāng)前用戶(hù)信息前后端配合創(chuàng)建一份問(wèn)卷問(wèn)卷出現(xiàn)在列表中狀態(tài)為草稿后端前端發(fā)布問(wèn)卷并復(fù)制填答鏈接鏈接能正常打開(kāi)答題頁(yè)前端提交一份答卷答題頁(yè)提示成功后端入表后端數(shù)據(jù)庫(kù)查看統(tǒng)計(jì)報(bào)表選項(xiàng)分布和圖表正確后端前端這七步走通系統(tǒng)主干功能就全部驗(yàn)證過(guò)了。后續(xù)再去填一些邊界功能比如編輯題目、數(shù)據(jù)導(dǎo)出等就會(huì)順很多。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 啟動(dòng)類(lèi)問(wèn)題端口被占與依賴(lài)沖突實(shí)際跑項(xiàng)目最常遇到的就是兩類(lèi)啟動(dòng)問(wèn)題。一個(gè)是端口被占用另一個(gè)是依賴(lài)版本沖突。端口被占用的報(bào)錯(cuò)信息一般是Port 8080 was already in use。如果你本機(jī)之前跑過(guò)其他Java服務(wù)或者有別的開(kāi)發(fā)工具占了8080后端就起不來(lái)。最省事的解決辦法是改后端端口在application.yml里改server.port比如改成8090。同時(shí)前端所有請(qǐng)求的baseURL也要同步改如果前端配置里寫(xiě)死了8080記得一并調(diào)整。依賴(lài)沖突更多發(fā)生在Maven下載階段。項(xiàng)目能“直接運(yùn)行”的前提是你本地Maven倉(cāng)庫(kù)里有正確版本的依賴(lài)。如果下載過(guò)程中斷或者本地倉(cāng)庫(kù)里存在舊版本的jar包會(huì)拋出NoClassDefFoundError或BeanCreationException。遇到這種問(wèn)題優(yōu)先試一次強(qiáng)制刷新mvn clean package -U-U參數(shù)強(qiáng)制讓Maven檢查遠(yuǎn)程倉(cāng)庫(kù)的更新版本對(duì)付依賴(lài)版本不一致有奇效。還需要確認(rèn)一下你的Maven全局配置里settings.xml的鏡像地址是否可靠國(guó)內(nèi)環(huán)境經(jīng)常因?yàn)槟J(rèn)遠(yuǎn)在國(guó)外下載到一半就超時(shí)把這個(gè)配置好能省下一大半的折騰時(shí)間。4.2 數(shù)據(jù)庫(kù)連接的經(jīng)典報(bào)錯(cuò)Public Key Retrieval is not allowed這是一個(gè)極具代表性的MySQL 8.0踩坑點(diǎn)。如果你本機(jī)用的是MySQL 8.0以上跑這個(gè)項(xiàng)目時(shí)后端日志里出現(xiàn)了Public Key Retrieval is not allowed不要慌不是項(xiàng)目本身有問(wèn)題而是MySQL 8.0默認(rèn)的caching_sha2_password認(rèn)證插件不允許客戶(hù)端在非SSL連接時(shí)直接請(qǐng)求公鑰。有兩個(gè)解決方向二選一即可。一個(gè)是連接串里加參數(shù)jdbc:mysql://localhost:3306/survey_db?allowPublicKeyRetrievaltrueuseSSLfalse另一個(gè)是把數(shù)據(jù)庫(kù)用戶(hù)的認(rèn)證方式改成mysql_native_passwordSQL語(yǔ)句如下ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密碼;這兩種方法第一種改起來(lái)快不影響數(shù)據(jù)庫(kù)全局配置第二種對(duì)老舊客戶(hù)端兼容性更好實(shí)際選哪個(gè)都行。4.3 前端白屏Node版本過(guò)低與依賴(lài)缺失前端起不來(lái)或者起來(lái)后白屏大概率卡在兩個(gè)地方。第一Node.js版本過(guò)舊。Vue CLI的現(xiàn)代版本對(duì)Node的版本要求最低在12以上如果你還在用8或10啟動(dòng)時(shí)就會(huì)直接報(bào)錯(cuò)或卡在編譯階段。用node -v先確認(rèn)自己的版本如果版本過(guò)低去官網(wǎng)裝一個(gè)LTS版本即可。第二node_modules目錄不完整。很多從Gitee或網(wǎng)盤(pán)下載的壓縮包并不會(huì)連同node_modules一起打包因?yàn)轶w積太大且不通用如果你沒(méi)有執(zhí)行npm install或者這個(gè)過(guò)程中斷啟動(dòng)后頁(yè)面就會(huì)因?yàn)槿蹦K而渲染失敗。判斷方法很簡(jiǎn)單打開(kāi)前端dist或src下的入口文件瀏覽器F12打開(kāi)控制臺(tái)如果看到一堆Module not found的紅色報(bào)錯(cuò)就是依賴(lài)不全重新執(zhí)行一次npm install并確認(rèn)安裝過(guò)程成功到100%。4.4 功能性問(wèn)題答題完成后統(tǒng)計(jì)結(jié)果不刷新這是一個(gè)比較隱蔽的邏輯層面的坑。問(wèn)卷創(chuàng)建者填完問(wèn)卷、查看統(tǒng)計(jì)報(bào)表時(shí)發(fā)現(xiàn)數(shù)據(jù)不是最新的或者提示“暫無(wú)數(shù)據(jù)”。排這個(gè)問(wèn)題的思路要先從數(shù)據(jù)鏈路從上往下捋。打開(kāi)數(shù)據(jù)庫(kù)查詢(xún)answer表看你剛才提交的那份答卷是否真的落庫(kù)了。如果表里沒(méi)有說(shuō)明提交接口出問(wèn)題檢查前端控制臺(tái)提交請(qǐng)求的返回狀態(tài)如果表里有那就是統(tǒng)計(jì)接口的SQL邏輯問(wèn)題常見(jiàn)的原因是統(tǒng)計(jì)時(shí)條件篩選里多了不必要的狀態(tài)判斷比如只統(tǒng)計(jì)某種狀態(tài)的答卷而真實(shí)數(shù)據(jù)里狀態(tài)值不一致。我這幾次幫用戶(hù)排查九成以上都卡在第一步——接口請(qǐng)求根本沒(méi)有成功發(fā)送到后端。前端提交按鈕的校驗(yàn)邏輯過(guò)多有一項(xiàng)沒(méi)通過(guò)請(qǐng)求壓根就沒(méi)發(fā)出去。所以排查順序建議固定為前端請(qǐng)求是否發(fā)出 - 后端是否收到并返回 - 數(shù)據(jù)庫(kù)是否寫(xiě)入 - 統(tǒng)計(jì)接口是否讀出來(lái)。這個(gè)順序在問(wèn)卷系統(tǒng)里幾乎是通用的排查路線(xiàn)。5. 快速上手心得與擴(kuò)展建議5.1 最省時(shí)間的上手路徑如果你剛拿到這套源碼別急著打開(kāi)IDE就直接跑。我的建議是先花十分鐘看README和目錄結(jié)構(gòu)摸清楚前后端各自的位置再去改配置。磨刀不誤砍柴工這個(gè)習(xí)慣在接手任何開(kāi)源項(xiàng)目時(shí)都適用。對(duì)于學(xué)生朋友我還有一個(gè)額外建議在能完整跑通的基礎(chǔ)上試著在問(wèn)卷題目類(lèi)型里加一種新題型。比如現(xiàn)在只有單選、多選、填空你可以嘗試加一個(gè)“評(píng)分題”1到5星打分。這個(gè)擴(kuò)展會(huì)同時(shí)涉及數(shù)據(jù)庫(kù)取值、后端類(lèi)型判斷、前端動(dòng)態(tài)渲染、統(tǒng)計(jì)圖表適配四個(gè)層面的修改把這條鏈路走通你對(duì)這個(gè)項(xiàng)目的理解會(huì)瞬間從“會(huì)用”升級(jí)到“懂原理”比背十遍八股文都管用。5.2 部署到服務(wù)器的路線(xiàn)參考本地跑通了下一步通常是部署到云服務(wù)器上。部署時(shí)需要將前端項(xiàng)目進(jìn)行構(gòu)建npm run build構(gòu)建完成后會(huì)在dist目錄下生成一堆靜態(tài)文件。這些靜態(tài)文件可以直接用Nginx托管然后通過(guò)Nginx反向代理把/api路徑轉(zhuǎn)發(fā)到后端的Spring Boot服務(wù)上。后端側(cè)打包也類(lèi)似mvn clean package -DskipTests生成的jar包直接在服務(wù)器上用java -jar運(yùn)行即可。如果服務(wù)器內(nèi)存比較小比如只有1G可以在運(yùn)行命令里加參數(shù)限制JVM內(nèi)存java -jar -Xms256m -Xmx512m questionnaire-system.jar我在部署過(guò)程中還習(xí)慣給服務(wù)器開(kāi)一個(gè)定時(shí)任務(wù)每天自動(dòng)備份一次數(shù)據(jù)庫(kù)。做法簡(jiǎn)單用crontab加一條mysqldump命令就行。問(wèn)卷系統(tǒng)的數(shù)據(jù)一旦上線(xiàn)會(huì)持續(xù)增長(zhǎng)沒(méi)有備份機(jī)制就像把文件只放在桌面不備份到網(wǎng)盤(pán)一樣風(fēng)險(xiǎn)太大。5.3 記錄一個(gè)數(shù)據(jù)安全的合規(guī)提醒最后給所有要實(shí)際運(yùn)營(yíng)問(wèn)卷系統(tǒng)的朋友提個(gè)醒收集用戶(hù)填答數(shù)據(jù)時(shí)如果問(wèn)卷里包含手機(jī)號(hào)、姓名、身份證等個(gè)人信息盡量不要明文存儲(chǔ)后端可以在入庫(kù)前用哈?;蚣用芩惴ㄌ幚怼A硗馔暾拇鹁砻骷?xì)在統(tǒng)計(jì)完有效問(wèn)卷數(shù)量后可以考慮定期物理刪除或脫敏。我在實(shí)際項(xiàng)目中見(jiàn)過(guò)不止一次因?yàn)閱?wèn)卷數(shù)據(jù)泄露引發(fā)的糾紛。系統(tǒng)源碼再完善也只是工具數(shù)據(jù)安全意識(shí)和存儲(chǔ)規(guī)范才是你自己要守住的底線(xiàn)。這兩條做到位問(wèn)卷系統(tǒng)才能真正用得長(zhǎng)久。