設計與實現(xiàn))
前后端分離健身俱樂部網(wǎng)站系統(tǒng)這個項目我做過類似的好幾個學員和粉絲也拿這類選題當畢業(yè)設計或者練手項目。說實話SpringBoot Vue MyBatis MySQL這一套組合放在今天依然是前后端分離項目里最有代表性的技術棧之一。它不像微服務那套那么重但麻雀雖小五臟俱全RESTful API、數(shù)據(jù)持久化、JWT鑒權、跨域處理、前端路由守衛(wèi)、反向代理這些核心概念全都能覆蓋到。如果你正準備搞一個能寫進簡歷、能跑起來演示的完整系統(tǒng)這個方向非常合適。這篇就圍繞健身俱樂部這個業(yè)務場景把整個系統(tǒng)的設計思路、核心模塊、關鍵實現(xiàn)和部署過程全拆開來講。包括后端接口怎么組織、數(shù)據(jù)庫表怎么設計、前端頁面怎么對接、打包部署要注意哪些坑每一步我都會把選擇背后的理由說清楚。不管你是有一定基礎的開發(fā)者想找參考還是剛學完框架想做實戰(zhàn)項目這篇都能幫你省下不少彎路。1. 項目整體設計與思路拆解1.1 為什么選健身俱樂部這個業(yè)務場景做項目最怕的就是業(yè)務太抽象比如“電商系統(tǒng)”“管理系統(tǒng)”這種邊界模糊做著做著就不知道自己該實現(xiàn)什么了。健身俱樂部這個場景好在業(yè)務閉環(huán)非常清晰核心鏈條就是會員注冊 - 查看課程 - 預約課程 - 教練排課 - 上課簽到 - 消費結算。這個閉環(huán)涵蓋了最常見的CRUD操作還牽扯到一對多、多對多關系以及訂單狀態(tài)流轉(zhuǎn)這類稍微進階一點的數(shù)據(jù)處理。另外健身俱樂部的業(yè)務天然適合做權限區(qū)分。會員看到的是課程列表和自己預約記錄教練看到的是自己的排課和學員列表管理員則需要管理會員、審核課程、查看營收統(tǒng)計。有了三種角色JWT鑒權、路由守衛(wèi)、按鈕級權限控制這些技術點就能全部落地上而不是空談概念。1.2 前后端分離架構的優(yōu)勢在哪里早年的Java Web項目是JSP Servlet那一套前端頁面寫在Java項目里模板渲染交給服務端技術和業(yè)務耦合得非常緊。換個前端樣式要動后端代碼前端工程師和后端工程師經(jīng)常在一個項目里互相踩腳。前后端分離的本質(zhì)是把“展示邏輯”和“業(yè)務邏輯”徹底拆成兩個獨立工程中間只通過JSON格式的HTTP接口通信。這種架構帶來的直接好處有三個。第一前端可以獨立部署Vue打包出來就是一堆靜態(tài)文件扔給Nginx托管就行完全不需要Java運行環(huán)境。第二后端接口可以被多個客戶端復用同一套API既給Web用以后要做小程序或者App也能直接用。第三團隊協(xié)作效率高前端用Mock數(shù)據(jù)開發(fā)后端用Postman調(diào)試接口兩邊只要約定好接口文檔就能并行開工。1.3 技術選型的理由和替代方案SpringBoot MyBatis MySQL在這個項目里是最順手的組合。SpringBoot不用多說自動裝配和Starter機制大幅簡化了配置一個可運行的Web服務幾行代碼就能起來。MyBatis的優(yōu)勢在于SQL可控復雜查詢可以手寫SQL優(yōu)化而且動態(tài)SQL在按條件篩選這種場景下非常好用。MySQL穩(wěn)定可靠學習成本低部署簡單作為中小型項目的數(shù)據(jù)庫完全沒有壓力。前端選Vue是因為它的漸進式框架特性很契合這類項目。從Vue 2到Vue 3Composition API的引入讓邏輯復用更清晰腳手架項目默認幫你配好了Vite或者Webpack路由和狀態(tài)管理都有官方的配套方案。當然你完全可以用MyBatis-Plus替代MyBatis單表CRUD幾乎不用寫SQL或者用Spring Data JPA那是另一套風格。這些不是誰比誰好關鍵是你對哪套更熟。我建議學習階段盡量用原生MyBatis把SQL和映射關系親手寫一遍理解了底層后再用增強框架認知會扎實很多。提示既然是做完整系統(tǒng)別只把它當練習。如果你打算寫進簡歷一定要把業(yè)務模塊梳理清楚并且保證項目能在新環(huán)境下一鍵跑起來。跑不起來的項目寫再多技術點都白搭。2. 數(shù)據(jù)庫設計與后端核心實現(xiàn)2.1 數(shù)據(jù)庫表結構怎么規(guī)劃健身俱樂部系統(tǒng)的表設計重點在于把業(yè)務核心鏈路的數(shù)據(jù)模型理清楚。我一般會分三組來設計基礎信息組、業(yè)務流轉(zhuǎn)組、訂單支付組?;A信息組包括用戶表、會員信息表、教練信息表、課程表。用戶表保存登錄賬號、密碼加密存儲、角色類型這三要素角色用int型字段區(qū)分0是管理員1是教練2是會員。會員信息表關聯(lián)用戶表擴展存身高、體重、體脂率、會員等級這些健身屬性。課程表主要字段是課程名稱、封面圖、分類、簡介、價格、上課地點。業(yè)務流轉(zhuǎn)組是重點包括課程預約表和教練排課表。預約表要和課程表、用戶表建立關聯(lián)記錄約課時間、狀態(tài)。狀態(tài)字段也是用int表示0待上課、1已上課、2已取消。教練排課表則需要設置每個時間段的可約人數(shù)上限這個字段在后續(xù)實現(xiàn)預約校驗時很關鍵。訂單支付組負責消費記錄訂單表包含訂單號、用戶ID、商品類型、金額、支付狀態(tài)、支付時間、支付方式。另外再加一個操作日志表記錄誰在什么時間改了什么數(shù)據(jù)后臺管理時能看到操作痕跡。創(chuàng)建表的時候有幾個細節(jié)容易忽略。第一所有表都建議加create_time和update_time字段用datetime類型后面做排序和分析數(shù)據(jù)都方便。第二邏輯刪除字段del_flag建議加上物理刪除對運營類系統(tǒng)來說風險太高。第三金額字段強烈建議用decimal而不是float用float存金額做加減運算會出現(xiàn)精度丟失。2.2 SpringBoot項目結構怎么組織才清晰后端工程的結構直接影響后續(xù)維護體驗。按模塊分包是最常規(guī)的約定但很多人習慣把所有東西都塞到controller和service兩個包下面結果就是類越來越多后期自己都找不到。這里貼一下我用下來的結構com.example.fitness ├── config # 配置類跨域配置、JWT攔截器注冊、全局異常處理 ├── controller # 接口層接收參數(shù)返回統(tǒng)一響應體 ├── service # 業(yè)務層接口 實現(xiàn)類 ├── mapper # MyBatis的Mapper接口 ├── entity # 數(shù)據(jù)庫對應的實體類 ├── dto # 數(shù)據(jù)傳輸對象接收請求參數(shù) ├── vo # 視圖對象返回給前端的數(shù)據(jù)結構 ├── utils # 工具類JWT工具、密碼加密工具 └── common # 公共枚舉、常量、統(tǒng)一返回結果封裝entity、dto、vo分開這一點很多人會忽略。直接用entity接收前端參數(shù)、再直接返回給前端圖省事一時爽后面改需求就是災難。舉個例子前端傳登錄參數(shù)需要的是用戶名和密碼但user表有十幾個字段直接拿實體接收會有一些安全隱患多傳字段也容易出問題。只暴露出需要的字段接口安全性和可讀性都好很多。統(tǒng)一返回結果也要在一開始就做好。我習慣定義一個Result對象里面有code、message、data三個字段。code為200表示成功401表示未登錄或token失效500表示服務器異常。所有接口都返回這個對象前端axios封裝時只需處理一種數(shù)據(jù)結構。2.3 MyBatis中幾個會踩坑的細節(jié)MyBatis看似簡單但用不好真會讓人頭疼兩三天這里把最容易踩的幾個點挨個說。2.3.1 Mapper接口掃描配置Mapper接口和XML文件能否被正確識別取決于兩處配置。第一處是在啟動類上加MapperScan(com.example.fitness.mapper)或者每個Mapper接口上單獨加注解二選一即可。第二處是XML文件的存放位置如果你把Mapper XML放在java目錄下需要在pom.xml里加配置把xml文件打包進去否則構建后運行就會報“Invalid bound statement (not found)”這個錯在部署時非常常見。build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources /build如果XML放在src/main/resources目錄下的mapper子目錄則要在application.yml里配置mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.fitness.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case這個配置強烈建議開啟這樣數(shù)據(jù)庫的create_time字段就能自動映射到實體的createTime屬性少寫很多resultMap。2.3.2 動態(tài)SQL怎么避免誤更新空字段更新操作是動態(tài)SQL最容易出問題的場景。如果用set標簽更新用戶信息前端只傳了昵稱結果把密碼、手機號也更新成null了這種事故幾乎每個新手都遇到過。解決方法是更新接口單獨設計DTO并且使用MyBatis的if標簽做非空判斷update idupdateMemberInfo parameterTypecom.example.fitness.dto.MemberUpdateDTO update member set if testheight ! nullheight #{height},/if if testweight ! nullweight #{weight},/if if testbodyFat ! nullbody_fat #{bodyFat},/if if testmemberLevel ! nullmember_level #{memberLevel},/if /set where id #{id} /update這樣前端傳什么字段就更新什么字段不傳的字段保持原狀。要注意set標簽會自動處理最后一個逗號別畫蛇添足去寫, where。2.3.3 循環(huán)插入用foreach還是單條insert批量添加課程排期、批量導入會員這類場景很多人會寫一個for循環(huán)調(diào)用單條insert數(shù)據(jù)量大了效率就很差。正確做法是用foreach一次批量插入insert idbatchInsertSchedule insert into coach_schedule (coach_id, course_id, start_time, end_time, max_people, create_time) values foreach collectionlist itemitem separator, (#{item.coachId}, #{item.courseId}, #{item.startTime}, #{item.endTime}, #{item.maxPeople}, now()) /foreach /insertMySQL默認的max_allowed_packet大小足夠容納幾千條數(shù)據(jù)的批量插入一般不用擔心超出限制。加rewriteBatchedStatementstrue到數(shù)據(jù)庫連接串還能進一步提升批量執(zhí)行效率。2.4 JWT登錄鑒權的完整邏輯登錄鑒權是前后端分離項目的核心環(huán)節(jié)。因為接口是無狀態(tài)的所以需要用一種機制讓服務端能識別“你是誰”。我用的是JWT方案流程是這樣用戶登錄后后端校驗用戶名密碼校驗通過就生成一個JWT令牌返回給前端。這個令牌由三部分組成Header、Payload、Signature核心信息放在Payload里。我習慣把userId和role兩個字段放進去這樣后續(xù)接口里從token中解析出userId就知道當前請求是哪個用戶在操作。令牌設置兩小時過期前端在axios攔截器里發(fā)現(xiàn)返回401就跳回登錄頁。生成JWT的核心代碼邏輯參考String token Jwts.builder() .setSubject(user.getUsername()) .claim(userId, user.getId()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();有了令牌攔截器在SpringBoot中通過實現(xiàn)HandlerInterceptor接口在preHandle方法里校驗token。要記得把注冊登錄接口排除在攔截范圍外配置攔截路徑時用excludePathPatterns把公開接口放行。這個過程中最大的坑是前后端對token的命名要完全一致。有的項目后端返回token字段前端卻存在access_token里結果所有請求都帶不上token全是401。盡量在一開始就約定好response里的字段叫token前端storage里存的key也叫fitness_token。注意密碼存儲絕對不能明文。使用BCrypt加密有兩點好處一是每個用戶生成的哈希值不同即使密碼相同存庫結果也不同二是BCrypt自帶鹽值暴力碰撞成本極高。Spring Security的BCryptPasswordEncoder可以直接單獨拿來用不用引入完整的安全框架。3. 前端Vue工程搭建與聯(lián)調(diào)細節(jié)3.1 初始化一個Vue項目并配好路由搭建前端工程有兩個方式Vue CLI和Vite。新項目我更推薦Vite構建速度快開發(fā)體驗好。初始化命令npm create vitelatest fitness-admin -- --template vue創(chuàng)建完成后按照需要安裝路由、狀態(tài)管理和HTTP庫npm install vue-router4 pinia axios element-plusElement Plus是這套系統(tǒng)里非常好用的UI組件庫表格、表單、對話框、分頁這些后臺管理常用的組件都封裝好了。整套后臺頁面如果不用組件庫手寫樣式的工作量至少翻三倍。路由設計是前端最需要提前規(guī)劃的部分。我習慣在路由配置文件里把整個布局拆成兩層外層是Layout包含側(cè)邊欄和頂欄內(nèi)層是具體頁面。所有需要登錄的頁面都放在一個父路由下通過路由守衛(wèi)控制訪問權限。// src/router/index.js const routes [ { path: /login, component: Login }, { path: /, component: Layout, redirect: /dashboard, children: [ { path: dashboard, name: Dashboard, component: Dashboard }, { path: course, name: Course, component: CourseList, meta: { roles: [1, 2] } }, { path: member, name: Member, component: MemberList, meta: { roles: [0] } } ] } ]路由守衛(wèi)的作用是在跳轉(zhuǎn)前檢查登錄態(tài)和角色權限。用戶沒登錄去訪問需要鑒權的頁面直接路由跳轉(zhuǎn)到登錄頁并帶上redirect參數(shù)登錄成功后跳回原來想去的頁面router.beforeEach((to, from, next) { const token localStorage.getItem(fitness_token) if (!token to.path ! /login) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } })3.2 axios封裝與跨域處理axios封裝做得好不好直接決定后面聯(lián)調(diào)是否痛苦。我在項目里封裝了一個request模塊統(tǒng)一處理三件事請求頭自動帶上token、響應數(shù)據(jù)統(tǒng)一解包、錯誤提示統(tǒng)一彈窗。// src/utils/request.js import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(fitness_token) if (token) { config.headers[token] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code 401) { localStorage.removeItem(fitness_token) window.location.href /login return Promise.reject(new Error(未登錄或登錄已過期)) } return res }, error { ElMessage.error(error.response?.data?.message || 網(wǎng)絡異常請稍后重試) return Promise.reject(error) } )開發(fā)環(huán)境直接請求后端地址會遇到跨域問題??缬虻谋举|(zhì)是瀏覽器的同源策略前端跑在8080端口后端跑在8081端口兩者源不同瀏覽器就會攔截響應數(shù)據(jù)。開發(fā)環(huán)境下最簡單的方案是在vite.config.js里配置代理讓前端請求走自己的域名然后轉(zhuǎn)發(fā)到后端// vite.config.js export default defineConfig({ server: { port: 3000, proxy: { /api: { target: http://localhost:8081, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } })這樣前端代碼里請求/api/course/list實際轉(zhuǎn)發(fā)到后端的是http://localhost:8081/course/list。生產(chǎn)環(huán)境部署時同樣用Nginx處理轉(zhuǎn)發(fā)原理是一樣的。3.3 頁面開發(fā)時如何高效對接接口頁面聯(lián)調(diào)最怕的就是沒有接口文檔全靠猜。建議后端在寫接口時順手把參數(shù)和返回值整理清楚。前端拿到接口先別急著寫頁面花10分鐘把接口返回的數(shù)據(jù)結構看明白再設計表格列和表單字段效率會高很多。以課程管理為例前端調(diào)用課程列表接口后數(shù)據(jù)結構一般是這樣的{ code: 200, message: success, data: { total: 35, rows: [ { id: 1, name: 燃脂操, category: 團操, price: 29.9, status: 1, createTime: 2024-05-12 10:30:00 } ] } }前端拿到這個結構后Element Plus的Table組件直接用prop綁定字段名就能渲染。要注意時間字段后端返回的是字符串還是時間戳如果返回的是2024-05-12 10:30:00這樣的格式前端可以直接展示如果是時間戳需要做一次格式化轉(zhuǎn)換。我踩過的坑是后端把時間字段統(tǒng)一做成了字符串拼接導致日期排序完全亂掉。后面改成返回時間戳前端再統(tǒng)一格式化問題就解決了。建議后端時間格式約定成ISO字符串或者時間戳并且保證項目中所有接口風格一致否則前端處理成本很高。3.4 關鍵業(yè)務頁面怎么設計和實現(xiàn)課程預約是核心頁面用戶看到的是一個課程卡片列表加預約按鈕。點擊預約按鈕后前端彈出一個對話框展示課程詳情包括上課時間、授課教練、可約人數(shù)、價格。用戶確認后調(diào)用預約接口后端校驗是否已滿、時間是否沖突、用戶是否重復預約??紤]到真實場景里用戶可能一次預約多節(jié)課課程卡片上要標注當前剩余名額。剩余名額怎么計算每次預約成功時減少名額取消預約時增加名額同時預約時再校驗一次余量。這個邏輯聽起來簡單但并發(fā)場景下需要注意兩個人同時約最后一個名額如果先查詢再更新就會超賣。穩(wěn)妥做法是在更新時帶上條件update coach_schedule set booked_count booked_count 1 where id #{scheduleId} and booked_count max_people受影響行數(shù)為1表示預約成功為0表示名額已滿。這種原子操作比先查再更靠譜得多。教練端頁面主要展示課程日歷每個教練可以看到自己的工作安排。這個功能其實就是把某個教練的排課表查詢出來按日期分組顯示。前端用日歷組件渲染對應日期顯示課程信息。管理端頁面是重頭戲會員列表需要支持條件搜索和分頁課程管理需要支持上下架操作訂單管理需要導出Excel營收統(tǒng)計需要展示圖表。建議這些頁面按模塊獨立開發(fā)先列表再表單再操作按鈕逐步完善。實操心得開發(fā)順序有個技巧。先做后端接口用Postman驗證通過后再寫前端頁面或者先定好接口用Mock數(shù)據(jù)跑通前端再對接真實后端。千萬別兩邊同時開工邊寫邊改不然兩邊互相等排錯時也搞不清問題在前端還是后端。4. 環(huán)境準備與項目部署全流程4.1 本地開發(fā)環(huán)境需要裝哪些東西很多新手卡在環(huán)境配置上這里列一個完整的清單。JDK推薦裝JDK 8或者JDK 11。選JDK 8的原因是穩(wěn)定SpringBoot 2.x系列完全兼容網(wǎng)上搜問題也容易找到答案。JDK 17也不是不行但要確認和SpringBoot版本兼容SpringBoot 2.x對JDK 17的支持會遇到模塊化限制建議直接用JDK 8省心。Maven裝最新版或者3.6都可以重點是要在IDEA里把Maven的settings.xml指到國內(nèi)鏡像否則下載依賴慢得讓人想放棄。鏡像我這里就不寫具體地址了你在搜索引擎搜“Maven國內(nèi)鏡像”就有很多現(xiàn)成的配置。MySQL建議裝5.7或者8.0。安裝時要注意編碼設置成utf8mb4數(shù)據(jù)庫排序規(guī)則選utf8mb4_general_ci。用utf8mb4而不是utf8的原因很簡單utf8mb4能存儲emoji和生僻字utf8存不了。連接串里再加一行參數(shù)保證高版本MySQL的SSL連接不會報錯。Node.js裝16.20 LTS或者18 LTS版本都可以Vite 4要求Node 14.18以上。安裝完以后把npm的registry切換成淘寶源執(zhí)行命令npm config set registry https://registry.npmmirror.com就行。這些環(huán)境裝完后建議先各自驗證一遍JDK用java -versionMaven用mvn -vNode用node -vMySQL用mysql -u root -p登錄測試。確認都沒問題再開始跑項目不然環(huán)境出問題會干擾你判斷項目本身的Bug。4.2 前端打包與Nginx部署Vue項目開發(fā)完成后構建生產(chǎn)產(chǎn)物npm run build構建完會在項目目錄下生成dist文件夾里面就是打包好的靜態(tài)文件。把dist文件夾里的內(nèi)容復制到Nginx的html目錄然后配置Nginx。配置Nginx做兩件事托管靜態(tài)文件反向代理API請求。先看一個最小可用的配置server { listen 80; server_name your-domain.com; root /usr/share/nginx/html/fitness; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files $uri $uri/ /index.html;這行是前端路由模式的關鍵配置。Vue Router默認使用history模式頁面地址看起來是/course/list這種真實路徑。如果刷新頁面Nginx會根據(jù)路徑找文件找不到就返回404。加上這行配置后所有請求兜底返回index.html再由Vue路由接管頁面就正常了。location /api/塊把前端發(fā)往/api/的請求反向代理到后端服務。注意proxy_pass http://127.0.0.1:8081/;末尾的斜杠作用是把/api前綴去掉再轉(zhuǎn)發(fā)保證后端收到的地址和本地開發(fā)時一致。前端有兩種路由模式剛才說的history模式在部署時需要Nginx配合還有一種hash模式URL地址里帶#號部署簡單但不好看。如果你用的是hash模式不需要try_files那行也能正常工作。什么場景用哪種如果域名是給外部訪問的正式項目建議history模式配好Nginx如果是本地演示或者測試環(huán)境hash模式省事不折騰。4.3 后端工程打包與運行全流程后端SpringBoot項目打包前先確認application-prod.yml配置正確。數(shù)據(jù)庫地址、用戶名、密碼換成了生產(chǎn)環(huán)境的值日志輸出級別調(diào)整成info去掉打印SQL的配置。然后執(zhí)行Maven打包命令mvn clean package -Dmaven.test.skiptrue打包成功后在target目錄下會生成一個xxx.jar文件。把這個jar上傳到服務器運行指令java -jar fitness-server.jar默認是在前臺運行關掉終端服務就停了。正式環(huán)境用后臺運行方式nohup java -jar fitness-server.jar fitness.log 21 日志輸出重定向到fitness.log文件里排查問題的時候直接看這個文件。nohup的意思是忽略掛斷信號加上符號讓進程在后臺運行。要停止服務先查進程號再殺掉ps -ef | grep fitness-server kill -9 pid繼承標題里提到的“Tomcat部署前后端分離項目”這里多說一句。如果是用SpringBoot內(nèi)嵌Tomcat打成jar直接運行就行壓根不用單獨裝Tomcat。如果你自己用SpringBoot打的是war包就需要裝一個外置Tomcat把war包丟到webapps目錄下啟動Tomcat后項目自動解壓部署。但既然用SpringBoot了沒必要退回傳統(tǒng)做法內(nèi)置Tomcat的jar包部署方式更簡單也更好維護。4.4 服務器端運行環(huán)境的快速搭建在Linux服務器上搭建整個運行環(huán)境核心要做這么幾件事# 安裝JDK 8以通用方式為例 yum install java-1.8.0-openjdk # 檢查安裝 java -version # 安裝Nginx yum install nginx systemctl start nginx # 安裝MySQL yum install mysql-server systemctl start mysqld # 設置MySQL開機自啟 systemctl enable mysqld # 導入項目數(shù)據(jù)庫 mysql -u root -p fitness.sqlJDK、Nginx、MySQL都裝好并啟動后按照前面說的方法啟動后端jar再把前端dist目錄放到Nginx的站點目錄整個系統(tǒng)就通了。驗證方式很簡單瀏覽器訪問服務器IP地址看到登錄頁面說明前端通了輸入賬號密碼能登錄說明前后端聯(lián)調(diào)沒問題。有一個經(jīng)常被忽視的點服務器的防火墻端口。如果前端頁面能打開但接口請求不通十有八九是8081端口沒開或者說Nginx轉(zhuǎn)發(fā)沒配好。檢查端口監(jiān)聽情況用命令netstat -tlnp | grep 8081如果是8080和8081這類非80端口還需確認安全組規(guī)則里放行了對應端口。4.5 演示環(huán)境的完整驗證清單項目部署完成后不能只看頁面能打開就算交付。我會按業(yè)務主流程完整走一遍確認每個模塊都在真實環(huán)境跑通。驗證清單大概是這樣的注冊一個新會員看看登錄后角色是否正確管理員創(chuàng)建課程、添加教練排課會員瀏覽課程列表預約一門課程教練端查看預約名單和排課表管理員查看預約統(tǒng)計和營收訂單會員取消預約后名額是否正常釋放所有頁面刷新后不出現(xiàn)404退出登錄后訪問受限頁面自動跳登錄頁每個環(huán)節(jié)都走一遍發(fā)現(xiàn)問題就修掉確認全通后才能把這個項目交付出去。很多人項目做完沒驗證就直接提交了答辯現(xiàn)場演示時突然崩了那場面確實是災難。注意部署到服務器和本地環(huán)境是有差異的。本地跑通的項目到服務器上很可能因為目錄路徑、數(shù)據(jù)庫名、端口占用等原因出問題務必按上面的清單在服務器環(huán)境完整驗證一遍。不要想當然認為“本地通了服務器肯定也通”。5. 常見問題與排查技巧實錄5.1 后端啟動失敗的幾個高頻坑啟動后端口被占用是出現(xiàn)頻率最高的問題。有時候是上次的進程沒殺掉有時候是另一個項目占用了同一端口。排查辦法lsof -i :8081找到占用端口的進程號殺完之后再重新啟動。如果8081端口被系統(tǒng)進程占用了最簡單的辦法是換一個端口在application.yml里改server.port同時把Nginx的代理地址同步改掉。數(shù)據(jù)庫連接失敗也比較常見SpringBoot啟動時報錯Communications link failure。絕大多數(shù)原因是MySQL沒啟動、密碼錯誤、或者連接串寫錯。逐個排查# 確認MySQL在運行 systemctl status mysqld # 確認能登錄MySQL mysql -u root -p # 確認連接串里的端口號 mysql -h localhost -P 3306 -u root -pMySQL 8.0啟動出錯還要檢查連接串里有沒有加useSSLfalse。默認會啟用SSL連接而本地MySQL實例沒有正確配置SSL證書時就會報錯。連接串這樣寫保證沒問題url: jdbc:mysql://localhost:3306/fitness?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai5.2 MyBatis相關報錯與排查“Invalid bound statement (not found)”是我在后端項目里遇到頻率最高的報錯。這個報錯的意思是Mapper接口的方法和XML里對應的id對不上或者XML文件根本沒被加載進來。排查順序如下確認Mapper接口方法名和XML的id完全一致包括大小寫確認XML文件被Maven打進了jar包檢查target目錄下有沒有對應的XML文件確認application.yml里的mapper-locations路徑配置正確“TooManyResultsException”也偶爾碰到意思是查詢結果有多條記錄但方法返回值寫的單對象。這個錯誤一般是SQL寫的條件不準查出了多條記錄排查SQL條件和數(shù)據(jù)或者改方法返回值為List。分頁時的坑也要說一下。如果用了PageHelper分頁插件注意查詢結果的類型是PageInfo要用new PageInfo(list)拿到總條數(shù)直接list.getTotal()會提示找不到getTotal方法。另外PageHelper的分頁參數(shù)只在緊跟著的下一條SQL生效想分頁哪個查詢就要在它前面緊貼著設置分頁中間不能夾雜其他SQL。5.3 前端常見報錯與解決思路前端代碼運行報錯最多的地方首推跨域。報錯信息通常長這樣Access to XMLHttpRequest at http://localhost:8081/... from origin http://localhost:3000 has been blocked by CORS policy。出現(xiàn)這個錯誤第一個要確認的是你有沒有走代理。如果你在代碼里把baseURL寫死了http://localhost:8081那請求根本沒有走vite代理而是瀏覽器直接跨域調(diào)用代理配置就白寫了。正確做法是baseURL寫/api由代理轉(zhuǎn)發(fā)到后端。前端另一個常見問題是打包后頁面白屏控制臺報錯Failed to fetch dynamically imported module。這個一般是路由懶加載的文件路徑寫錯了或者構建產(chǎn)物沒上傳完整。先看瀏覽器Network面板里面哪個js文件404了再對著修正路徑或者重新構建一次再上傳。Vue 3 Element Plus還有一個經(jīng)典問題就是圖標點擊不顯示。Element Plus從1.x版本開始圖標庫單獨拆成了element-plus/icons-vue包你沒有安裝或者沒有全局注冊圖標就是空白的。在main.js里面注冊import * as ElementPlusIconsVue from element-plus/icons-vue for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) }5.4 登錄后跳回登錄頁的排查思路前后端分離項目里“點擊按鈕后莫名其妙跳回登錄頁”也是高頻問題。先判斷是不是所有請求都跳登錄頁還是個別接口跳。如果所有請求都跳大概率是token寫入不一致。比如后端要求請求頭參數(shù)叫token前端卻用Authorization去傳后端解析不到統(tǒng)一返回401路由守衛(wèi)就把頁面重定向到登錄頁了?;蛘遲oken過期也有同樣效果。如果是個別接口跳那就是后端某些接口的攔截配置有問題。比如你登錄接口動態(tài)更新用戶信息后沒把新token返回給前端用舊token訪問新接口時就過期了。或者某個接口路徑不小心被排除在攔截范圍外了導致該鑒權的接口沒有鑒權。排查這類問題最快的辦法是把瀏覽器Network面板打開看接口返回的狀態(tài)碼和響應體。是401還是403響應信息提示是“未登錄”還是“無權限”看準了再動手改不要瞎調(diào)代碼。5.5 部署環(huán)節(jié)設計一個快速自查表結合上述經(jīng)驗整理一張排查速查表按順序檢查能解決90%的部署問題?,F(xiàn)象可能原因排查命令/操作頁面打不開Nginx未啟動或配置錯誤systemctl status nginxnginx -t 檢查配置前端頁面能開但接口404反向代理路徑配置錯誤檢查location /api塊的proxy_pass配置接口請求跨域前端沒走代理直接請求后端檢查baseURL是否為/api瀏覽器Network看請求URL后端啟動報端口被占用端口被其他進程占用lsof -i 查看占用進程并處理后端啟動報數(shù)據(jù)庫連接失敗MySQL未啟動或密碼錯誤systemctl status mysqldmysql -u root -p 測試登錄登錄后接口401/跳登錄頁token傳參名稱不一致或過期檢查前端請求攔截器headers配置刷新頁面404前端路由history模式?jīng)]配try_filesNginx中l(wèi)ocation /增加try_files配置這張表你在部署時對照著排查能省下大量查資料的時間。我自己第一次部署前后端分離項目時因為Nginx沒配try_files所有頁面刷新都404查了半天才想到是這個問題。有了這張表后來再部署就順手多了。結尾這個健身俱樂部網(wǎng)站系統(tǒng)整套做下來覆蓋了前后端分離開發(fā)的全部核心環(huán)節(jié)業(yè)務建模、數(shù)據(jù)庫設計、RESTful API開發(fā)、JWT鑒權、Vue組件化開發(fā)、Nginx部署。如果你正在找畢業(yè)設計選題或者想豐富項目經(jīng)歷按這套思路把代碼寫透收獲會遠遠超過看一遍教程。個人經(jīng)驗中最深的體會是環(huán)境準備和部署環(huán)節(jié)看似不起眼但坑最多。寧可開工前多花半天把JDK、MySQL、Node、Nginx這些環(huán)境都弄利索也不要寫到一半再來查環(huán)境問題那樣非常打斷思路。另一個建議是保留一份部署自查文檔每次換機器部署都照著跑出了問題時排查效率會高很多。最后再分享一個實用的小技巧整個系統(tǒng)跑通之后建議用Postman把主要接口整理成一份接口集合導出成JSON存到項目倉庫里。后面不管是自己重構還是別人接手都能快速看懂接口定義不用費力去代碼里翻。項目交付的世界里“文檔完善”這件事的分數(shù)占比可能比你想象中高很多。