卷調(diào)查系統(tǒng)從開(kāi)發(fā)到論文全流程解析)
簡(jiǎn)介這是一套完整的基于SpringBoot與Vue的在線問(wèn)卷調(diào)查系統(tǒng)畢業(yè)設(shè)計(jì)資源面向計(jì)算機(jī)專(zhuān)業(yè)本科生及Java全棧初學(xué)者解決課程設(shè)計(jì)、畢設(shè)選題與前后端協(xié)同開(kāi)發(fā)實(shí)踐需求。資源包含755個(gè)文件涵蓋101個(gè)Java后端邏輯文件、50個(gè)Vue前端組件、160個(gè)JS交互腳本、53個(gè)CSS樣式文件、79個(gè)GIF動(dòng)效資源及1個(gè)SQL數(shù)據(jù)庫(kù)腳本完整支撐系統(tǒng)部署與二次開(kāi)發(fā)壓縮包大小23.59MB結(jié)構(gòu)清晰含build/run/install三類(lèi)批處理腳本便于快速啟動(dòng)。已有86人學(xué)習(xí)下載資源附帶可直接運(yùn)行的源碼、MySQL建庫(kù)建表腳本、管理員與用戶雙角色功能模塊含問(wèn)卷管理、題目統(tǒng)計(jì)、新聞資訊、輪播圖等后臺(tái)功能以及配套畢業(yè)論文框架開(kāi)箱即用顯著降低畢設(shè)開(kāi)發(fā)門(mén)檻與調(diào)試成本?;赟pringBootVue的在線問(wèn)卷調(diào)查系統(tǒng)從選題到跑通再到寫(xiě)論文的全過(guò)程記錄最近幫一個(gè)學(xué)弟把關(guān)他的畢業(yè)設(shè)計(jì)題目就是在線問(wèn)卷調(diào)查系統(tǒng)技術(shù)棧是SpringBootMySQLMavenVue附帶源碼、數(shù)據(jù)庫(kù)腳本和畢業(yè)論文。這個(gè)組合在計(jì)算機(jī)畢業(yè)設(shè)計(jì)里可以說(shuō)非常經(jīng)典了但正因?yàn)榻?jīng)典很多人只是在Gitee上找個(gè)項(xiàng)目下載下來(lái)跑起來(lái)就以為完事了——結(jié)果答辯的時(shí)候連自己項(xiàng)目里的表結(jié)構(gòu)都說(shuō)不清楚更別提被老師問(wèn)幾句就卡殼。這篇文章我打算從實(shí)際做項(xiàng)目的角度把這個(gè)問(wèn)卷系統(tǒng)從選題邏輯、技術(shù)選型、數(shù)據(jù)庫(kù)設(shè)計(jì)、前后端核心流程到畢業(yè)論文寫(xiě)作、常見(jiàn)坑排查完整地過(guò)一遍。如果你正好也在做類(lèi)似題目或者手頭已經(jīng)有了這份源碼但還沒(méi)完全吃透這篇文章應(yīng)該能幫你省下不少時(shí)間。1. 這個(gè)選題為什么值得做問(wèn)卷系統(tǒng)背后的業(yè)務(wù)邏輯遠(yuǎn)比你想的復(fù)雜很多同學(xué)選畢業(yè)設(shè)計(jì)題目時(shí)有兩個(gè)極端要么選一個(gè)網(wǎng)上教程滿天飛的圖書(shū)管理系統(tǒng)做出來(lái)千篇一律答辯時(shí)老師看一眼就pass要么選一個(gè)偏算法或底層框架的方向結(jié)果半年都沒(méi)能落地。問(wèn)卷調(diào)查系統(tǒng)屬于典型的看起來(lái)簡(jiǎn)單、做起來(lái)有內(nèi)容的題目。表面上看問(wèn)卷系統(tǒng)無(wú)非就是管理員創(chuàng)建問(wèn)卷、用戶填寫(xiě)問(wèn)卷、查看統(tǒng)計(jì)結(jié)果這三件事。但如果你真正去梳理需求會(huì)發(fā)現(xiàn)這里面藏著好幾層業(yè)務(wù)邏輯。第一層是問(wèn)卷本身的生命周期。一份問(wèn)卷從創(chuàng)建到真正投放要經(jīng)歷草稿態(tài)、發(fā)布態(tài)、關(guān)閉態(tài)。草稿態(tài)的問(wèn)卷可以隨意編輯一旦發(fā)布就不能再改題目了否則之前填過(guò)的數(shù)據(jù)就對(duì)不上。這個(gè)狀態(tài)流轉(zhuǎn)的設(shè)計(jì)是最容易體現(xiàn)一個(gè)開(kāi)發(fā)者對(duì)業(yè)務(wù)理解深度的地方。第二層是題型維度的差異。常見(jiàn)題型有單選題、多選題、填空題、下拉選擇題、評(píng)分題有些系統(tǒng)還需要矩陣題即一組題目共用一組選項(xiàng)。不同題型的數(shù)據(jù)存儲(chǔ)方式不一樣單選題存一個(gè)選項(xiàng)ID就行多選題可能要存逗號(hào)分隔的多個(gè)選項(xiàng)ID填空題就是純文本。如果一開(kāi)始表結(jié)構(gòu)設(shè)計(jì)得不夠靈活后面兼容新題型會(huì)非常痛苦。第三層是數(shù)據(jù)統(tǒng)計(jì)的需求。用戶填完問(wèn)卷只是第一步管理員真正關(guān)心的是每個(gè)選項(xiàng)被選了多少次參與總?cè)藬?shù)是多少各題目的回收率如何這些統(tǒng)計(jì)結(jié)果需要從答卷表里實(shí)時(shí)聚合出來(lái)。如果只是用group by硬查數(shù)據(jù)量一上來(lái)就會(huì)出現(xiàn)明顯的性能瓶頸這時(shí)候就需要考慮緩存或者預(yù)聚合。第四層是權(quán)限控制。普通用戶和管理員看到的東西完全不一樣用戶能看到已發(fā)布的問(wèn)卷、填寫(xiě)問(wèn)卷、查看自己的填寫(xiě)記錄管理員能管理所有問(wèn)卷、查看詳情、導(dǎo)出數(shù)據(jù)。沒(méi)有SSO和RBAC那一套復(fù)雜體系但至少要保證基本的越權(quán)防護(hù)不能讓普通用戶通過(guò)改URL參數(shù)就刪掉別人的問(wèn)卷。所以你看這個(gè)題目雖然入門(mén)容易但要做到能答辯、能講清楚、能扛住追問(wèn)其實(shí)需要你把CRUD背后的業(yè)務(wù)規(guī)則理解透徹。這也正是老師希望通過(guò)這個(gè)題目考察你的東西能不能把一個(gè)真實(shí)場(chǎng)景的問(wèn)題抽象成合理的數(shù)據(jù)模型和代碼結(jié)構(gòu)。2. 技術(shù)棧選型為什么偏偏是SpringBootMySQLMavenVue而不是別的組合選技術(shù)棧這件事很多同學(xué)的思路是學(xué)長(zhǎng)用啥我用啥或者推薦系統(tǒng)里有啥我用啥。但實(shí)際上這個(gè)組合能成為畢業(yè)設(shè)計(jì)的主流選擇是有它內(nèi)在邏輯的。SpringBoot解決的是后端開(kāi)發(fā)效率問(wèn)題。如果你的項(xiàng)目用的是SSHSpringMVCSpringHibernate那套老古董光是各種XML配置就能寫(xiě)掉你一個(gè)月的時(shí)間而且你寫(xiě)的大部分配置跟你做的業(yè)務(wù)沒(méi)有任何關(guān)系。SpringBoot通過(guò)自動(dòng)配置和起步依賴把搭建一個(gè)能跑起來(lái)的Web項(xiàng)目這件事壓縮到了幾分鐘。對(duì)于畢設(shè)來(lái)講你省下的時(shí)間可以用來(lái)打磨業(yè)務(wù)代碼和寫(xiě)論文而不是跟配置死磕。MySQL的選擇幾乎沒(méi)有懸念。因?yàn)闊o(wú)論你以后進(jìn)哪家公司關(guān)系型數(shù)據(jù)庫(kù)的基本功都是必須要過(guò)的關(guān)卡。MySQL的JDBC驅(qū)動(dòng)、連接池比如Druid或HikariCP、ORM框架MyBatis或JPA這些在SpringBoot生態(tài)里都有非常成熟的整合方案你只要學(xué)會(huì)基本的SQL寫(xiě)法就能完成絕大多數(shù)功能。相比之下如果一上來(lái)就用MongoDB或者Elasticsearch這種偏門(mén)數(shù)據(jù)庫(kù)不僅學(xué)習(xí)成本高答辯時(shí)老師也會(huì)質(zhì)疑你為什么要在這個(gè)場(chǎng)景下引入非關(guān)系型數(shù)據(jù)庫(kù)。Maven解決的是依賴管理和構(gòu)建問(wèn)題。這個(gè)可能被很多同學(xué)忽視但它實(shí)際幫你省了大事。SpringBoot官方推薦使用Maven或Gradle來(lái)管理項(xiàng)目因?yàn)槟愕捻?xiàng)目要依賴Controller、Service、Mapper、JWT、Lombok等十幾個(gè)甚至幾十個(gè)Jar包如果用最原始的方式手動(dòng)把Jar包往lib目錄里拷版本沖突能把人逼瘋。Maven有了中央倉(cāng)庫(kù)的機(jī)制后你在pom.xml里聲明坐標(biāo)它自動(dòng)把依賴和傳遞依賴都拉到本地做畢業(yè)設(shè)計(jì)完全夠用。而且最終打包成Jar包交給老師跑比打WAR包部署到Tomcat省心得多。Vue解決的是前后端分離的問(wèn)題。當(dāng)然你也可以用Thymeleaf做服務(wù)端渲染純后端也能把問(wèn)卷系統(tǒng)做完。但既然題目明確說(shuō)了基于Vue那就意味著你要做的是前后端分離架構(gòu)——前端通過(guò)AJAX調(diào)用后端提供的RESTful API拿到JSON數(shù)據(jù)之后渲染頁(yè)面。這個(gè)架構(gòu)的好處是前端開(kāi)發(fā)和后端開(kāi)發(fā)可以并行你不需要改一處前端代碼就重啟一次Java進(jìn)程部署的時(shí)候前端打包后的靜態(tài)文件扔到Nginx或放到后端static目錄里都行。對(duì)于畢設(shè)來(lái)說(shuō)前后端分離有一個(gè)很現(xiàn)實(shí)的好處就是你的論文里可以多寫(xiě)一章系統(tǒng)架構(gòu)設(shè)計(jì)把前后端數(shù)據(jù)交互時(shí)序圖畫(huà)出來(lái)這也是老師比較喜歡看到的。這個(gè)組合沒(méi)有任何一個(gè)環(huán)節(jié)是為了炫技全部是沖著穩(wěn)定、可控、能講清楚去的。你自己去看真實(shí)的公司項(xiàng)目很多中小型項(xiàng)目就是這套組合的變體所以做這個(gè)題目對(duì)找實(shí)習(xí)和工作也有一定幫助。3. 拿到一份完整的問(wèn)卷系統(tǒng)源碼后先花30分鐘摸清這套代碼的數(shù)據(jù)結(jié)構(gòu)不管是你自己從零寫(xiě)的還是從網(wǎng)上拿到的配套源碼第一步都不建議急著跑起來(lái)而是先建庫(kù)、看表、摸清數(shù)據(jù)流轉(zhuǎn)。我見(jiàn)過(guò)太多人栽在項(xiàng)目跑不起來(lái)上結(jié)果發(fā)現(xiàn)不是代碼的問(wèn)題而是表結(jié)構(gòu)和他本地?cái)?shù)據(jù)庫(kù)版本不兼容或者初始化數(shù)據(jù)沒(méi)導(dǎo)入全。以這份基于SpringBootMySQL的在線問(wèn)卷調(diào)查系統(tǒng)為例它的數(shù)據(jù)庫(kù)設(shè)計(jì)可以分成三塊核心區(qū)域。第一塊是用戶與權(quán)限相關(guān)表。典型的設(shè)計(jì)是sys_user表存放用戶ID、用戶名、加密后的密碼通常是MD5加鹽或者BCrypt、用戶類(lèi)型管理員還是普通用戶、創(chuàng)建時(shí)間等。有些項(xiàng)目會(huì)把角色單獨(dú)拆一張role表再用user_role關(guān)聯(lián)表做多對(duì)多但問(wèn)卷調(diào)查系統(tǒng)通常只有管理員和普通用戶兩種角色很多項(xiàng)目直接用role字段區(qū)分這也是可以接受的。第二塊是問(wèn)卷本身的結(jié)構(gòu)化數(shù)據(jù)。這里通常是三張表搭配使用問(wèn)卷表questionnaire存儲(chǔ)問(wèn)卷標(biāo)題、描述、狀態(tài)草稿/發(fā)布/關(guān)閉、創(chuàng)建人、創(chuàng)建時(shí)間、開(kāi)始時(shí)間、結(jié)束時(shí)間。題目表question屬于某份問(wèn)卷存儲(chǔ)題目?jī)?nèi)容、題型radio/checkbox/text/score、是否必填、排序號(hào)。選項(xiàng)表option屬于某道題存儲(chǔ)選項(xiàng)內(nèi)容、排序號(hào)。這三張表通過(guò)外鍵層層關(guān)聯(lián)形成一個(gè)問(wèn)卷的完整結(jié)構(gòu)。做設(shè)計(jì)的時(shí)候有個(gè)細(xì)節(jié)要注意盡量不要用物理外鍵。很多商業(yè)項(xiàng)目規(guī)范里明確要求禁用物理外鍵因?yàn)樵诟卟l(fā)插入時(shí)外鍵約束會(huì)帶來(lái)額外的鎖開(kāi)銷(xiāo)而且一旦分表分庫(kù)外鍵就完全失效。畢設(shè)項(xiàng)目里如果老師不強(qiáng)制要求建議在代碼層面維護(hù)邏輯外鍵關(guān)系即在Java代碼里自己控制關(guān)聯(lián)邏輯建表語(yǔ)句里不寫(xiě)FOREIGN KEY。如果你能在論文或答辯里說(shuō)出為什么不用物理外鍵這個(gè)點(diǎn)絕對(duì)是個(gè)加分項(xiàng)。第三塊是答卷數(shù)據(jù)。這塊是整個(gè)系統(tǒng)設(shè)計(jì)的關(guān)鍵也是區(qū)分會(huì)設(shè)計(jì)和不會(huì)設(shè)計(jì)的分水嶺。常見(jiàn)的做法有兩種。第一種是寬表設(shè)計(jì)一份答卷就一行記錄每個(gè)題目對(duì)應(yīng)一個(gè)字段比如questionnaire_id、q1_answer、q2_answer...。這種設(shè)計(jì)寫(xiě)起來(lái)爽但問(wèn)題是題目一旦變化比如刪了一道題、加了一道題表結(jié)構(gòu)就得跟著改而且空值也會(huì)比較多。第二種是行表設(shè)計(jì)也叫EAV模式核心是下面兩張表答卷主表answer_record記錄某用戶對(duì)某份問(wèn)卷的一次填寫(xiě)記錄字段包括ID、問(wèn)卷ID、用戶ID或匿名標(biāo)識(shí)、填寫(xiě)時(shí)間。答卷明細(xì)表answer_detail每次填寫(xiě)記錄對(duì)應(yīng)多行每行存儲(chǔ)一道題的答案字段包括ID、答卷ID、題目ID、選項(xiàng)ID如果是選擇類(lèi)題目、文本內(nèi)容如果是填空或主觀題。這種設(shè)計(jì)犧牲了一點(diǎn)查詢效率但換來(lái)了極大的靈活性新增一種題型你不需要改表結(jié)構(gòu)只要在代碼里做對(duì)應(yīng)的數(shù)據(jù)解析就行。對(duì)畢設(shè)來(lái)講這套設(shè)計(jì)能撐住單選題、多選題、填空題三種基本題型的存儲(chǔ)需求答辯時(shí)也更好講清楚。我建議拿到源碼后先把這三塊的表關(guān)系畫(huà)出來(lái)確認(rèn)每個(gè)字段的含義再去看代碼。因?yàn)楸斫Y(jié)構(gòu)就是后端代碼的地形圖你只要把表和代碼里的實(shí)體類(lèi)對(duì)應(yīng)上就離理解整個(gè)系統(tǒng)八九不離十了。4. 環(huán)境搭建JDK版本、MySQL8、Maven配置里最容易坑你的三個(gè)地方這個(gè)部分我直接用一個(gè)清單式的完整過(guò)程來(lái)演示每一步都是踩過(guò)坑之后換來(lái)的經(jīng)驗(yàn)。4.1 JDK環(huán)境如果你用的是SpringBoot 2.7.xJDK 8或者JDK 11都行如果是SpringBoot 3.x那必須JDK 17以上。切記JDK版本和SpringBoot版本要配套否則項(xiàng)目啟動(dòng)時(shí)會(huì)出現(xiàn)UnsupportedClassVersionError或者各種奇怪的Bean創(chuàng)建異常。檢查完JDK版本后命令行里輸入java -version確認(rèn)下很多同學(xué)環(huán)境變量配置了但沒(méi)刷新終端或者當(dāng)前終端的PATH里還有舊版本的JDK路徑就會(huì)導(dǎo)致明明安裝的是17卻顯示1.8。建議裝完JDK之后打開(kāi)一個(gè)新的終端窗口再驗(yàn)證別在配置之前的窗口里折騰。4.2 MySQL8的初始化注意事項(xiàng)現(xiàn)在大部分配套資料里的SQL腳本都是基于MySQL 5.7或8.0寫(xiě)的如果你裝的是MySQL 8.0需要注意utf8mb4字符集的問(wèn)題。建庫(kù)時(shí)建議直接執(zhí)行CREATE DATABASE IF NOT EXISTS survey DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE survey; source /your/path/survey.sql;使用source命令導(dǎo)入SQL腳本時(shí)腳本路徑不要帶中文和空格否則容易報(bào)錯(cuò)。導(dǎo)入之后用show tables;確認(rèn)表都建出來(lái)了再執(zhí)行一條select * from sys_user;看看數(shù)據(jù)是不是正常。還有一個(gè)特別容易踩的坑MySQL 8.0默認(rèn)使用caching_sha2_password認(rèn)證插件而某些老版本的JDBC驅(qū)動(dòng)比如mysql-connector-java 5.x不支持這個(gè)插件連接時(shí)會(huì)報(bào)Public Key Retrieval is not allowed。解決方案有兩個(gè)一是把JDBC驅(qū)動(dòng)換成mysql-connector-j8.0.x版本二是在數(shù)據(jù)庫(kù)連接URL上加參數(shù)allowPublicKeyRetrievaltrue。你如果用SpringBoot 2.7.x自帶的MySQL驅(qū)動(dòng)就是8.0.x版本但建議還是在application.yml的連接URL里加上這個(gè)參數(shù)有備無(wú)患。4.3 Maven的鏡像倉(cāng)庫(kù)配置Maven是個(gè)好東西但如果你直接用自己的中央倉(cāng)庫(kù)地址在國(guó)內(nèi)網(wǎng)絡(luò)環(huán)境下下載依賴會(huì)慢到懷疑人生。Maven倉(cāng)庫(kù)網(wǎng)頁(yè)版入口這個(gè)熱搜詞能進(jìn)實(shí)時(shí)趨勢(shì)說(shuō)明被Maven配置折磨過(guò)的人不止一個(gè)。解決方式是修改Maven的settings.xml文件把鏡像地址換成國(guó)內(nèi)倉(cāng)庫(kù)。在mirrors節(jié)點(diǎn)下加上mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共倉(cāng)庫(kù)/name urlhttps://maven.aliyun.com/repository/public/url /mirror這樣配置之后拉依賴的速度會(huì)快很多。需要提醒的是改完settings.xml之后在IDEA里要注意Maven的配置指向File-Settings-Build, Execution, Deployment-Build Tools-Maven確認(rèn)User settings file指向你修改過(guò)的那個(gè)settings.xml并且勾選Override。很多同學(xué)改了自己的settings.xml但I(xiàn)DEA沒(méi)生效就是因?yàn)檫@里沒(méi)選對(duì)路徑。4.4 把SpringBoot后端跑起來(lái)在確保MySQL創(chuàng)建好庫(kù)、導(dǎo)入好數(shù)據(jù)后打開(kāi)SpringBoot項(xiàng)目在src/main/resources/application.yml里檢查以下幾個(gè)配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/survey?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse username: root password: 你自己的密碼 driver-class-name: com.mysql.cj.jdbc.Driver然后啟動(dòng)項(xiàng)目??吹筋?lèi)似下面的日志輸出就說(shuō)明后端已經(jīng)起來(lái)了Tomcat started on port(s): 8080 (http) with context path / Started SurveyApplication in 5.32 seconds第一次跑通這個(gè)流程我建議你一定要手動(dòng)用Postman或者瀏覽器訪問(wèn)一下后端接口。如果接口能返回JSON數(shù)據(jù)再往下走前端這樣能避免前后端一起出問(wèn)題時(shí)不知道該排查誰(shuí)。4.5 前端Vue項(xiàng)目的啟動(dòng)Vue項(xiàng)目通常是用Vue CLIvue/cli開(kāi)發(fā)構(gòu)建的拿到項(xiàng)目后檢查有沒(méi)有node_modules目錄如果沒(méi)有在項(xiàng)目根目錄執(zhí)行npm install。這一步在國(guó)內(nèi)也可能慢可以把npm鏡像切換成淘寶鏡像npm config set registry https://registry.npmmirror.com確認(rèn)vue.config.js如果有里的代理配置把前端開(kāi)發(fā)服務(wù)器的/api開(kāi)頭的請(qǐng)求代理到后端的http://localhost:8080。執(zhí)行npm run serve默認(rèn)會(huì)在localhost:8081或其他端口上啟動(dòng)開(kāi)發(fā)服務(wù)器。瀏覽器訪問(wèn)后能看到登錄頁(yè)面就算基本跑通了。5. 核心業(yè)務(wù)的前后端協(xié)作流程從創(chuàng)建問(wèn)卷到查看統(tǒng)計(jì)代碼到底經(jīng)歷了什么跑通項(xiàng)目之后不要急著改代碼。你要做的是跟著一條完整的業(yè)務(wù)鏈路把前后端代碼過(guò)一遍。我就以管理員創(chuàng)建問(wèn)卷、用戶填寫(xiě)、管理員查看統(tǒng)計(jì)這條主線把核心邏輯拆給你看。5.1 管理員創(chuàng)建問(wèn)卷的Data Flow管理員在Vue前端填寫(xiě)問(wèn)卷標(biāo)題、添加題目題目類(lèi)型選單選題、錄入選項(xiàng)A/B/C/D、點(diǎn)擊保存。Vue頁(yè)面拿到表單數(shù)據(jù)后向后端發(fā)送請(qǐng)求this.$axios.post(/api/questionnaire/save, this.createForm)后端對(duì)應(yīng)的Controller接收到請(qǐng)求把JSON轉(zhuǎn)換成QuestionnaireCreateDTO。隨后Service層做三件事向questionnaire表插入一條問(wèn)卷記錄狀態(tài)為draft。遍歷DTO里的題目列表逐題插入question表拿到每個(gè)題目的自增ID。遍歷每道題的選項(xiàng)列表插入option表并記錄所屬的題目ID。這里有個(gè)事務(wù)的細(xì)節(jié)整個(gè)創(chuàng)建過(guò)程必須在同一個(gè)Transactional事務(wù)里完成。因?yàn)橐坏┲虚g某一步出錯(cuò)比如第3題的選項(xiàng)沒(méi)插進(jìn)去你不能讓問(wèn)卷記錄已經(jīng)落庫(kù)了否則就會(huì)出現(xiàn)有問(wèn)卷但沒(méi)題目的臟數(shù)據(jù)。類(lèi)上要加Transactional(rollbackFor Exception.class)注意不是只寫(xiě)Transactional因?yàn)镾pring默認(rèn)只是在拋出RuntimeException時(shí)才回滾如果你代碼里把異常catch住并轉(zhuǎn)成普通異常拋出事務(wù)就不會(huì)回滾那就會(huì)出大問(wèn)題。5.2 用戶填寫(xiě)問(wèn)卷的原子性保證用戶打開(kāi)一份已發(fā)布的問(wèn)卷逐題填寫(xiě)并提交。這步操作的代碼邏輯是public boolean submitAnswer(AnswerSubmitDTO dto) { // 1. 校驗(yàn)問(wèn)卷狀態(tài)是否已發(fā)布 // 2. 校驗(yàn)必填題是否都寫(xiě)了 // 3. 插入answer_record表 // 4. 批量插入answer_detail表 // 5. 更新問(wèn)卷的已填寫(xiě)數(shù)量字段 }有兩個(gè)容易在答辯時(shí)被問(wèn)到的問(wèn)題問(wèn)題一如何防止用戶重復(fù)提交最簡(jiǎn)單的方案是在表單提交時(shí)加一個(gè)冪等標(biāo)識(shí)比如前端生成一個(gè)UUID作為submissionToken每次進(jìn)入問(wèn)卷時(shí)向后端申請(qǐng)一個(gè)token提交時(shí)后端校驗(yàn)這個(gè)token是否已經(jīng)被使用過(guò)。如果只用數(shù)據(jù)庫(kù)判斷該用戶是否已填過(guò)這份問(wèn)卷一旦同一用戶開(kāi)兩個(gè)頁(yè)面會(huì)有并發(fā)問(wèn)題。如果你的項(xiàng)目里沒(méi)有做冪等可以在答辯時(shí)坦誠(chéng)說(shuō)明這個(gè)不足并給出優(yōu)化思路這也是一種加分項(xiàng)。問(wèn)題二多選和單選答案是怎么存儲(chǔ)的單選答案在answer_detail表的option_id字段里直接存選項(xiàng)ID。 多選答案option_id字段沒(méi)法存多個(gè)值有兩種處理方式。一種是在代碼里把多個(gè)選項(xiàng)ID用英文逗號(hào)拼接成字符串放進(jìn)一個(gè)字段另一種是每選一個(gè)選項(xiàng)就插一條answer_detail記錄。第一種方案查詢簡(jiǎn)單但不符合第一范式第二種方案更歸范化但代碼略復(fù)雜。不同的項(xiàng)目實(shí)現(xiàn)會(huì)有差異你看源碼的時(shí)候留意一下它用的是哪種方式。5.3 統(tǒng)計(jì)報(bào)表的SQL實(shí)現(xiàn)用戶填完問(wèn)卷后管理員端需要實(shí)時(shí)看到統(tǒng)計(jì)結(jié)果。以單選題你最喜歡的編程語(yǔ)言A. Java B. Python C. Go為例統(tǒng)計(jì)每個(gè)選項(xiàng)被選了多少次用SQL大致是SELECT opt.id AS option_id, opt.content AS option_content, COUNT(detail.id) AS answer_count FROM questionnaire q INNER JOIN question qs ON qs.questionnaire_id q.id INNER JOIN option opt ON opt.question_id qs.id LEFT JOIN answer_detail detail ON detail.option_id opt.id WHERE q.id 1 AND qs.id 1 GROUP BY opt.id, opt.content ORDER BY opt.sort_order;注意這里用的是LEFT JOIN而不是INNER JOIN因?yàn)橐坏李}里可能存在某個(gè)選項(xiàng)從未被選過(guò)的極端情況INNER JOIN會(huì)把選項(xiàng)記錄直接過(guò)濾掉從0變成查不到這在統(tǒng)計(jì)頁(yè)面上就是選項(xiàng)列表憑空少了一項(xiàng)很不友好。5.4 前端的數(shù)據(jù)可視化統(tǒng)計(jì)結(jié)果如果只是表格效果一般。稍微做得好看一點(diǎn)前端會(huì)引入ECharts來(lái)畫(huà)餅圖或柱狀圖import * as echarts from echarts; const chart echarts.init(document.getElementById(main)); chart.setOption({ tooltip: { trigger: item }, series: [ { name: 投票數(shù), type: pie, radius: 60%, data: this.statisticsList.map(item ({ name: item.optionContent, value: item.answerCount })) } ] });大部分基于Vue的問(wèn)卷系統(tǒng)都自帶了這個(gè)功能你可以在前端源碼里搜echarts看看是否已經(jīng)引入了相關(guān)依賴。如果還沒(méi)有可以讓論文里多一個(gè)可視化統(tǒng)計(jì)分析模塊的功能點(diǎn)工作量和難度都不大但內(nèi)容的完整度會(huì)明顯提升。6. 畢業(yè)論文怎么寫(xiě)才算有料一個(gè)好的體系勝過(guò)空話畢設(shè)項(xiàng)目的源碼可以跑通但論文寫(xiě)不出來(lái)或者寫(xiě)得很水同樣沒(méi)辦法順利通過(guò)。問(wèn)卷調(diào)查系統(tǒng)這個(gè)題目的論文我建議你按照下面的主體結(jié)構(gòu)來(lái)組織每個(gè)章節(jié)的寫(xiě)作邏輯都不一樣。6.1 第一章 引言重點(diǎn)在為什么做、背景是什么這一章不要空談互聯(lián)網(wǎng)時(shí)代信息爆炸這類(lèi)廢話而是要聚焦到問(wèn)卷調(diào)研本身的痛點(diǎn)傳統(tǒng)紙質(zhì)問(wèn)卷成本高、數(shù)據(jù)回收慢、統(tǒng)計(jì)容易出錯(cuò)通用的問(wèn)卷平臺(tái)比如某些在線SaaS雖然功能全面但數(shù)據(jù)存儲(chǔ)在第三方平臺(tái)上存在數(shù)據(jù)安全風(fēng)險(xiǎn)且定制化能力受限。因此從零開(kāi)發(fā)一套可私有化部署的問(wèn)卷系統(tǒng)對(duì)中小企業(yè)或高校內(nèi)部調(diào)研具有一定現(xiàn)實(shí)意義。6.2 第二章 相關(guān)技術(shù)介紹每個(gè)技術(shù)都交代清楚在這套系統(tǒng)里承擔(dān)什么角色寫(xiě)技術(shù)章節(jié)時(shí)別把每個(gè)技術(shù)都抄一遍百度百科。重點(diǎn)寫(xiě)清楚它們?cè)谀愕南到y(tǒng)里干了什么SpringBoot提供了自動(dòng)配置和快速構(gòu)建RESTful API的能力在本系統(tǒng)中用于實(shí)現(xiàn)用戶管理、問(wèn)卷管理、答卷管理等核心業(yè)務(wù)接口。MySQL保存問(wèn)卷、題目、選項(xiàng)、答卷和用戶數(shù)據(jù)利用InnoDB引擎的事務(wù)機(jī)制保證創(chuàng)建問(wèn)卷和提交答卷的原子性。Vue實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)的SPA單頁(yè)面應(yīng)用配合Element UI組件庫(kù)完成登錄、問(wèn)卷編輯器和統(tǒng)計(jì)展示頁(yè)面。Maven管理項(xiàng)目依賴統(tǒng)一構(gòu)建流程配合spring-boot-maven-plugin將后端項(xiàng)目打包為可執(zhí)行Jar。6.3 第三章 系統(tǒng)需求分析用例圖配合文字描述需求分析最忌諱的是只列功能列表一定要有角色場(chǎng)景的維度。問(wèn)卷系統(tǒng)至少有管理員和普通用戶兩個(gè)角色你要描述清楚每個(gè)角色有哪些用例。這一章建議畫(huà)兩到三個(gè)UML用例圖把登錄注冊(cè)問(wèn)卷管理填寫(xiě)問(wèn)卷查看統(tǒng)計(jì)結(jié)果等核心用例全部覆蓋到。6.4 第四章 系統(tǒng)設(shè)計(jì)架構(gòu)圖、功能模塊圖、數(shù)據(jù)庫(kù)ER圖三件套這一章是最重要的也是老師最?lèi)?ài)挑刺的地方。你要用架構(gòu)圖把前后端分離的結(jié)構(gòu)畫(huà)清楚Vue前端通過(guò)Axios調(diào)用后端接口后端Controller層接收請(qǐng)求、Service層處理業(yè)務(wù)邏輯、Mapper層操作MySQL數(shù)據(jù)庫(kù)。數(shù)據(jù)庫(kù)設(shè)計(jì)部分必須給出ER圖并把我在第3節(jié)提到的每張表的核心字段列出來(lái)并說(shuō)明含義。6.5 第五章 系統(tǒng)實(shí)現(xiàn)代碼截圖配合關(guān)鍵邏輯描述展示關(guān)鍵功能的時(shí)候不要大段貼代碼要配合截圖和文字說(shuō)明。比如實(shí)現(xiàn)問(wèn)卷編輯器時(shí)選Element UI的el-form和el-radio-group就描述清楚前端根據(jù)題型的枚舉值動(dòng)態(tài)渲染對(duì)應(yīng)的輸入組件提交時(shí)將題目列表序列化后傳給后端即可。如果你在某個(gè)地方處理了一個(gè)異常或一個(gè)邊界條件一定要寫(xiě)出來(lái)這是論文的加分項(xiàng)。6.6 第六章 系統(tǒng)測(cè)試用黑盒和接口測(cè)試雙管齊下這一章至少要覆蓋操作正確性測(cè)試數(shù)據(jù)庫(kù)連接是否正常、頁(yè)面能否正常加載、CRUD功能是否符合預(yù)期。核心功能測(cè)試用例表寫(xiě)清測(cè)試數(shù)據(jù)、測(cè)試步驟、預(yù)期結(jié)果和實(shí)際結(jié)果。接口測(cè)試用Postman對(duì)后端RESTful API做驗(yàn)證測(cè)試正常參數(shù)、異常參數(shù)缺失字段、超長(zhǎng)字符串返回的狀態(tài)碼和響應(yīng)體。7. 排查鏈路實(shí)錄項(xiàng)目跑不起來(lái)時(shí)按這個(gè)順序定位問(wèn)題能省一半時(shí)間最后分享一下我在幫學(xué)弟調(diào)試過(guò)程中積累的一個(gè)排查鏈路。你如果照著這個(gè)順序一步步走完90%的啟動(dòng)問(wèn)題都能定位。7.1 第一步核對(duì)MySQL的庫(kù)和表是否真的初始化成功項(xiàng)目起不來(lái)的原因里數(shù)據(jù)庫(kù)異常占了大頭。先在命令行登錄MySQL執(zhí)行SHOW DATABASES; USE survey; SHOW TABLES;看看survey庫(kù)里有沒(méi)有表。常見(jiàn)問(wèn)題有三類(lèi)建庫(kù)了但沒(méi)導(dǎo)入SQL腳本表是空的。SQL腳本導(dǎo)入時(shí)報(bào)錯(cuò)比如編碼問(wèn)題導(dǎo)致部分表缺失。項(xiàng)目application.yml里的spring.datasource.url指向了錯(cuò)誤的庫(kù)名。如果數(shù)據(jù)庫(kù)正常但項(xiàng)目還是報(bào)Access denied for user rootlocalhost那基本是密碼錯(cuò)了或者權(quán)限沒(méi)給夠。如果是密碼錯(cuò)誤就修改配置如果使用云服務(wù)器MySQL記得確認(rèn)bind-address允許你的IP訪問(wèn)。7.2 第二步確認(rèn)Maven依賴下載完整SpringBoot項(xiàng)目啟動(dòng)時(shí)報(bào)ClassNotFoundException或NoClassDefFoundError通常是某些Jar包沒(méi)被Maven正確拉取。解決方式是先執(zhí)行mvn clean再執(zhí)行mvn install -DskipTests把項(xiàng)目重新編譯一遍。如果還有缺失切到Maven倉(cāng)庫(kù)本地目錄默認(rèn)在~/.m2/repository看看對(duì)應(yīng)坐標(biāo)的目錄里是不是只有.lastUpdated文件如果是說(shuō)明下載失敗了把該目錄刪掉重新mvn install。7.3 第三步看完整的后端啟動(dòng)日志頁(yè)面訪問(wèn)不了時(shí)你需要看后端接口返回了什么。打開(kāi)瀏覽器開(kāi)發(fā)者工具F12 - Network看一下失敗的請(qǐng)求返回的狀態(tài)碼。具體表現(xiàn)對(duì)應(yīng)的常見(jiàn)原因如下現(xiàn)象大概率原因檢查方向請(qǐng)求返回404后端沒(méi)這個(gè)路徑或Controller的RequestMapping和接口Url不一致檢查Controller類(lèi)上的RequestMapping和Vue里實(shí)際請(qǐng)求的路徑請(qǐng)求返回403跨域或認(rèn)證權(quán)限問(wèn)題檢查CORS配置、攔截器是否對(duì)OPTIONS請(qǐng)求放行請(qǐng)求返回500后端代碼異常最常見(jiàn)是SQL或空指針看后端控制臺(tái)堆棧日志請(qǐng)求一直P(pán)ending前后端端口不通或代理配置錯(cuò)誤檢查vue.config.js的proxy配置單獨(dú)用Postman確認(rèn)接口能通重啟后端口被占用上一次進(jìn)程沒(méi)關(guān)掉Windows用netstat -ano | findstr 8080找到PID并結(jié)束進(jìn)程7.4 第四步前端資源加載不出來(lái)怎么辦前端能打開(kāi)登錄頁(yè)面但頁(yè)面樣式是亂的或者某個(gè)圖標(biāo)加載不出來(lái)多半是靜態(tài)資源路徑問(wèn)題。Vue項(xiàng)目用public目錄存放靜態(tài)資源在引用路徑上建議使用絕對(duì)路徑如/img/logo.png而不是相對(duì)路徑./img/logo.png否則部署到子路徑環(huán)境下會(huì)出問(wèn)題。如果Vue頁(yè)面打開(kāi)后一片空白控制臺(tái)報(bào)各種關(guān)于module的錯(cuò)通常是依賴裝得不完整直接刪除node_modules目錄重新npm install。7.5 第五步遇到這類(lèi)問(wèn)題先停一下自己思考很多同學(xué)遇到報(bào)錯(cuò)的第一反應(yīng)是直接把報(bào)錯(cuò)信息復(fù)制到搜索引擎或者截圖扔進(jìn)群里。這沒(méi)錯(cuò)但更高效的方式是先看報(bào)錯(cuò)信息里的前五行——尤其是Exception類(lèi)型和描述性文字。比如你看到j(luò)ava.sql.SQLSyntaxErrorException那十有八九是SQL寫(xiě)錯(cuò)了看到NullPointerException那就去檢查某個(gè)對(duì)象是不是沒(méi)有被賦值。看懂報(bào)錯(cuò)信息的能力是編程基本功里很關(guān)鍵的一項(xiàng)答辯時(shí)如果被問(wèn)到你是怎么排查這個(gè)問(wèn)題的你能把這個(gè)過(guò)程講出來(lái)會(huì)非常加分。8. 一些可以繼續(xù)深化的小方向如果你做完了基礎(chǔ)功能還有時(shí)間可以考慮在兩三個(gè)方向上做一點(diǎn)小優(yōu)化它們不只是加分項(xiàng)也能讓你在答辯時(shí)更有談資。防重復(fù)提交的冪等設(shè)計(jì)。正如前面提到的加一個(gè)submissionToken讓每份問(wèn)卷在用戶手里只能提交一次。這個(gè)優(yōu)化實(shí)現(xiàn)簡(jiǎn)單但能體現(xiàn)你對(duì)并發(fā)和數(shù)據(jù)一致性的理解。問(wèn)卷的回收率統(tǒng)計(jì)。問(wèn)卷系統(tǒng)不只是誰(shuí)填了、填了什么管理員也很關(guān)心多少人看到了問(wèn)卷但沒(méi)填。你可以加一個(gè)統(tǒng)計(jì)字段記錄問(wèn)卷頁(yè)面的訪問(wèn)次數(shù)和實(shí)際提交次數(shù)算出轉(zhuǎn)化率。這個(gè)功能需要的不過(guò)是給訪問(wèn)接口加一條埋點(diǎn)日志但業(yè)務(wù)價(jià)值很明顯。數(shù)據(jù)導(dǎo)出功能。把答卷明細(xì)導(dǎo)出成Excel文件使用EasyExcel或者Apache POI。管理員填完問(wèn)卷后能一鍵導(dǎo)出數(shù)據(jù)做進(jìn)一步分析是非常實(shí)用的功能。這個(gè)功能也很適合寫(xiě)在論文的功能模塊里。基于角色的訪問(wèn)控制優(yōu)化?,F(xiàn)在的系統(tǒng)可能只是簡(jiǎn)單區(qū)分管理員和普通用戶。如果能把權(quán)限做到更細(xì)粒度比如問(wèn)卷管理員只能管理自己的問(wèn)卷不能查看別人的問(wèn)卷那就是一個(gè)合格的RBAC基于角色的訪問(wèn)控制模型了講起來(lái)也能展示你對(duì)權(quán)限設(shè)計(jì)的思考深度。每個(gè)方向的工作量都不大兩三天之內(nèi)就能完成但對(duì)系統(tǒng)的完整度和論文的豐滿度幫助很大。9. 我個(gè)人在實(shí)際操作中的幾點(diǎn)體會(huì)做到這里整個(gè)項(xiàng)目已經(jīng)從能跑到了能講的階段。我?guī)У膶W(xué)弟最后答辯前我讓他干了一件事自己對(duì)著項(xiàng)目把每個(gè)接口對(duì)應(yīng)的表、每個(gè)表對(duì)應(yīng)的字段、每個(gè)字段對(duì)應(yīng)的含義口頭講一遍。講不清楚的地方再回去看代碼。這個(gè)過(guò)程相當(dāng)枯燥但非常有價(jià)值。因?yàn)榇疝q時(shí)老師不一定看你的系統(tǒng)用得順不順暢更多的是看你對(duì)自己的代碼、自己的設(shè)計(jì)熟不熟悉。另外如果你的源碼是從網(wǎng)上拿來(lái)的建議不要原封不動(dòng)地提交。至少做一點(diǎn)自己的改動(dòng)和重構(gòu)比如把一個(gè)工具類(lèi)提取出來(lái)、優(yōu)化一個(gè)查詢SQL、加一個(gè)自定義異常處理類(lèi)。這些改動(dòng)在論文里都有地方寫(xiě)也在一定程度上避免雷同帶來(lái)的被動(dòng)。這個(gè)題目的上限不算高但下限也低不到哪去關(guān)鍵看你怎么理解和組織。把表結(jié)構(gòu)講透了把前后端數(shù)據(jù)流梳理清了把答辯可能問(wèn)的問(wèn)題提前想好答案這個(gè)項(xiàng)目就能穩(wěn)穩(wěn)地拿下一個(gè)不錯(cuò)的成績(jī)。如果你在實(shí)操中碰到了具體的報(bào)錯(cuò)歡迎帶著具體的日志信息來(lái)聊根據(jù)具體的報(bào)錯(cuò)去定位往往比通篇來(lái)看要快得多。本文還有配套的精品資源點(diǎn)擊獲取