查系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn))
簡介一份基于Spring Boot與Vue的在線問卷調(diào)查系統(tǒng)畢業(yè)設(shè)計(jì)論文文檔面向計(jì)算機(jī)相關(guān)專業(yè)畢業(yè)生及需要完成類似課題的開發(fā)者提供從需求分析、系統(tǒng)設(shè)計(jì)到編碼實(shí)現(xiàn)、測試評估的完整論述。論文核心解決問卷調(diào)查信息管理不規(guī)范、效率低等問題涵蓋問卷管理、用戶管理、題目管理、問卷調(diào)查管理、新聞資訊管理等模塊并使用MySQL數(shù)據(jù)庫、Java語言及Spring Boot框架完成技術(shù)落地。資源為1個(gè)doc文檔容量約4.04MB包含完整論文正文方便查閱、修改與二次編輯。目前已有198人瀏覽學(xué)習(xí)適合用于畢業(yè)設(shè)計(jì)參考、論文結(jié)構(gòu)梳理或技術(shù)方案借鑒。文檔內(nèi)含系統(tǒng)架構(gòu)說明、數(shù)據(jù)庫設(shè)計(jì)思路、后端API與前端頁面開發(fā)要點(diǎn)還能了解系統(tǒng)測試與穩(wěn)定性評估方法為獨(dú)立完成類似項(xiàng)目提供有力支撐。1. 在線問卷調(diào)查系統(tǒng)從畢業(yè)設(shè)計(jì)到可落地的 Spring Boot Vue 全棧項(xiàng)目問卷系統(tǒng)的難點(diǎn)從來不在「能填」而在「怎么設(shè)計(jì)題目、怎么控制發(fā)布狀態(tài)、怎么保證一次作答不重復(fù)提交」。這套基于 Spring Boot Vue 的在線問卷調(diào)查系統(tǒng)覆蓋了問卷管理、題目管理、用戶管理、問卷調(diào)查記錄和新聞資訊模塊是一個(gè)完整的前后端分離案例。后端用 Spring Boot 提供 REST API前端用 Vue 做頁面交互數(shù)據(jù)落在 MySQL適合正在做 Java 方向畢業(yè)設(shè)計(jì)的人也適合想快速搭一套內(nèi)部調(diào)研工具的開發(fā)者。它的表結(jié)構(gòu)設(shè)計(jì)——問卷表、題目表、答卷記錄表——足夠簡潔稍加擴(kuò)展就能接進(jìn)真實(shí)業(yè)務(wù)。下面從數(shù)據(jù)模型開始拆解這套系統(tǒng)的實(shí)現(xiàn)路徑。2. 數(shù)據(jù)模型先行問卷、題目與答卷記錄的表結(jié)構(gòu)設(shè)計(jì)在線問卷調(diào)查系統(tǒng)的核心不在頁面而在數(shù)據(jù)庫里那幾張表的關(guān)系。問卷與題目是一對多問卷與答卷記錄是一對多用戶與答卷記錄也是一對多。這三層關(guān)系理順了后面寫接口和頁面都會順暢很多。2.1 問卷表狀態(tài)、時(shí)長與結(jié)束語的設(shè)計(jì)問卷表對應(yīng)實(shí)體是「問卷」字段在論文中給出了明確設(shè)計(jì)建表 SQL 如下CREATE TABLE exampaper ( id int(20) NOT NULL AUTO_INCREMENT COMMENT 主鍵, exampaper_name varchar(200) NOT NULL COMMENT 問卷名稱, exampaper_date int(11) NOT NULL COMMENT 時(shí)長分鐘, exampaper_jieshuyu varchar(255) DEFAULT NULL COMMENT 結(jié)束語, exampaper_state tinyint(1) DEFAULT 0 COMMENT 問卷狀態(tài)0-草稿 1-發(fā)布 2-關(guān)閉, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 創(chuàng)建時(shí)間, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT問卷表;字段里值得注意的有兩個(gè)。exampaper_state用 tinyint 而不是 varchar 存狀態(tài)是為了方便做狀態(tài)流轉(zhuǎn)判斷后端只比對數(shù)值即可exampaper_jieshuyu允許為空因?yàn)閱柧韯?chuàng)建初期不需要立刻填寫結(jié)束語可以發(fā)布前再補(bǔ)。exampaper_date設(shè)定的時(shí)長不是計(jì)時(shí)上限而是提醒閾值——用戶進(jìn)入問卷后前端根據(jù)這個(gè)值啟動倒計(jì)時(shí)提醒但不強(qiáng)制交卷避免數(shù)據(jù)丟失。create_time用數(shù)據(jù)庫默認(rèn)值生成后端插入時(shí)不需要手動賦值。2.2 題目表與問卷調(diào)查記錄表一對多關(guān)系落到字段上題目表在設(shè)計(jì)上必須攜帶所屬問卷的 ID否則無法建立關(guān)聯(lián)。問卷調(diào)查記錄表則是每次作答的「流水賬」記錄誰在什么時(shí)間答了哪份問卷。兩張表的設(shè)計(jì)如下CREATE TABLE exampaper_question ( id int(20) NOT NULL AUTO_INCREMENT COMMENT 題目ID, exampaper_id int(20) NOT NULL COMMENT 所屬問卷ID, question_name varchar(255) NOT NULL COMMENT 試題名稱, question_type tinyint(1) DEFAULT 1 COMMENT 試題類型1-單選 2-多選 3-填空, question_option text COMMENT 選項(xiàng)內(nèi)容用JSON數(shù)組存儲, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_exampaper_id (exampaper_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT題目表; CREATE TABLE exampaper_record ( id int(20) NOT NULL AUTO_INCREMENT COMMENT 問卷調(diào)查記錄ID, record_no varchar(32) DEFAULT NULL COMMENT 問卷調(diào)查編號, user_id int(20) NOT NULL COMMENT 問卷調(diào)查用戶ID, exampaper_id int(20) NOT NULL COMMENT 所屬問卷ID, answer_content text COMMENT 答卷內(nèi)容JSON格式存儲, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT問卷調(diào)查記錄表;題目的question_option用 text 存 JSON 數(shù)組比單獨(dú)建一張選項(xiàng)表更省事。問卷系統(tǒng)的題目選項(xiàng)一般是固定幾個(gè)文本不需要對選項(xiàng)做單獨(dú)檢索或統(tǒng)計(jì)JSON 存儲能減少一次關(guān)聯(lián)查詢。答題記錄表的record_no是業(yè)務(wù)編號格式可以定為「問卷ID 用戶ID 時(shí)間戳」用于前端展示查詢answer_content存放用戶提交的答案映射格式為{題目ID: 答答案}這樣無論題目怎么增刪記錄結(jié)構(gòu)都不用變。2.3 用戶表最小集認(rèn)證所需字段與擴(kuò)展邊界用戶表在論文實(shí)體屬性中給出了較多字段——賬戶、密碼、姓名、性別、手機(jī)號、照片、身份證號。實(shí)際做系統(tǒng)時(shí)身份證號這類敏感字段能不放就不放尤其畢設(shè)階段沒必要引入額外合規(guī)負(fù)擔(dān)。建議保留核心字段CREATE TABLE sys_user ( id int(20) NOT NULL AUTO_INCREMENT COMMENT 用戶ID, username varchar(50) NOT NULL COMMENT 賬戶, password varchar(100) NOT NULL COMMENT 密碼BCrypt加密, real_name varchar(50) DEFAULT NULL COMMENT 用戶姓名, sex varchar(10) DEFAULT NULL COMMENT 性別, phone varchar(20) DEFAULT NULL COMMENT 手機(jī)號, avatar varchar(255) DEFAULT NULL COMMENT 照片, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用戶表;密碼字段長度設(shè)為 100是因?yàn)?Spring Security 的 BCrypt 加密結(jié)果長度約為 60 字符如果按直覺設(shè)成 varchar(50)加密后寫入會報(bào)數(shù)據(jù)過長錯(cuò)誤。username加唯一索引這是登錄查詢的命脈字段創(chuàng)建時(shí)就要預(yù)防重復(fù)注冊。3. Spring Boot 后端實(shí)現(xiàn)Controller、Service 與 MyBatis 的工程落地后端部分的核心不是代碼量而是三層結(jié)構(gòu)的清晰度。Controller 只接收參數(shù)和返回結(jié)果Service 處理業(yè)務(wù)邏輯Mapper 層負(fù)責(zé) SQL 操作。以問卷管理模塊為例把這套結(jié)構(gòu)完整走一遍。3.1 問卷管理接口分頁查詢與狀態(tài)過濾問卷列表頁最常見的需求是分頁展示并支持按狀態(tài)篩選。Controller 層代碼RestController RequestMapping(/api/exampaper) public class ExampaperController { Autowired private ExampaperService exampaperService; GetMapping(/page) public Result page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String exampaperName, RequestParam(required false) Integer exampaperState) { PageInfoExampaper pageInfo exampaperService.findPage(pageNum, pageSize, exampaperName, exampaperState); return Result.success(pageInfo); } }Service 層對應(yīng)實(shí)現(xiàn)Service public class ExampaperServiceImpl implements ExampaperService { Autowired private ExampaperMapper exampaperMapper; Override public PageInfoExampaper findPage(Integer pageNum, Integer pageSize, String exampaperName, Integer exampaperState) { PageHelper.startPage(pageNum, pageSize); ExampaperQuery query new ExampaperQuery(); query.setExampaperName(exampaperName); query.setExampaperState(exampaperState); ListExampaper list exampaperMapper.selectByCondition(query); return new PageInfo(list); } }PageHelper.startPage(pageNum, pageSize)是 MyBatis 分頁插件的入口寫在這行之后的第一個(gè)查詢會自動追加 limit 語句。這里有個(gè)常見的坑startPage 必須緊跟查詢語句中間不能插入其他查詢操作否則分頁會作用到錯(cuò)誤的 SQL 上。exampaperName和exampaperState都允許為空查詢條件里要做動態(tài) SQL 判斷避免空值參與拼接導(dǎo)致查不到數(shù)據(jù)。3.2 新增與編輯參數(shù)校驗(yàn)和狀態(tài)判斷創(chuàng)建問卷時(shí)只保存名稱、時(shí)長和狀態(tài)字段題目先不處理。這里關(guān)鍵在于狀態(tài)機(jī)的約束——只有處于草稿狀態(tài)的問卷才能編輯題目和基礎(chǔ)信息已發(fā)布的問卷只允許關(guān)閉不允許再改結(jié)構(gòu)。PostMapping(/save) public Result save(RequestBody Exampaper exampaper) { if (exampaper.getId() null) { exampaper.setExampaperState(0); exampaperMapper.insert(exampaper); } else { Exampaper dbPaper exampaperMapper.selectById(exampaper.getId()); if (dbPaper.getExampaperState() ! 0) { return Result.error(當(dāng)前狀態(tài)不允許編輯); } exampaperMapper.updateById(exampaper); } return Result.success(); }新增和編輯共用一個(gè)接口通過id是否為空來區(qū)分。如果前端傳入的 id 和數(shù)據(jù)庫中的狀態(tài)不一致——比如數(shù)據(jù)庫里已經(jīng)是發(fā)布狀態(tài)但請求試圖修改問卷名稱——后端必須攔截只靠前端按鈕隱藏沒有任何意義。3.3 題目批量保存事務(wù)與級聯(lián)刪除編輯問卷題目時(shí)常見的做法是前端一次提交全部題目后端先刪后插。先按問卷 ID 刪除舊題目再批量插入新題目。這個(gè)操作必須加Transactional否則刪除成功但插入失敗時(shí)題庫會變成空表對用戶來說等于問卷的題全丟了。Transactional(rollbackFor Exception.class) public void saveQuestions(Long exampaperId, ListQuestion questions) { exampaperQuestionMapper.deleteByExampaperId(exampaperId); for (Question q : questions) { q.setExampaperId(exampaperId); exampaperQuestionMapper.insert(q); } }rollbackFor Exception.class指定了異常即回滾不加這個(gè)參數(shù)的話Spring 默認(rèn)只在運(yùn)行時(shí)異常時(shí)回滾檢查異常不會觸發(fā)回滾數(shù)據(jù)就可能處在不一致狀態(tài)。批量插入也可以在 Mapper 里用 foreach 實(shí)現(xiàn)減少數(shù)據(jù)庫連接往返次數(shù)。3.4 登錄與權(quán)限攔截器判斷管理員與普通用戶系統(tǒng)分了管理員和普通用戶兩個(gè)角色最簡單的實(shí)現(xiàn)方式是攔截器 角色標(biāo)記位不需要把 Spring Security 全套引入畢設(shè)或小型系統(tǒng)這樣足夠清晰Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; } HttpSession session request.getSession(); User loginUser (User) session.getAttribute(loginUser); if (loginUser null) { response.setStatus(401); return false; } return true; } }Controller 中通過request.getSession().getAttribute(loginUser)判斷當(dāng)前用戶角色管理員接口再校驗(yàn)user.getRole()是否為管理員。這種方案的取舍是非登錄用戶會被統(tǒng)一攔截到 401前端 axios 響應(yīng)攔截器里捕獲 401 后跳轉(zhuǎn)登錄頁但登錄頁本身要放行否則會死循環(huán)。4. Vue 前端實(shí)現(xiàn)問卷列表到答題頁的路由與狀態(tài)管理前端的核心頁面是問卷列表、答題頁面和答卷記錄查看。Vue 2 的 Options API 寫法在這類項(xiàng)目里最常用代碼結(jié)構(gòu)直觀生態(tài)資料也多網(wǎng)上搜到的組件和插件基本都兼容。4.1 問卷列表頁axios 請求與分頁渲染問卷列表頁根據(jù)登錄角色的不同展示不同功能——管理員看到的是「編輯」「發(fā)布」「關(guān)閉」操作按鈕普通用戶看到的是「參與答題」。頁面初始化時(shí)調(diào)用分頁接口export default { data() { return { pageNum: 1, pageSize: 10, total: 0, exampaperList: [], exampaperName: , exampaperState: undefined }; }, created() { this.loadPage(); }, methods: { async loadPage() { const res await axios.get(/api/exampaper/page, { params: { pageNum: this.pageNum, pageSize: this.pageSize, exampaperName: this.exampaperName, exampaperState: this.exampaperState } }); this.exampaperList res.data.data.list; this.total res.data.data.total; } } };params傳參會自動進(jìn)行 URL 編碼不需要手動拼接查詢字符串。created鉤子里調(diào)用加載方法確保頁面渲染前數(shù)據(jù)已經(jīng)就緒。分頁組件切換頁碼時(shí)把pageNum改成目標(biāo)值并再次調(diào)用loadPage()。注意不要在data里把exampaperState定義為空字符串否則請求參數(shù)會帶上exampaperState后端接收時(shí)可能因類型轉(zhuǎn)換報(bào)錯(cuò)。4.2 答題頁動態(tài)渲染根據(jù)題目類型加載組件答題頁拿到問卷 ID 后調(diào)用題目查詢接口獲取題目列表。題目類型是單選、多選、填空三種前端使用v-if判斷組件類型template div v-for(question, index) in questionList :keyquestion.id div classquestion-title{{ index 1 }}. {{ question.questionName }}/div el-radio-group v-ifquestion.questionType 1 v-modelanswers[question.id] el-radio v-foropt in question.optionList :keyopt :labelopt{{ opt }}/el-radio /el-radio-group el-checkbox-group v-else-ifquestion.questionType 2 v-modelanswers[question.id] el-checkbox v-foropt in question.optionList :keyopt :labelopt{{ opt }}/el-checkbox /el-checkbox-group el-input v-else-ifquestion.questionType 3 v-modelanswers[question.id] typetextarea placeholder請輸入你的回答/el-input /div /templateanswers是用題目 id 做 key 的對象answers {}初始化運(yùn)行時(shí)根據(jù)題目 id 動態(tài)添加屬性。這樣設(shè)計(jì)的好處是提交時(shí)直接把整個(gè)answers對象序列化成 JSON后端存進(jìn)answer_content字段不需要前端額外組裝。4.3 答題時(shí)長控制與提交倒計(jì)時(shí)與防重復(fù)時(shí)長提醒放在問卷頁的路由守衛(wèi)里beforeRouteLeave(to, from, next) { if (this.timer) { clearInterval(this.timer); this.timer null; } next(); }進(jìn)入問卷時(shí)讀取exampaperDate字段啟動一個(gè) setInterval 倒計(jì)時(shí)頁面離開時(shí)清除定時(shí)器避免內(nèi)存泄漏。提交時(shí)把問卷 ID 和答案對象一起傳給后端后端檢查該用戶是否有這條問卷的已提交記錄如果存在則拒絕保存并提示「請勿重復(fù)提交」。5. 發(fā)布前驗(yàn)證用測試用例和服務(wù)狀態(tài)機(jī)守住問卷的可用性系統(tǒng)寫完到交付之間最有價(jià)值的驗(yàn)證方式不是對著頁面點(diǎn)點(diǎn)點(diǎn)而是把關(guān)鍵流程整理成可執(zhí)行的測試用例同時(shí)把問卷的狀態(tài)流轉(zhuǎn)邏輯明確到代碼里防止后面接手的人把狀態(tài)改亂。5.1 測試場景設(shè)計(jì)登錄、答題、結(jié)果校驗(yàn)下面是一組覆蓋率較高的測試用例表按照這個(gè)順序去做基本能跑通日常路徑用例編號測試場景操作步驟預(yù)期結(jié)果TC-01管理員登錄輸入正確賬號密碼跳轉(zhuǎn)后臺首頁顯示問卷管理菜單TC-02管理員創(chuàng)建問卷填寫名稱及時(shí)長點(diǎn)擊保存列表出現(xiàn)「草稿」?fàn)顟B(tài)的新問卷TC-03管理員編輯題目進(jìn)入草稿問卷添加單選/多選/填空各一題題目數(shù)顯示為 3重復(fù)進(jìn)入后數(shù)據(jù)仍在TC-04管理員發(fā)布問卷點(diǎn)擊發(fā)布按鈕用戶端問卷列表可見該問卷TC-05用戶提交問卷填寫全部題目后點(diǎn)提交跳轉(zhuǎn)完成頁顯示結(jié)束語「感謝參與」TC-06用戶重復(fù)提交再次進(jìn)入該問卷提交答案后端返回「請勿重復(fù)提交」不寫入新記錄TC-07用戶查看記錄進(jìn)入「問卷調(diào)查記錄」菜單展示歷史提交時(shí)間及問卷名稱測試中要注意「編輯題目后發(fā)布」的順序問題。發(fā)布完成后再去修改題目用戶端正在作答的問卷就會出現(xiàn)題目錯(cuò)位——用戶已經(jīng)答了第 1 題后端數(shù)據(jù)庫中第 1 題的名稱變了但答案 ID 對應(yīng)的還是舊題目。這就是為什么要在后端鎖死編輯權(quán)限只允許修改草稿狀態(tài)的問卷。壓測場景下問卷通常不要求極高的并發(fā)如果預(yù)計(jì)參與人數(shù)破千數(shù)據(jù)庫索引的重點(diǎn)放在exampaper_record表的exampaper_id和user_id聯(lián)合索引上防止重復(fù)提交校驗(yàn)的查詢拖慢寫入。5.2 狀態(tài)機(jī)的邊界處理草稿、發(fā)布到關(guān)閉的生命周期問卷狀態(tài)不能只是「改個(gè)數(shù)字」要設(shè)計(jì)成不可逆的狀態(tài)流轉(zhuǎn)。從草稿到發(fā)布允許撤回草稿但保留原答案從發(fā)布到關(guān)閉拒絕編輯和刪除只保留查看統(tǒng)計(jì)的權(quán)限。用枚舉管理狀態(tài)字段的語義public enum ExampaperState { DRAFT(0, 草稿), PUBLISHED(1, 發(fā)布), CLOSED(2, 關(guān)閉); private final int code; private final String desc; ExampaperState(int code, String desc) { this.code code; this.desc desc; } }實(shí)際開發(fā)時(shí)把枚舉替換成「狀態(tài)常量類」也能運(yùn)行但枚舉的好處是編譯期就能發(fā)現(xiàn)狀態(tài)值寫錯(cuò)的問題。關(guān)閉問卷后用戶端列表不再展示「參與答題」入口已提交的答卷保留在記錄表中管理員仍然能查看結(jié)果。這套生命周期設(shè)計(jì)解決了在線問卷最容易出現(xiàn)的「已經(jīng)收集了數(shù)據(jù)卻不小心被改壞」的故障。寫完后用 TC-04 到 TC-07 跑一遍再手動驗(yàn)證一下「發(fā)布后試圖編輯」的攔截是否正確這份系統(tǒng)就可以從畢設(shè)演示平滑過渡到小范圍的正式使用了。本文還有配套的精品資源點(diǎn)擊獲取