:狀態(tài)流轉與并發(fā)控制實戰(zhàn))
簡介面向高校計算機專業(yè)畢業(yè)設計的流浪動物救助數(shù)據(jù)庫項目使用SpringBoot框架與微信小程序技術構建覆蓋后端Java服務、小程序前端展示、數(shù)據(jù)庫表結構及完整部署說明。系統(tǒng)圍繞流浪動物信息登記、領養(yǎng)申請與救助記錄等核心功能展開能幫助開發(fā)者深入理解前后端分離架構、小程序生命周期及接口聯(lián)調流程。壓縮包共681個文件以97個Java源碼、96個Vue頁面、61個JavaScript腳本、22個WXSS樣式與21個WXML模板等小程序開發(fā)文件為主附有演示視頻、一鍵啟動腳本、SQL初始化文件和多種圖標素材整體體積約49.97MB目錄組織層次分明便于按模塊檢索學習。項目已在Windows10/11環(huán)境嚴格調試答辯評審獲得97分配套使用文檔、部署教程及全部項目資料下載配置后即可運行既適用于畢業(yè)設計參考也能作為期末課程作業(yè)的完整示例。目前已有208人學習下載對于正在尋找全棧實戰(zhàn)項目或入門小程序開發(fā)的Java學習者這套項目能提供從代碼到部署的完整參考。1. 流浪動物救助程序難點不是CRUD而是狀態(tài)流轉拿到這個壓縮包第一眼看到的是五樣東西SpringBoot后端、微信小程序端、救助數(shù)據(jù)庫、使用文檔、演示視頻。畢業(yè)設計做成這個標題真正要回答的問題不是“有多少張表”而是當一只動物同時有三個人提交領養(yǎng)申請時系統(tǒng)怎樣保證只有一個人能領養(yǎng)成功。我第一次做這類項目也會先把動物檔案、領養(yǎng)申請、救助記錄各建一張表跑起來之后才發(fā)現(xiàn)狀態(tài)字段不一致會帶來一堆臟數(shù)據(jù)。這篇文章會把數(shù)據(jù)庫怎么設計、SpringBoot接口怎么把狀態(tài)流轉鎖住、小程序端怎么把列表和表單串起來講清楚適合準備SpringBoot框架面試題時想找一個完整業(yè)務鏈路的讀者也適合開始接觸微信小程序開發(fā)想要一個能答辯的項目樣板的人。2. 拆解救助數(shù)據(jù)庫從ER圖到SpringBoot實體與表結構做數(shù)據(jù)庫課程設計的時候習慣先從ER圖開始。流浪動物救助這個業(yè)務域第一版只需要四張表動物檔案、領養(yǎng)申請、救助記錄、小程序用戶。實體之間的關系不復雜真正的復雜度來自“狀態(tài)聯(lián)動”領養(yǎng)申請審核通過時動物要從待領養(yǎng)變成已領養(yǎng)同時該動物其他待審核的申請必須全部作廢。這個動作如果只在頁面里改一個字段數(shù)據(jù)很快就會臟。表名業(yè)務含義關鍵字段animal動物檔案id, name, species, gender, status, cover_url, locationadopt_application領養(yǎng)申請id, animal_id, user_id, applicant_name, phone, statusrescue_record救助記錄id, animal_id, rescuer_name, phone, address, descriptionsys_user小程序用戶id, openid, nickname, phone, role救助記錄和動物檔案是邏輯上的多對一一只動物可能被同一個人救助過多次救助記錄只負責留痕不參與領養(yǎng)狀態(tài)判斷。領養(yǎng)申請和動物檔案是多對一和用戶也是一對多。這個項目不建物理外鍵用邏輯外鍵加索引就夠了否則小程序端刪除種子數(shù)據(jù)的時候會頻繁觸發(fā)外鍵約束演示反而卡住。2.1 救助域的三類核心實體與關系實體關系理清之后第二件事是給狀態(tài)字段定邊界。動物狀態(tài)和申請狀態(tài)不能混在一個枚舉里否則業(yè)務邏輯會越來越繞。我的建議是動物表單獨維護 status領養(yǎng)申請表單獨維護 status兩個狀態(tài)通過業(yè)務規(guī)則同步。2.1.1 動物狀態(tài)與申請狀態(tài)的邊界動物狀態(tài)建議用四個值0 待審核、1 待領養(yǎng)、2 已領養(yǎng)、3 已下架。領養(yǎng)申請狀態(tài)用四個值0 待審核、1 已通過、2 已拒絕、3 已取消。救援人員提交救助記錄后默認進入待審核管理員審核通過后變成待領養(yǎng)用戶提交領養(yǎng)申請后管理員審核通過動物狀態(tài)變成已領養(yǎng)同時把其他待審核申請置為已拒絕。這里最容易錯的是認為“動物狀態(tài) 最新一條申請狀態(tài)”。實際上申請狀態(tài)只是流程單據(jù)狀態(tài)動物狀態(tài)才是業(yè)務狀態(tài)。比如動物體檢不過被下架狀態(tài)變成 3這時它的待審核申請仍然可能是 0需要服務層在下架時統(tǒng)一處理。把狀態(tài)邊界寫在表注釋里比寫在代碼注釋里更有效因為建表腳本會被多人反復導入。2.2 用Navicat建表的字段與索引設計建表腳本可以直接導入Navicat執(zhí)行。我一般會關閉外鍵檢查避免初始化順序導致導入失敗。下面是核心的建表語句已經去掉了不必要的冗余字段CREATE TABLE animal ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL DEFAULT COMMENT 動物昵稱, species VARCHAR(20) NOT NULL COMMENT cat/dog/rabbit, gender TINYINT NOT NULL DEFAULT 0 COMMENT 0未知 1公 2母, age_months INT NOT NULL DEFAULT 0, health_status VARCHAR(100) NOT NULL DEFAULT 待體檢, location VARCHAR(120) NOT NULL DEFAULT COMMENT 發(fā)現(xiàn)地或收容地址, cover_url VARCHAR(255) NOT NULL DEFAULT , status TINYINT NOT NULL DEFAULT 0 COMMENT 0待審核 1待領養(yǎng) 2已領養(yǎng) 3已下架, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_status_create (status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT動物檔案; CREATE TABLE adopt_application ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, animal_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL, applicant_name VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, reason VARCHAR(500) NOT NULL DEFAULT , housing_info VARCHAR(500) NOT NULL DEFAULT COMMENT 居住情況, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待審核 1已通過 2已拒絕 3已取消, apply_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, audit_time DATETIME DEFAULT NULL COMMENT 審核時間, KEY idx_animal_status (animal_id, status), KEY idx_user_apply (user_id, apply_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT領養(yǎng)申請表;id 使用 BIGINT UNSIGNED避免以后數(shù)據(jù)量超過 INT 上限。狀態(tài)字段用 TINYINT 而不是 INT節(jié)省行空間InnoDB 行越小單頁能存的記錄越多列表查詢越占優(yōu)勢。create_time 和 update_time 交給數(shù)據(jù)庫默認值維護Java 實體里只做讀取不手動 set。索引方面首頁列表最常見的查詢是where status ? order by create_time desc所以建聯(lián)合索引 idx_status_create領養(yǎng)審核頁最常見的查詢是where animal_id ? and status ?所以建 idx_animal_status。這里不建議在 animal_id 和 status 上建唯一索引因為業(yè)務規(guī)則要求的是“同一動物只有一個狀態(tài)為 0 的待審核申請”MySQL 普通唯一索引做不到只對某個狀態(tài)值唯一。要不依賴數(shù)據(jù)庫約束把這個校驗放到 Service 層事務里邏輯更直觀。2.3 MyBatis-Plus還是Spring Data JPA選型與代碼落地做 SpringBoot 項目ORM 選 MyBatis-Plus 最常見。理由很實際單表 CRUD 完全不用寫 SQL繼承 BaseMapper 就有 selectById、selectPage、updateById聯(lián)表查詢寫 Xml 也能接住。Spring Data JPA 的派生查詢寫起來也省事但面試時導師更容易追問代理對象、懶加載、N1 問題畢業(yè)設計時間緊的話沒必要給自己加戲。pom 里引入 mybatis-plus-boot-starter 就行版本選你之前跑通過的一個 3.5.x不要追最新SpringBoot 版本太高反而會出現(xiàn)分頁插件攔截器不兼容的情況。實體類和表字段做映射注意 MyBatis-Plus 默認把駝峰轉下劃線所以 Java 屬性寫 createTime表字段寫 create_time不需要額外配置。TableName(animal) public class Animal { TableId(type IdType.AUTO) private Long id; private String name; private String species; private Integer gender; private Integer ageMonths; private String healthStatus; private String location; private String coverUrl; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; }接口層再寫一個 Mapper 接口繼承 BaseMapper。TableName指定表名TableId(type IdType.AUTO)對應自增主鍵。animal 表里有 localDateTime 字段數(shù)據(jù)庫 URL 參數(shù)里記得配 serverTimezoneAsia/Shanghai否則本機時間和服務器時間對不上演示視頻里會出現(xiàn)“剛提交的申請時間比當前時間晚 8 小時”這種尷尬。3. SpringBoot后端救助狀態(tài)機與小程序端API實現(xiàn)后端接口設計最怕兩種極端一種是按頁面拆接口比如 getIndexData、getDetailData另一種是每個表單獨暴露 CRUD小程序端可以直接改任意字段。正確做法是把接口按業(yè)務流程拆小程序端只能看到自己需要的操作比如提交領養(yǎng)申請、查詢申請記錄、上傳救助信息不能直接跨表改狀態(tài)。3.1 REST API 的路徑設計與統(tǒng)一返回體接口路徑建議這樣劃分方法路徑說明GET/api/animals?statusspeciespage1size10分頁查詢動物列表GET/api/animals/{id}查詢動物詳情POST/api/adopt/apply提交領養(yǎng)申請PUT/api/adopt/{id}/audit?passtrue管理員審核申請GET/api/rescue/records查詢救助記錄統(tǒng)一返回體使用一個 Result 泛型類code 為 0 表示成功非 0 表示業(yè)務失敗。不要直接用 HTTP 狀態(tài)碼做業(yè)務判斷因為小程序端和后端之間的網(wǎng)關可能會重寫狀態(tài)碼而且 200 之外的響應在 wx.request 里也能拿到但處理邏輯會碎。public class ResultT { private int code; private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 0; r.msg ok; r.data data; return r; } public static T ResultT error(int code, String msg) { ResultT r new Result(); r.code code; r.msg msg; return r; } }寫 Controller 的時候方法參數(shù)用 RequestParam 給默認值分頁參數(shù) page 和 size 一定不能讓用戶傳負數(shù)。MyBatis-Plus 的 Page 對非法值會做修正但業(yè)務上最好在入口就擋住。GetMapping(/animals) public ResultPageAnimal list(RequestParam(defaultValue 1) long page, RequestParam(defaultValue 10) long size, RequestParam(required false) Integer status) { LambdaQueryWrapperAnimal wrapper Wrappers.lambdaQuery(); wrapper.eq(status ! null, Animal::getStatus, status) .orderByDesc(Animal::getCreateTime); return Result.ok(animalMapper.selectPage(new Page(page, size), wrapper)); }eq 方法第一個參數(shù)是布爾條件status 為 null 時不拼這個條件避免出現(xiàn)where status null。orderByDesc 直接用 Lambda 屬性引用不會因為字段改名而漏改字符串。3.2 領養(yǎng)申請的狀態(tài)流轉實現(xiàn)事務與行鎖審核領養(yǎng)申請是整個項目最核心的業(yè)務。很多人只寫了普通 select再 update并發(fā)時會出大問題。兩個管理員同時審核同一只動物的不同申請都查到動物狀態(tài)是待領養(yǎng)于是兩個都通過動物就被“雙領養(yǎng)”了。解決辦法是先鎖行再判斷。Transactional(rollbackFor Exception.class) public void auditApply(Long applyId, boolean pass) { AdoptApplication apply adoptApplicationMapper.selectById(applyId); if (apply null || apply.getStatus() ! 0) { throw new BusinessException(申請不存在或已審核); } Animal animal animalMapper.selectByIdForUpdate(apply.getAnimalId()); if (animal null || animal.getStatus() ! 1) { throw new BusinessException(該動物當前不可領養(yǎng)); } if (pass) { animal.setStatus(2); animalMapper.updateById(animal); apply.setStatus(1); apply.setAuditTime(LocalDateTime.now()); adoptApplicationMapper.updateById(apply); LambdaUpdateWrapperAdoptApplication update Wrappers.lambdaUpdate(); update.eq(AdoptApplication::getAnimalId, apply.getAnimalId()) .eq(AdoptApplication::getStatus, 0) .ne(AdoptApplication::getId, applyId) .set(AdoptApplication::getStatus, 2); adoptApplicationMapper.update(null, update); } else { apply.setStatus(2); apply.setAuditTime(LocalDateTime.now()); adoptApplicationMapper.updateById(apply); } }selectByIdForUpdate 不是 MyBatis-Plus 自帶方法直接寫在 AnimalMapper 上即可Select(SELECT * FROM animal WHERE id #{id} FOR UPDATE) Animal selectByIdForUpdate(Param(id) Long id);這段邏輯有四個關鍵點。第一先鎖 animal 行再查申請和更新申請鎖順序固定避免兩個事務各自鎖了不同行然后互相等待的死鎖。第二FOR UPDATE會把 animal 這一行鎖到當前事務提交第二個并發(fā)事務執(zhí)行同樣的 selectForUpdate 會阻塞直到第一個事務提交后重新讀取此時 animal.status 已經是已領養(yǎng)進入“不可領養(yǎng)”分支。第三Transactional 默認只對 RuntimeException 回滾這里指定 rollbackFor Exception.classBusinessException 也是繼承 RuntimeException所以業(yè)務異常同樣觸發(fā)回滾。第四批量把其他申請置為 2 時用 ne 排除當前審核通過的這一條避免把自己的申請重新寫成已拒絕。3.2.1 為什么不直接用樂觀鎖樂觀鎖也可以做比如 animal 表加 version 字段update 時帶 version 條件影響行數(shù)為 0 就失敗。但這種情況需要前端重試或者后端循環(huán)重試邏輯分發(fā)到多個入口后很容易漏。單機部署的畢業(yè)設計悲觀鎖反而清晰鎖的那一行代碼就代表了“這只動物同時只有一個審核事務能操作”。如果你面試被問到高并發(fā)可以補充說生產環(huán)境會把鎖上移到 Redis但演示時不用。3.3 SpringBoot配置、yml密文與上傳參數(shù)從 zip 里拿到項目后最常改的就是 application.yml。數(shù)據(jù)庫密碼、文件上傳大小、MyBatis-Plus 映射配置都在這一個文件里。建議密碼用 Jasypt 加密不要明文提交到壓縮包。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/animal_shelter?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: ENC(這里放密文) servlet: multipart: max-file-size: 5MB max-request-size: 20MB mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jasypt: encryptor: password: ${JASYPT_PASSWORD}啟動時指定環(huán)境變量java -jar app.jar --JASYPT_PASSWORD你的密鑰。這里的密碼不是數(shù)據(jù)庫密碼是 Jasypt 加密用的鹽配置文件里的 ENC() 密文必須用它才能解開。multipart 配置只控制 SpringBoot 接受的文件大小如果項目前端用 Nginx 轉發(fā)上傳請求Nginx 的 client_max_body_size 也要同步放開否則前端報 413但后端日志里什么都沒有。SpringBoot 版本如果特別新jasypt-spring-boot-starter 要用適配 SpringBoot 3 的分支否則啟動會直接報自動配置類不存在。這種問題很典型屬于“springboot版本太高”導致的依賴兼容性踩坑。不想引 Jasypt 的話另一個更簡單的方案是數(shù)據(jù)庫密碼不寫進 yml用環(huán)境變量password: ${DB_PASSWORD}部署時在系統(tǒng)環(huán)境變量里設置同樣能避免明文入庫。3.4 接口調試從Postman腳本到Burp Suite抓取小程序請求后端接口寫完先用 Postman 過一遍最基礎的 CRUD確認返回結構是 Result 的 JSON 格式。然后在小程序開發(fā)者工具里把 BASE_URL 指向本機局域網(wǎng) IP真機預覽時手機和電腦連同一個 WiFi后端要在 application.yml 里加server.address0.0.0.0否則真機訪問不到。如果需要查看小程序實際發(fā)出去的請求體、響應體、請求頭可以用 Burp Suite。常見做法是Burp Suite 里新建一個監(jiān)聽 127.0.0.1:8080 的項目微信開發(fā)者工具右上角“詳情 - 本地設置”里把請求流量指向這個地址再安裝并信任 Burp 的 CA 證書就能在 Burp 里看到 wx.request 從構建請求到收到響應的完整過程。這里要注意抓包工具只能幫你看請求不能幫你改數(shù)據(jù)庫調試狀態(tài)聯(lián)動還是要在后端日志里打關鍵節(jié)點。4. 微信小程序端列表、詳情、狀態(tài)更新與表單校驗小程序端更接近一個展示和錄入層業(yè)務規(guī)則盡量不放前端。列表頁從 /api/animals 拉數(shù)據(jù)詳情頁打開后展示動物檔案和一個“申請領養(yǎng)”按鈕申請頁提交表單個人中心查看我的申請記錄和審核狀態(tài)。做到這四個頁面項目演示就已完整。4.1 小程序目錄結構與導航欄適配小程序端目錄結構建議這樣組織miniprogram/ app.js app.json pages/ index/ detail/ apply/ mine/ utils/ request.js images/app.json 里 pages 數(shù)組的第一項是啟動頁。很多新手遇到每次編譯都進錯頁面問題就出在這里。要修改剛進入的加載頁面直接調整 pages 數(shù)組順序而不是去 project.config.json 里找入口。頂部導航欄高度是小程序開發(fā)里的高頻問題。如果 app.json 里設置了自定義導航navigationStyle: custom頁面頂部狀態(tài)欄會空出一塊直接寫死 px 會在不同機型上錯位。正確做法是拿系統(tǒng)狀態(tài)欄高度和膠囊按鈕位置計算function getNavInfo() { const windowInfo wx.getWindowInfo(); const menu wx.getMenuButtonBoundingClientRect(); const navBarHeight (menu.top - windowInfo.statusBarHeight) * 2 menu.height; return { statusBarHeight: windowInfo.statusBarHeight, navBarHeight: navBarHeight, menuHeight: menu.height }; }這個公式把膠囊按鈕到狀態(tài)欄的距離翻倍加上膠囊自身高度就是自定義導航欄的完整高度。用它去設置容器 paddingTop頁面內容就不會被劉海屏和狀態(tài)欄遮擋。4.2 用Promise封裝wx.request并處理加載頁wx.request 默認回調風格頁面一多會出現(xiàn)多層嵌套。先封裝一層 Promise后續(xù)頁面統(tǒng)一調 request 方法返回體已經幫終端把 Result 拆好了。const BASE_URL http://192.168.1.100:8080/api; function request(url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method: method, data: data, header: { Content-Type: application/json }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg || 請求失敗, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 網(wǎng)絡異常, icon: none }); reject(err); } }); }); } module.exports { request };加載頁的體驗問題是列表頁首次進入容易白屏。原因很簡單onLoad 發(fā)請求是異步的頁面 onReady 先執(zhí)行數(shù)據(jù)回來后 setData 才觸發(fā)渲染。處理辦法是在 onLoad 里 wx.showLoading在請求結束后的 finally 里 wx.hideLoading。如果用了自定義導航欄wx.showNavigationBarLoading 不顯示所以不要依賴它。4.3 表單提交與radio-group單選框的坑申請頁的表單至少要包含姓名、電話、申請理由、居住情況。物種和性別篩選在列表頁用 radio-group 實現(xiàn)WXML 寫法radio-group bindchangeonSpeciesChange label wx:for{{speciesList}} wx:keyvalue classspecies-item radio value{{item.value}} checked{{item.checked}} color#07c160 / text{{item.label}}/text /label /radio-group對應的 JSdata: { speciesList: [ { value: cat, label: 貓, checked: true }, { value: dog, label: 狗, checked: false } ] }, onSpeciesChange(e) { const selected e.detail.value; this.setData({ selectedSpecies: selected }); }這里有個典型坑radio 的 checked 必須是布爾值如果后端返回的 0/1 直接放進 WXML字符串false在部分基礎庫版本里會被當作選中態(tài)導致兩個單選框同時高亮。正確做法是在接口返回后手動轉換成布爾值。表單提交時手機號校驗用正則const phoneReg /^1[3-9]\d{9}$/; if (!phone || !phoneReg.test(phone)) { wx.showToast({ title: 請輸入正確的手機號, icon: none }); return; }校驗通過后再調 request 提交提交按鈕加 disabled 狀態(tài)防止用戶連續(xù)點擊產生兩條申請這樣后端就算沒加防重前端也擋住了大部分重復請求。4.4 小程序與web-view內嵌H5的頁面坑有的項目會把救助協(xié)議或多頁面的免責聲明用 web-view 內嵌 H5。常見問題有兩個一個是“微信小程序內嵌H5工具欄左側返回箭頭沒有了”還有一個是 H5 頁面內跳轉后返回層級錯亂。出現(xiàn)返回箭頭消失先檢查承載 web-view 的那個頁面的導航欄配置。如果這個頁面設置了自定義導航返回箭頭會被自己的自定義按鈕蓋住或覆蓋web-view 全屏時原本由宿主提供的導航 UI 就不穩(wěn)定。解決辦法是不對 web-view 頁面開自定義導航如果必須自定義就在 H5 頁面用 wx.miniProgram.postMessage 告訴小程序原生層手動控制返回。另一個做法是 H5 內部用 history.back() 自己維護返回而不依賴小程序的導航棧。web-view 和業(yè)務頁通信時建議把參數(shù)通過 URL 查詢串傳遞比如/pages/webview/webview?urlxxxanimalId1這樣 H5 側原生 JS 拿到的參數(shù)和后端日志里的請求參數(shù)能一一對上排查問題也容易。5. 用演示視頻反推驗收清單從數(shù)據(jù)庫腳本到高分答辯一個高分項目的演示視頻不是隨便點兩下就行的它其實暴露了完整驗收路徑。拿到 zip 后先看演示視頻里做了什么再反過來檢查自己能不能復現(xiàn)這個順序比直接看代碼更高效。5.1 演示視頻里必須出現(xiàn)的4個場景演示場景操作步驟答辯可說點首頁列表進入小程序按狀態(tài)篩選動物分頁、狀態(tài)過濾、數(shù)據(jù)庫索引提交領養(yǎng)申請打開詳情填寫表單提交參數(shù)校驗、申請寫入審核通過后端或管理端調用審核接口行鎖、事務、狀態(tài)聯(lián)動數(shù)據(jù)復查Navicat 查詢申請和動物兩張表索引設計、邏輯外鍵取舍演示視頻里如果有管理端頁面那么管理端代碼也必須在 zip 中存在。很多資料包會混入兩個版本的后端演示視頻里調用的接口和當前代碼對不上答辯時現(xiàn)場操作直接露餡。5.2 數(shù)據(jù)庫初始化腳本的三個檢查點先執(zhí)行命令導入數(shù)據(jù)庫mysql -u root -p animal_shelter init.sql如果導入報錯優(yōu)先看三個檢查點。第一init.sql 里是否有 CREATE DATABASE 和 USE保證庫存在第二表是否都用了 InnoDB 和 utf8mb4整庫導出的腳本常見 DEFINER 綁定原用戶需要手動去掉第三演示數(shù)據(jù)覆蓋 status 的 0、1、2 三種狀態(tài)沒有已領養(yǎng)數(shù)據(jù)就沒法演示審核通過后的聯(lián)動效果。5.3 從zip到能run的最后一公里使用文檔如果照著做還是起不來大概率卡在三個本地差異application.yml 里的數(shù)據(jù)庫密碼不對、小程序 appid 不是自己的、BASE_URL 寫的 localhost。逐項改成自己的環(huán)境后后端執(zhí)行mvn spring-boot:run前端在微信開發(fā)者工具里導入項目本地開發(fā)時勾選“不校驗合法域名”真機預覽再改成局域網(wǎng) IP。把這一套動作在答辯前親手走一遍從數(shù)據(jù)庫腳本到演示視頻里最后一個點擊能全部復現(xiàn)項目基本不會卡在半路。本文還有配套的精品資源點擊獲取