戰(zhàn):美食互動(dòng)平臺(tái)從設(shè)計(jì)到部署全解析)
先說點(diǎn)實(shí)在的這個(gè)標(biāo)題里頭的東西我基本都親手折騰過一遍。拿java vue SpringBoot組合做美食烹飪互動(dòng)平臺(tái)算是一個(gè)非常標(biāo)準(zhǔn)的全棧實(shí)戰(zhàn)項(xiàng)目既能覆蓋SSM到SpringBoot的過渡知識(shí)又能把前端工程化、數(shù)據(jù)庫(kù)設(shè)計(jì)、接口聯(lián)調(diào)、部署上線這些職場(chǎng)里天天要用的事全部串起來。市面上類似的課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)數(shù)量不少但很多都停留在“能跑起來”的水平代碼結(jié)構(gòu)亂、數(shù)據(jù)庫(kù)設(shè)計(jì)粗糙、答辯一問就卡殼。這篇博文就按一個(gè)完整的、可以拿去交作業(yè)也可以拿來面試講的項(xiàng)目來拆從表結(jié)構(gòu)到接口、從路由到部署、從答辯問到避坑一步不落。1. 美食互動(dòng)平臺(tái)的核心需求與整體設(shè)計(jì)1.1 這個(gè)項(xiàng)目到底在解決什么問題美食烹飪互動(dòng)平臺(tái)聽名字就知道不是普通的菜譜展示站。它要解決的核心問題是一個(gè)想學(xué)做菜的人如何快速找到適合自己的菜譜并且能跟發(fā)布菜譜的人以及其他學(xué)習(xí)者產(chǎn)生互動(dòng)。所以業(yè)務(wù)上至少要拆出三塊內(nèi)容側(cè)菜譜的發(fā)布、瀏覽、檢索、分類、詳情展示。這是平臺(tái)的底座沒有內(nèi)容一切互動(dòng)都是空談。用戶側(cè)注冊(cè)、登錄、個(gè)人信息維護(hù)、我發(fā)布的菜譜、我的收藏。這部分解決“誰在玩”的問題?;?dòng)側(cè)收藏、點(diǎn)贊、評(píng)論、瀏覽計(jì)數(shù)。這是“互動(dòng)”二字的直接體現(xiàn)也是答辯時(shí)最能講出亮點(diǎn)的模塊。這三塊對(duì)應(yīng)的就是后端的用戶模塊、菜譜模塊、互動(dòng)模塊前端對(duì)應(yīng)的是首頁(yè)、菜譜列表頁(yè)、詳情頁(yè)、發(fā)布頁(yè)、個(gè)人中心。整個(gè)項(xiàng)目把前后端分離的常規(guī)打法練了一遍又沒有刻意堆砌復(fù)雜業(yè)務(wù)難度剛好卡在一個(gè)大四學(xué)生跳一跳能夠到的位置。1.2 技術(shù)選型為什么是java vue SpringBoot這套組合在近幾年的課程設(shè)計(jì)和畢業(yè)設(shè)計(jì)里幾乎成了標(biāo)配不是沒有原因的。后端用SpringBoot最大好處是約定優(yōu)于配置。以前用Spring MVC那一套光XML配置文件就能寫好幾頁(yè)現(xiàn)在一個(gè)啟動(dòng)類加幾個(gè)注解就能把Web環(huán)境拉起來。項(xiàng)目里再用Spring Boot MyBatis-Plus操作數(shù)據(jù)庫(kù)CRUD可以省掉大量樣板代碼甚至分頁(yè)查詢都是現(xiàn)成的。對(duì)于時(shí)間有限的畢設(shè)黨來說這套組合能把精力從配置里解放出來放到業(yè)務(wù)實(shí)現(xiàn)上。前端用Vue核心優(yōu)勢(shì)是組件化開發(fā)和生態(tài)成熟。頁(yè)面里每一個(gè)功能塊都可以拆成組件比如菜譜卡片、評(píng)論區(qū)、分頁(yè)條寫一次到處復(fù)用。配上Vue Router做路由跳轉(zhuǎn)Axios做HTTP請(qǐng)求Element UI提供現(xiàn)成的UI組件一套后臺(tái)管理風(fēng)格的前端界面幾天就能搭出來。數(shù)據(jù)庫(kù)選用MySQL配合Navicat這類可視化工具建表、改結(jié)構(gòu)、導(dǎo)數(shù)據(jù)都直觀。項(xiàng)目交付時(shí)數(shù)據(jù)庫(kù)腳本是必備的評(píng)審老師打開就能導(dǎo)入印象分會(huì)好很多。1.3 功能拆分與頁(yè)面映射把需求翻譯成功能清單大概是下面這樣模塊后端接口前端頁(yè)面核心交互用戶注冊(cè)、登錄、個(gè)人信息、修改資料登錄頁(yè)、注冊(cè)頁(yè)、個(gè)人中心JWT鑒權(quán)、頭像上傳菜譜分頁(yè)查詢、條件搜索、分類篩選、詳情、發(fā)布、編輯、刪除首頁(yè)、菜譜列表、菜譜詳情、發(fā)布頁(yè)多條件組合查詢、圖片上傳互動(dòng)收藏、取消收藏、點(diǎn)贊、評(píng)論、瀏覽計(jì)數(shù)菜譜詳情頁(yè)內(nèi)嵌狀態(tài)實(shí)時(shí)更新統(tǒng)計(jì)排行、最新、熱門首頁(yè)排行榜、列表頁(yè)排序?yàn)g覽量計(jì)算與排序頁(yè)面不需要炫技但核心鏈路必須通用戶進(jìn)入首頁(yè)看菜譜列表點(diǎn)進(jìn)詳情能看圖文步驟覺得不錯(cuò)就收藏、點(diǎn)贊、評(píng)論想分享自己的做法就注冊(cè)登錄后發(fā)布菜譜在個(gè)人中心管理自己發(fā)過和收藏過的內(nèi)容。這一套走下來項(xiàng)目講解時(shí)能講出一條非常清晰的故事線。2. 數(shù)據(jù)庫(kù)設(shè)計(jì)菜譜與互動(dòng)的表結(jié)構(gòu)詳解2.1 設(shè)計(jì)思路先畫業(yè)務(wù)ER圖再落表數(shù)據(jù)庫(kù)設(shè)計(jì)是答辯環(huán)節(jié)最容易暴露基本功的地方。很多同學(xué)上來就建表建到一半發(fā)現(xiàn)字段不夠或者關(guān)系沖突再回頭改結(jié)構(gòu)效率奇低。我的做法是先畫ER圖再落表。這個(gè)項(xiàng)目里的實(shí)體其實(shí)很清晰用戶、菜譜分類、菜譜、評(píng)論、收藏記錄。點(diǎn)贊記錄可以考慮單獨(dú)建表也可以跟收藏合并到一張互動(dòng)表里用type區(qū)分但為了講解方便和代碼清晰我建議拆開。關(guān)系是用戶和菜譜一對(duì)多一個(gè)用戶能發(fā)布多個(gè)菜譜。分類和菜譜一對(duì)多一個(gè)分類下掛多個(gè)菜譜。用戶和評(píng)論一對(duì)多一個(gè)用戶能評(píng)論多個(gè)菜譜。菜譜和評(píng)論一對(duì)多一個(gè)菜譜有多條評(píng)論。用戶和菜譜的互動(dòng)多對(duì)多通過收藏表、點(diǎn)贊表來體現(xiàn)。2.2 核心表結(jié)構(gòu)拆解用戶表不需要復(fù)雜常規(guī)字段就夠了。注意密碼必須加密存儲(chǔ)這一步在答辯時(shí)經(jīng)常被問到答不上“為什么不能明文存”會(huì)非常掉分。字段類型說明idbigint主鍵自增usernamevarchar(50)用戶名唯一passwordvarchar(100)BCrypt加密后的密碼nicknamevarchar(50)昵稱avatarvarchar(255)頭像地址emailvarchar(100)郵箱rolevarchar(20)角色區(qū)分普通用戶和管理員create_timedatetime注冊(cè)時(shí)間statustinyint狀態(tài)1正常0禁用菜譜表是內(nèi)容核心字段要多一些。食材和步驟這類結(jié)構(gòu)化文本建議用JSON或者用分隔符存儲(chǔ)避免再拆兩張子表增加復(fù)雜度。我當(dāng)時(shí)的做法是用JSON字符串存食材列表和步驟列表后端解析后返回給前端渲染實(shí)測(cè)方便很多。字段類型說明idbigint主鍵user_idbigint發(fā)布者關(guān)聯(lián)用戶表category_idbigint分類關(guān)聯(lián)分類表titlevarchar(100)菜譜標(biāo)題covervarchar(255)封面圖descriptiontext簡(jiǎn)介ingredientstext食材清單JSON格式stepstext操作步驟JSON格式difficultytinyint難度1簡(jiǎn)單2中等3困難cook_timeint耗時(shí)單位分鐘viewsint瀏覽數(shù)likesint點(diǎn)贊數(shù)statustinyint狀態(tài)1已發(fā)布0草稿create_timedatetime發(fā)布時(shí)間互動(dòng)相關(guān)的三張表評(píng)論表存內(nèi)容收藏表和點(diǎn)贊表記錄用戶與菜譜的關(guān)聯(lián)關(guān)系。后兩張表結(jié)構(gòu)相似但語義不同分開寫更容易講清楚也方便做判斷“當(dāng)前用戶是否已收藏”這類查詢。2.3 索引設(shè)計(jì)列表查詢快不快要看索引數(shù)據(jù)量小的時(shí)候索引感覺不明顯但答辯時(shí)如果被問到“你怎么保證查詢性能”索引就是必答項(xiàng)。我在菜譜表的category_id、user_id、create_time上建了索引在評(píng)論表的recipe_id上建了索引在收藏表和點(diǎn)贊表的user_id recipe_id上建了聯(lián)合唯一索引。聯(lián)合唯一索引同時(shí)解決了兩個(gè)問題防止用戶對(duì)同一條菜譜重復(fù)收藏/點(diǎn)贊以及快速判斷用戶是否收藏過某菜譜。CREATE UNIQUE INDEX uk_user_recipe ON favorite(user_id, recipe_id);這條語句在講解時(shí)可以單獨(dú)拿出來說因?yàn)樗瑫r(shí)體現(xiàn)了你對(duì)約束和數(shù)據(jù)完整性的理解。3. 后端SpringBoot核心實(shí)現(xiàn)從項(xiàng)目骨架到接口落地3.1 項(xiàng)目結(jié)構(gòu)初始化與依賴引入創(chuàng)建SpringBoot項(xiàng)目我用的是IDEA的Spring InitializrJava版本選擇8或者11都可以SpringBoot版本選擇2.7.x。這里有個(gè)過來人的建議不要去追最新的SpringBoot 3.x因?yàn)?.x強(qiáng)制要求JDK 17而且很多教程還停留在2.x遇到問題排查起來會(huì)非常痛苦。關(guān)鍵依賴如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependencyMyBatis-Plus的引入能省掉大量Mapper XML簡(jiǎn)單的CRUD直接繼承BaseMapper就有現(xiàn)成方法。項(xiàng)目結(jié)構(gòu)上按標(biāo)準(zhǔn)分包c(diǎn)ontroller、service、mapper、entity、commoncommon里面放統(tǒng)一返回結(jié)果、異常處理、工具類。3.2 統(tǒng)一返回結(jié)果與全局異常處理這一步看起來不起眼但直接決定前后端聯(lián)調(diào)時(shí)的效率。如果沒有統(tǒng)一返回格式前端每個(gè)接口都要單獨(dú)判斷數(shù)據(jù)結(jié)構(gòu)調(diào)試成本會(huì)翻倍。我在common包里定義了一個(gè)Result類Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }后端所有Controller都返回Result對(duì)象前端Axios統(tǒng)一攔截后取data字段如果code不是200彈出message提示。這樣寫還有另一個(gè)好處全局異常處理器里捕獲的異常也能包裝成Result返回前端不會(huì)看到一堆看不懂的英文堆棧。3.3 JWT登錄認(rèn)證的設(shè)計(jì)與實(shí)現(xiàn)認(rèn)證這塊我用的是JWT而不是傳統(tǒng)的Session。原因很簡(jiǎn)單前后端分離的項(xiàng)目里后端不維護(hù)Session更利于水平擴(kuò)展而且Vue端拿到token后存到localStorage每次請(qǐng)求在攔截器里帶上Authorization頭一套流程很干凈。登錄流程是這樣的用戶提交用戶名密碼后端驗(yàn)證密碼。驗(yàn)證通過后用JWT工具類生成token把用戶ID和用戶名放入claims。token返回給前端前端保存并在后續(xù)請(qǐng)求中攜帶。后端攔截器校驗(yàn)token解析出用戶信息放入ThreadLocal供Controller使用。密碼加密用BCryptPasswordEncoder這里多說一句凡是數(shù)據(jù)庫(kù)里有用戶表且存了密碼的項(xiàng)目答辯幾乎必問密碼安全性。你如果能說出“BCrypt是單向哈希、自帶鹽值、同一密碼每次加密結(jié)果不同”這一問就過了。JWT攔截器的核心代碼大致是這樣Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } String token request.getHeader(Authorization); if (StringUtils.hasText(token) JwtUtil.validateToken(token)) { Integer userId JwtUtil.getUserId(token); UserContext.setUserId(userId); return true; } response.setStatus(401); return false; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); } }注意一個(gè)細(xì)節(jié)afterCompletion里必須清掉ThreadLocal否則Tomcat線程池復(fù)用時(shí)會(huì)出現(xiàn)用戶數(shù)據(jù)串號(hào)。這個(gè)點(diǎn)知道的人不多但講出來會(huì)顯得你對(duì)并發(fā)編程有真實(shí)理解。3.4 菜譜模塊的接口設(shè)計(jì)與分頁(yè)查詢菜譜列表接口是項(xiàng)目里最重要的一個(gè)接口因?yàn)樗瑫r(shí)支持分頁(yè)、分類篩選、按標(biāo)題搜索、按瀏覽量排序、按最新時(shí)間排序。如果不用MyBatis-Plus這一塊要拼不少SQL但現(xiàn)在用LambdaQueryWrapper就能優(yōu)雅搞定。Override public PageResultRecipeVO pageQuery(RecipeQuery query) { PageRecipe page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperRecipe wrapper new LambdaQueryWrapper(); wrapper.eq(Recipe::getStatus, 1) .eq(query.getCategoryId() ! null, Recipe::getCategoryId, query.getCategoryId()) .like(StringUtils.hasText(query.getKeyword()), Recipe::getTitle, query.getKeyword()) .orderByDesc(query.getOrderBy().equals(hot) ? Recipe::getViews : Recipe::getCreateTime); PageRecipe result recipeMapper.selectPage(page, wrapper); // 轉(zhuǎn)換為VO補(bǔ)充封面圖路徑、發(fā)布者昵稱等信息 return convertToPageResult(result); }這里的亮點(diǎn)在于條件構(gòu)造器可以很優(yōu)雅地把“可選參數(shù)”拼進(jìn)SQL里。eq里的第三個(gè)參數(shù)是布爾值只有條件滿足時(shí)才拼接該條件這樣就不用寫一堆if判斷了。發(fā)布菜譜的接口要注意事務(wù)問題。雖然目前只插入一條菜譜記錄但考慮到以后可能要同時(shí)刷新分類統(tǒng)計(jì)、生成搜索索引應(yīng)該在Service層加上Transactional注解。這個(gè)小細(xì)節(jié)同樣能在答辯時(shí)加分。4. 前端Vue實(shí)現(xiàn)從安裝環(huán)境到頁(yè)面組件落地4.1 Vue環(huán)境配置與項(xiàng)目創(chuàng)建Vue環(huán)境配置是很多新手卡住的第一道關(guān)。Node.js的安裝要注意版本Vue CLI 4.x版本建議Node 12以上Vite創(chuàng)建Vue 3項(xiàng)目建議Node 16以上。如果版本不匹配啟動(dòng)時(shí)會(huì)報(bào)一堆看不懂的錯(cuò)。建議直接使用Vite創(chuàng)建Vue 3項(xiàng)目創(chuàng)建命令npm create vitelatest food-platform -- --template vue然后安裝路由和狀態(tài)管理npm install vue-router4 npm install pinia npm install axios npm install element-plusElement Plus是Element UI的Vue 3版本組件風(fēng)格適合內(nèi)容管理類系統(tǒng)表格、表單、卡片、分頁(yè)這些都有現(xiàn)成的能省下大量寫CSS的時(shí)間。完整引入和按需引入的問題項(xiàng)目時(shí)間緊就直接完整引入反正畢設(shè)不追求首屏加載極致的包體積。4.2 前端路由設(shè)計(jì)與頁(yè)面結(jié)構(gòu)前端路由先想清楚再動(dòng)手不然后面越寫越亂。我的路由設(shè)計(jì)如下路徑組件說明/Home.vue首頁(yè)含輪播圖、分類導(dǎo)航、熱門菜譜/recipesRecipeList.vue菜譜列表支持分類和關(guān)鍵詞查詢/recipe/:idRecipeDetail.vue菜譜詳情核心頁(yè)面/publishPublishRecipe.vue發(fā)布菜譜需登錄/loginLogin.vue登錄/registerRegister.vue注冊(cè)/userUserCenter.vue個(gè)人中心/user/collectionsUserCollections.vue我的收藏路由守衛(wèi)很重要。發(fā)布菜譜、個(gè)人中心這類頁(yè)面必須登錄后才能訪問Vue Router的全局前置守衛(wèi)可以統(tǒng)一處理router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); } else { next(); } });4.3 Axios請(qǐng)求封裝與攔截器Axios不封裝直接用也能跑但每個(gè)頁(yè)面都要重復(fù)處理token拼接和錯(cuò)誤提示代碼會(huì)非常冗余。我習(xí)慣在src/utils/request.js里做統(tǒng)一封裝import axios from axios; import { ElMessage } from element-plus; import router from ../router; const request axios.create({ baseURL: /api, timeout: 10000 }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; }); request.interceptors.response.use( response { const res response.data; if (res.code 200) { return res.data; } ElMessage.error(res.message || 請(qǐng)求失敗); return Promise.reject(new Error(res.message)); }, error { if (error.response error.response.status 401) { ElMessage.error(請(qǐng)先登錄); localStorage.removeItem(token); router.push(/login); } else { ElMessage.error(網(wǎng)絡(luò)異常請(qǐng)稍后重試); } return Promise.reject(error); } ); export default request;這里有個(gè)關(guān)鍵配置baseURL設(shè)為/api然后在Vite配置文件里加代理轉(zhuǎn)發(fā)到后端8080端口。這樣開發(fā)時(shí)前端請(qǐng)求不會(huì)跨域部署后Nginx也能通過/api前綴做反向代理一套邏輯兩頭通用。4.4 菜譜詳情頁(yè)的組件拆分菜譜詳情頁(yè)是功能最集成的頁(yè)面包含基本信息、食材清單、步驟圖片、瀏覽量、收藏按鈕、點(diǎn)贊按鈕、評(píng)論區(qū)。組件拆分思路RecipeInfo展示標(biāo)題、封面、作者、難度、耗時(shí)、瀏覽量。IngredientList接收J(rèn)SON字符串解析后渲染食材表格。StepList步驟列表用el-steps組件展示。InteractionBar收藏、點(diǎn)贊按鈕動(dòng)態(tài)切換選中狀態(tài)。CommentSection評(píng)論區(qū)支持加載更多。這種拆法最直觀的好處是每個(gè)組件的props和emits是清晰的代碼可讀性強(qiáng)。更關(guān)鍵的是答辯講項(xiàng)目的時(shí)候你能說出“我把可獨(dú)立的功能抽成了組件”這種話比說“我寫了一個(gè)大頁(yè)面”有說服力得多。5. 前后端聯(lián)調(diào)與部署上線全流程5.1 本地聯(lián)調(diào)時(shí)的跨域與代理配置前后端分離開發(fā)時(shí)跨域問題無法回避。解決跨域有兩個(gè)層面后端加CORS配置或者前端代理轉(zhuǎn)發(fā)。我在開發(fā)階段用的是Vite代理。在vite.config.js里配置export default defineConfig({ server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } });這樣前端頁(yè)面里發(fā)的/api/user/login請(qǐng)求會(huì)被Vite開發(fā)服務(wù)器轉(zhuǎn)發(fā)到http://localhost:8080/user/login瀏覽器全程只跟同源地址打交道不會(huì)觸發(fā)CORS。后端的CORS配置也不能少因?yàn)椴渴鸷罂赡芮岸遂o態(tài)文件跟后端接口不在同一臺(tái)服務(wù)器上。寫一個(gè)全局CORS配置類Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }5.2 SpringBoot項(xiàng)目打包與配置文件分離SpringBoot項(xiàng)目的部署最簡(jiǎn)單的方式是把項(xiàng)目打成jar包然后mvn clean package -DskipTests java -jar food-platform.jar但實(shí)盤部署時(shí)建議把配置外置這樣改數(shù)據(jù)庫(kù)密碼或者端口時(shí)不用重新打包。方法是在jar包同目錄下放一個(gè)application-prod.yml啟動(dòng)時(shí)指定java -jar food-platform.jar --spring.profiles.activeprod生產(chǎn)環(huán)境的配置里數(shù)據(jù)庫(kù)密碼不要寫在明文中可以用Jasypt加密。雖然畢設(shè)階段用不到這么重的手段但你在報(bào)告里提到“配置外置”和“profile區(qū)分環(huán)境”比只說“把項(xiàng)目打成jar包就能跑”要好得多。默認(rèn)端口如果不是8080用server.port配置即可。還有一個(gè)小細(xì)節(jié)SpringBoot 2.7的啟動(dòng)Banner可以自己生成用網(wǎng)絡(luò)上的banner生成工具把字符畫丟進(jìn)去啟動(dòng)時(shí)就能看到效果這種小趣味雖然不影響功能但顯得項(xiàng)目有點(diǎn)人情味發(fā)給老師看的時(shí)候他會(huì)覺得你做事細(xì)致。5.3 Vue項(xiàng)目打包并放進(jìn)SpringBootVue項(xiàng)目打包默認(rèn)輸出到dist目錄npm run build打包完的dist里全是靜態(tài)文件部署方式有兩種常見路線路線一用Nginx托管前端靜態(tài)文件反向代理后端接口。這是生產(chǎn)的標(biāo)準(zhǔn)做法配置大概是server { listen 80; server_name your-domain.com; location / { root /opt/food-platform/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; } }注意try_files那句Vue Router使用history模式時(shí)不寫這行的話刷新頁(yè)面會(huì)404。路線二直接把dist目錄復(fù)制到SpringBoot的src/main/resources/static目錄下然后一起打包。這種做法簡(jiǎn)單粗暴適合沒有獨(dú)立服務(wù)器部署前端靜態(tài)資源的場(chǎng)景。需要注意的坑是如果后端接口前綴是/api而前端路由里也用了/api可能會(huì)跟靜態(tài)資源路徑?jīng)_突。我建議還是走Nginx方案因?yàn)椴渴餜evue打包文件到SpringBoot里會(huì)讓你的項(xiàng)目結(jié)構(gòu)變得不干凈而且給老師演示的時(shí)候Nginx方案更能展示你對(duì)生產(chǎn)環(huán)境的理解。但如果你只是為了在本地一個(gè)jar包把前后端都跑起來第二種方案確實(shí)省事把dist下內(nèi)容拷到static目錄重新打包訪問8080端口就能看到完整頁(yè)面。5.4 數(shù)據(jù)庫(kù)初始化腳本的編寫規(guī)范交付項(xiàng)目時(shí)數(shù)據(jù)庫(kù)腳本是標(biāo)配。腳本里要注意幾點(diǎn)開頭加CREATE DATABASE IF NOT EXISTS和USE語句。所有表加DROP TABLE IF EXISTS但是要注釋掉防止拿著腳本直接跑的時(shí)候把已有數(shù)據(jù)清了。主鍵用AUTO_INCREMENT不手動(dòng)插入主鍵值。插入必要的初始數(shù)據(jù)比如管理員賬號(hào)、分類數(shù)據(jù)、幾條示例菜譜。腳本寫完一定要自己從空庫(kù)跑一遍確認(rèn)沒問題再交這是基本功。我見過有人交的SQL腳本是從Navicat直接導(dǎo)出的里面有完整的建表冗余語句導(dǎo)入時(shí)各種報(bào)錯(cuò)這種細(xì)節(jié)問題在答辯現(xiàn)場(chǎng)特別尷尬。6. 常見問題排查與答辯準(zhǔn)備6.1 新手最容易踩的幾個(gè)坑我把這個(gè)項(xiàng)目從零到一做完遇到過的問題基本集中在下面幾類。端口沖突SpringBoot默認(rèn)8080如果本機(jī)已經(jīng)有別的服務(wù)占用了啟動(dòng)會(huì)直接報(bào)錯(cuò)“Port already in use”。排查方式是用netstat -ano | findstr 8080Windows或者lsof -i:8080Mac/Linux查占用進(jìn)程殺掉或者改server.port都行。MyBatis-Plus配置掃不到Mapperapplication.yml里沒配置mapper-locations或者啟動(dòng)類沒加MapperScan注解運(yùn)行時(shí)會(huì)報(bào)“Invalid bound statement”。解決方法是啟動(dòng)類加MapperScan(com.food.mapper)前端404除了前面提到的history模式刷新問題還有一個(gè)常見場(chǎng)景是Nginx只配置了location /反向代理沒寫對(duì)。請(qǐng)求打過去就返回前端index.html但接口路徑實(shí)際沒轉(zhuǎn)發(fā)到后端看到的結(jié)果就是頁(yè)面能開數(shù)據(jù)全是空的。圖片上傳后訪問不到項(xiàng)目本地上傳的圖片存在了磁盤上的某個(gè)目錄但前端使用img標(biāo)簽訪問的時(shí)候路徑不對(duì)。把上傳目錄映射成靜態(tài)資源是個(gè)好辦法Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }6.2 答辯高頻問題與回答思路答辯指導(dǎo)這部分我把評(píng)委老師大概率問的問題和對(duì)應(yīng)的回答思路都梳理一下項(xiàng)目用的什么架構(gòu)——前后端分離架構(gòu)。前端Vue Element Plus負(fù)責(zé)頁(yè)面渲染和用戶交互后端SpringBoot提供RESTful API通過JSON數(shù)據(jù)交互部署時(shí)前端交給Nginx托管、后端打成jar包運(yùn)行。為什么選SpringBoot不用SSM——SpringBoot簡(jiǎn)化了SSM的配置內(nèi)置Tomcat能夠獨(dú)立運(yùn)行自動(dòng)裝配機(jī)制減少了大量樣板配置。但底層仍然是Spring MVC MyBatis這套東西。數(shù)據(jù)庫(kù)表是怎么設(shè)計(jì)的——從業(yè)務(wù)實(shí)體出發(fā)梳理關(guān)系用戶和菜譜是一對(duì)多菜譜和分類是多對(duì)一用戶和菜譜的互動(dòng)通過收藏/點(diǎn)贊表體現(xiàn)。重點(diǎn)字段加了索引聯(lián)合唯一索引防止重復(fù)互動(dòng)。瀏覽量怎么實(shí)現(xiàn)的——就是在詳情接口里對(duì)views字段做1更新。如果要做得更精細(xì)可以通過Redis incr來計(jì)數(shù)再異步落庫(kù)但畢設(shè)講清楚基本實(shí)現(xiàn)即可。圖片上傳到哪去了——上傳到服務(wù)器的磁盤目錄通過靜態(tài)資源映射暴露訪問地址。注意不能直接存數(shù)據(jù)庫(kù)的BLOB字段會(huì)拖慢數(shù)據(jù)庫(kù)性能。評(píng)論和收藏接口怎么設(shè)計(jì)的——評(píng)論表關(guān)聯(lián)菜譜ID和用戶ID新增時(shí)同時(shí)更新菜譜的評(píng)論數(shù)可選。收藏接口先判斷是否已收藏再進(jìn)行插入或刪除操作返回最新狀態(tài)給前端。6.3 報(bào)告的寫法與重點(diǎn)章節(jié)建議項(xiàng)目的交付物不只是代碼還有一份文檔報(bào)告。報(bào)告的關(guān)鍵章節(jié)緒論寫清楚項(xiàng)目背景、國(guó)內(nèi)外現(xiàn)狀、研究意義。這部分不用寫太多三頁(yè)左右即可。需求分析核心功能的需求描述配合用例圖。用例圖可以用PlantUML或者ProcessOn畫不需要多精但要規(guī)范。系統(tǒng)設(shè)計(jì)架構(gòu)圖、技術(shù)選型、數(shù)據(jù)庫(kù)ER圖、表結(jié)構(gòu)說明。這是評(píng)委看得最細(xì)的部分。系統(tǒng)實(shí)現(xiàn)每個(gè)模塊的截圖核心代碼段說明。代碼不要求全量貼挑核心邏輯講清楚。系統(tǒng)測(cè)試寫功能測(cè)試用例表覆蓋登錄、發(fā)布菜譜、收藏、評(píng)論等核心流程。如果有余力加一點(diǎn)JMeter性能測(cè)試截圖會(huì)非常加分。浩瀚的技術(shù)棧里這種全棧項(xiàng)目最容易被問“這個(gè)是你自己寫的嗎”所以你一定要對(duì)每行代碼都了如指掌。我的習(xí)慣是在答辯之前把所有Service層的方法從頭到尾過一遍能說出每個(gè)方法的作用和調(diào)用鏈。6.4 個(gè)人實(shí)操體會(huì)與最后的小建議項(xiàng)目做完之后我最大的感觸是這套技術(shù)棧組合不是為了炫技而是它恰好覆蓋了一個(gè)真實(shí)Web項(xiàng)目從寫到跑的所有環(huán)節(jié)。你寫數(shù)據(jù)庫(kù)的時(shí)候在做表結(jié)構(gòu)和索引設(shè)計(jì)寫Vue的時(shí)候在考慮組件復(fù)用和路由權(quán)限寫部署的時(shí)候要處理跨域、靜態(tài)資源、進(jìn)程管理和日志查看這一整套經(jīng)驗(yàn)在以后的工作里會(huì)反復(fù)用到。最后分享一個(gè)很實(shí)用的小建議項(xiàng)目答辯前把項(xiàng)目在另一臺(tái)電腦上從零部署一遍。MySQL導(dǎo)入腳本、JDK安裝、Node環(huán)境、jar包啟動(dòng)全程記錄下來。一方面你會(huì)發(fā)現(xiàn)自己遺漏了哪些依賴沒說明另一方面這份部署文檔直接放進(jìn)報(bào)告正文當(dāng)“部署說明”章節(jié)質(zhì)量比網(wǎng)上抄來的教程靠譜得多。至于答辯現(xiàn)場(chǎng)評(píng)委讓你現(xiàn)場(chǎng)啟動(dòng)項(xiàng)目你也不會(huì)因?yàn)榄h(huán)境問題卡住而手忙腳亂。這個(gè)項(xiàng)目本身還可以繼續(xù)擴(kuò)展的方向有不少我后來在里面加了菜譜收藏統(tǒng)計(jì)排行、用戶上傳菜譜審核、按食材名稱搜索再往后甚至可以引入Redis緩存熱門菜譜列表。但對(duì)現(xiàn)階段的你來說先把基礎(chǔ)鏈路走通、把每個(gè)細(xì)節(jié)問透就已經(jīng)贏過大多數(shù)交爛大街項(xiàng)目的人了。