系統(tǒng)畢業(yè)設(shè)計:狀態(tài)機、Spring Boot與避坑指南)
簡介一套基于Java技術(shù)實現(xiàn)的菜鳥驛站快遞分發(fā)管理系統(tǒng)面向計算機專業(yè)畢業(yè)設(shè)計或課程設(shè)計人群可用于快速搭建演示項目或作為二次開發(fā)基線。資源圍繞用戶下單、快件入庫、取件、物流跟蹤等典型業(yè)務(wù)場景完整覆蓋了需求分析、總體設(shè)計、詳細設(shè)計及功能測試等環(huán)節(jié)涉及前端頁面、后端邏輯與數(shù)據(jù)庫交互。壓縮包內(nèi)共385個文件以111個Java源文件、20個XML配置、2個SQL數(shù)據(jù)庫腳本為主體同時集成162個SVG圖標、16個JS及13個PNG前端資源并附有BAT構(gòu)建腳本和環(huán)境配置說明整體僅10.65MB部署輕量。項目內(nèi)置1-install.bat、2-run.bat、3-build.bat等自動化腳本結(jié)合清晰目錄分層可快速啟動并觀察系統(tǒng)運行效果無需復(fù)雜環(huán)境調(diào)試。已有832人學習適合希望參考完整管理系統(tǒng)源碼、掌握JavaWeb工程化開發(fā)步驟的在校生與初級開發(fā)者。1. 拿到zip之后菜鳥驛站快遞分發(fā)系統(tǒng)到底是個什么畢設(shè)很多人拿到這類zip第一反應(yīng)是解壓然后找“論文”或“使用說明”這個動作不算錯但更該先想清楚標題里的“菜鳥驛站”是業(yè)務(wù)場景“快遞分發(fā)系統(tǒng)”是軟件類型“計算機畢業(yè)生設(shè)計”是使用場景zip只是交付形式。把它組合起來就是一套典型的Web畢設(shè)管理員后臺負責快遞入庫、生成取件碼、處理逾期用戶端負責憑手機號和取件碼取件。它出現(xiàn)在畢業(yè)生手里是因為業(yè)務(wù)邊界清楚、能落地的功能點夠多、演示又直觀屬于萬能選題里比較討巧的一種。這套系統(tǒng)值不值得往深做取決于你怎么回答三個問題包裹從進站到出庫的狀態(tài)在系統(tǒng)里怎么流轉(zhuǎn)取件碼如何生成、如何保證不重復(fù)逾期件和統(tǒng)計報表怎么在不堆功能的前提下自圓其說。這篇筆記按“業(yè)務(wù)設(shè)計—核心流程—工程改造—部署避坑—答辯自檢”的順序把能直接照著改的做法寫清楚新手跟得上熟手能直接拿去當checklist。2. 先別寫代碼快遞狀態(tài)機與六張核心表的設(shè)計很多人拿到zip就直接mvn spring-boot:run跑是能跑但答辯老師一問“你這套系統(tǒng)的核心狀態(tài)是怎么設(shè)計的”就卡殼。這類項目看起來功能多本質(zhì)只有一個核心業(yè)務(wù)包裹到達驛站后如何入庫、暫存、被取走以及在這個過程里如何把狀態(tài)和動作留在數(shù)據(jù)庫里。這一章先把業(yè)務(wù)拆成一個狀態(tài)機再落到表結(jié)構(gòu)上后面寫代碼才不翻車。2.1 三個角色、六種狀態(tài)把業(yè)務(wù)流程變成數(shù)據(jù)流轉(zhuǎn)先分清角色。菜鳥驛站快遞分發(fā)系統(tǒng)里通常有三類使用者快遞員或入庫員負責把包裹錄入系統(tǒng)、打印/生成取件碼、放到貨架站內(nèi)管理員負責處理逾期、異常退回、看統(tǒng)計報表用戶憑手機號和取件碼取件。常見實現(xiàn)里快遞員和管理員共用一張admin表用role字段區(qū)分前端少一套登錄頁面后端也能少寫一套登錄過濾器答辯時講“按角色收斂權(quán)限”反而更好說。角色核心動作頁面入口入庫員/快遞員錄單號、選貨架、生成取件碼、入庫入庫頁面站內(nèi)管理員查看滯留件、處理逾期/退回、統(tǒng)計管理后臺取件用戶輸手機號取件碼取件、查我的包裹取件/查詢頁面包裹的狀態(tài)建議按六態(tài)理解已入庫、已提醒、已出庫、逾期、異常退貨、丟失。實際zip包里的表可能只落前三態(tài)后三態(tài)用狀態(tài)字段和查詢條件推導也夠用但在論文里的狀態(tài)圖可以按六態(tài)畫一頁圖撐起來了答辯也有東西講。狀態(tài)推移的規(guī)則是入庫產(chǎn)生“已入庫”通知用戶后變“已提醒”取件成功變“已出庫”超時未取變“逾期”用戶要求退回就變“異常退貨”。出庫和退回是終態(tài)不能再被取走這一條要在寫代碼時徹底鎖死。2.2 六張核心表字段怎么給、哪里要加唯一索引這里有個常見誤區(qū)把用戶表設(shè)計得很重。驛站場景下大多數(shù)人沒有賬號只有一個手機號所以不要在表里堆昵稱、頭像、積分這類字段。用戶表只留id、phone、create_time就夠。真正要花心思的是快遞表和取件記錄表。下面這套建表語句是按通用畢設(shè)方案整理的拿到zip后先對著它查一遍缺什么補什么-- 快遞表核心表所有流程都圍繞它轉(zhuǎn) CREATE TABLE express ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主鍵, tracking_no VARCHAR(32) NOT NULL COMMENT 快遞單號, phone VARCHAR(20) NOT NULL COMMENT 收件人手機號取件判斷依據(jù), status TINYINT NOT NULL DEFAULT 1 COMMENT 1已入庫 2已提醒 3已出庫 4逾期 5退回, pickup_code VARCHAR(10) DEFAULT NULL COMMENT 取件碼入庫時生成, shelf_no VARCHAR(20) DEFAULT NULL COMMENT 貨架編號例如A-01, in_time DATETIME DEFAULT NULL COMMENT 入庫時間, notify_time DATETIME DEFAULT NULL COMMENT 通知時間, take_time DATETIME DEFAULT NULL COMMENT 出庫時間, PRIMARY KEY (id), UNIQUE KEY uk_tracking (tracking_no), KEY idx_phone_status (phone, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT快遞表;-- 取件記錄表每次出庫動作落一條用于統(tǒng)計和答辯時講對賬 CREATE TABLE take_record ( id BIGINT NOT NULL AUTO_INCREMENT, express_id BIGINT NOT NULL COMMENT 快遞ID, phone VARCHAR(20) NOT NULL COMMENT 取件人手機號, take_time DATETIME NOT NULL COMMENT 出庫時間, operator_type TINYINT DEFAULT 0 COMMENT 0-用戶自助 1-管理員代取, PRIMARY KEY (id), KEY idx_express (express_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT取件記錄表;注意三個關(guān)鍵點也是答辯??碱}。tracking_no必須加唯一索引同一個單號不能入兩次庫這是快遞場景的硬約束idx_phone_status是為了支撐“我的包裹”查詢頁讓查詢走索引而不是全表掃描pickup_code在業(yè)務(wù)層生成后最好也加唯一索引兜底并發(fā)場景下兩個請求撞相同取件碼的概率才能壓下去。其余四張表——admin管理員表、user用戶表、notice通知表、overdue_log逾期處理表設(shè)計上保持“誰在什么時間做了什么”即可。通知表不要做成短信平臺對接畢業(yè)生設(shè)計一般只落庫留一個send_flag字段表示已推送就行。3. 寫通入庫、取件、出庫最小可用的核心流程代碼有些zip的代碼跑得起來但讀不動Controller里直接寫SQLService方法兩百行事務(wù)注解一個沒有。這不是風格問題是模塊邊界沒立住。如果包里的代碼沒法讓你在答辯時講清楚職責我的建議是保留外部接口按下面這套方案重寫核心Service。三塊足夠閉環(huán)入庫、取件出庫、逾期統(tǒng)計。3.1 入庫從錄制快遞到生成取件碼的一串決策入庫接口的輸入有三個trackingNo快遞單號、phone收件人手機號、shelfNo貨架編號輸出是一個取件碼。取件碼的生成方案常見的有三種純6位隨機數(shù)、基于快遞ID編碼、時間戳低位拼接。純隨機最簡單但碰撞后要循環(huán)重查基于ID編碼的碼面穩(wěn)定還能在答辯時講出“為什么這么設(shè)計”。我一般用ID末四位加隨機段生成效果如下public String generatePickupCode(long expressId) { // 取主鍵ID末四位做前綴保證同一天入庫的快遞碼有一定辨識度 // 后綴用4位隨機數(shù)把碰撞范圍壓到極小區(qū)間 String prefix String.format(%04d, expressId % 10000); String suffix String.format(%04d, ThreadLocalRandom.current().nextInt(10000)); String code prefix suffix; // 查庫確認不重復(fù)撞碼后重新生成隨機部分 while (expressMapper.countByPickupCode(code) 0) { suffix String.format(%04d, ThreadLocalRandom.current().nextInt(10000)); code prefix suffix; } return code; }邏輯說明expressId是主鍵自增ID用它的末四位做前綴可以讓取件碼看起來有序且好記ThreadLocalRandom在高并發(fā)下比Random更省資源也不要為了省事用Math.random()拼字符串位數(shù)控制容易出錯。參數(shù)說明%04d是Java格式化輸出不足四位左補零保證碼位固定countByPickupCode是Mapper層的一個計數(shù)查詢返回0才認為碼可用。入庫Service建議按“先查重—再插入—后生成碼—最后回更”四步走。第一步查tracking_no是否已存在第二步插入基礎(chǔ)狀態(tài)第三步調(diào)用上面的方法生成取件碼并更新第四步任何一步失敗都回滾。這套順序能講成“先約束業(yè)務(wù)字段再落庫再分配業(yè)務(wù)編碼”是一個完整的事務(wù)邊界答辯時非常好解釋。3.2 取件出庫一個請求里要處理的三件事用戶取件的入?yún)⑹莗hone pickupCode。一個合格的出庫方法不是“查到就改狀態(tài)”它必須一次性完成三件事驗證手機號與取件碼匹配校驗當前狀態(tài)是否允許出庫在狀態(tài)變更的同時寫一條take_record。下面這段是核心邏輯public boolean takeExpress(String phone, String pickupCode) { // 第一步按手機號和取件碼精確鎖定快遞 Express exist expressMapper.selectOne( new LambdaQueryWrapperExpress() .eq(Express::getPhone, phone) .eq(Express::getPickupCode, pickupCode)); if (exist null) { return fail(取件碼與手機號不匹配或該快遞已被取走); } // 第二步狀態(tài)機校驗只允許已入庫/已提醒狀態(tài)出庫 if (exist.getStatus() TAKEN) { return fail(該包裹已出庫請勿重復(fù)操作); } if (exist.getStatus() RETURNED) { return fail(該包裹已辦理退回請聯(lián)系驛站管理員); } // 第三步先寫取件記錄再更新快遞狀態(tài) TakeRecord record new TakeRecord(); record.setExpressId(exist.getId()); record.setPhone(phone); record.setTakeTime(new Date()); takeRecordMapper.insert(record); exist.setStatus(TAKEN); exist.setTakeTime(new Date()); expressMapper.updateById(exist); return success(取件成功); }邏輯說明第一步的selectOne依賴數(shù)據(jù)庫唯一性如果庫里已經(jīng)出現(xiàn)多條相同phonepickup_code的記錄這里會直接拋異常所以上一章里給pickup_code加唯一索引是這步的前置保障。第二步的狀態(tài)判斷是防止重復(fù)出庫的關(guān)鍵很多翻車現(xiàn)場就是少了這個 if前端雙擊提交兩次請求狀態(tài)就亂了。第三步先插記錄再改主表記住包在Transactional里中間任何異常整體回滾。參數(shù)說明phone建議前端做一次1[3-9]\\d{9}正則校驗減少無效請求進后端pickupCode后端取參后先trim()用戶手輸多一個空格就會查出空結(jié)果這類問題在演示現(xiàn)場很常見。3.3 逾期與統(tǒng)計讓答辯PPT有東西可寫的兩個功能驛站業(yè)務(wù)有兩個隱藏指標值得做進系統(tǒng)包裹滯留周期和日取件高峰時段。前者對應(yīng)“逾期提醒”后者對應(yīng)“統(tǒng)計圖表”。逾期判斷不推薦用定時任務(wù)每分鐘掃全表畢設(shè)場景下沒必要也扛不住大數(shù)據(jù)量。常見做法是查詢時用SQL算差值超過閾值的記錄自然出現(xiàn)在“待提醒”頁面。-- 超過48小時未取件的快遞按庫齡倒序展示 select id, tracking_no, phone, pickup_code, in_time, timestampdiff(HOUR, in_time, now()) as stay_hours from express where status in (1, 2) and timestampdiff(HOUR, in_time, now()) 48 order by stay_hours desc;參數(shù)說明status in (1, 2)對應(yīng)已入庫和已提醒意思是“還在站里”的包裹48 是滯留閾值可以抽成一個配置項答辯時演示改為24看效果更直觀。timestampdiff(HOUR, ...)是MySQL函數(shù)不做索引優(yōu)化也能在萬級數(shù)據(jù)下秒回夠畢業(yè)設(shè)計用。統(tǒng)計功能同理從take_record里按小時分組就能得到取件高峰select hour(take_time) as hour_no, count(*) as cnt from take_record where take_time date_sub(now(), interval 7 day) group by hour(take_time) order by cnt desc;到這里核心流程已經(jīng)閉環(huán)。下一步把整套工程“換殼”讓它從別人的zip變成你能講清楚、也敢理直氣壯說是自己改造的畢設(shè)。4. 技術(shù)選型與改名換面把別人的工程變成能答辯的項目很多畢業(yè)生不敢用zip里的代碼怕雷同。實際上論文查重主要看正文文字代碼相似不是致命問題但項目必須能自圓其說。讓它變成“你的項目”不是大改業(yè)務(wù)而是動包名、動表前綴、動配置、重寫關(guān)鍵注釋。這一章把選型理由和換殼步驟一次講完。4.1 為什么選 Spring Boot MyBatis-Plus夠用、好講、容易加東西市面上這類zip打包的后端主要是兩塊Spring Boot MyBatis/MyBatis-Plus MySQL或者老一點兒的SSHStrutsSpringHibernate。SSH配置太重、注解散亂、演示前還得單獨裝Tomcat現(xiàn)在基本不在推薦范圍。Spring Boot 是畢業(yè)設(shè)計的及格線理由不是“新”而是三句話能講清內(nèi)嵌Tomcat開發(fā)機不用單獨裝容器注解式開發(fā)Controller/Service邊界一眼可見配置在application.yml集中管理演示前改端口、改數(shù)據(jù)庫連接都很快。前端常見三種LayUI、Bootstrap、VueElementUI。zip里如果給了Vue版前后端分離的架構(gòu)足夠你多講五分鐘如果只是靜態(tài)HTMLJQuery也不影響業(yè)務(wù)演示畢竟老師看的是流程完整性不是前端框架新不新。無論哪種記住一個原則不要再引入微服務(wù)、消息隊列、分布式事務(wù)這些概念。畢業(yè)設(shè)計的核心是讓系統(tǒng)穩(wěn)定運行并把業(yè)務(wù)講透架構(gòu)越簡單翻車概率越低。下面這套配置是我在同類項目里常用的最小模板server: # 避免和同學演示撞端口建議改成8081或8082 port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # serverTimezone 必須寫否則新版MySQL驅(qū)動啟動時報時區(qū)錯誤 url: jdbc:mysql://127.0.0.1:3306/express_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 # 很多zip自帶 schema.sql data.sql 每次啟動重建數(shù)據(jù)演示前務(wù)必關(guān)掉 sql: init: mode: never 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參數(shù)設(shè)置有三個坑都是實操里碰過的serverTimezoneAsia/Shanghai是防時區(qū)報錯的固定寫法少了它啟動時大概率拋The server time zone value相關(guān)異常sql.init.modenever非常重要有的zip為了讓評委打開就有數(shù)據(jù)會在啟動時執(zhí)行建表和初始化腳本副作用是每次重啟都會清庫重來演示到一半數(shù)據(jù)沒了直接現(xiàn)場翻車map-underscore-to-camel-case: true讓表字段take_time自動映射到實體屬性takeTime也能少寫一堆XML映射標簽。4.2 改包名、改庫名前綴、重寫注釋三個半小時的換殼工作把別人工程變成自己的至少做三件事。第一件改包名。假設(shè)原包是com.example.express改成com.yourname.express。不要只改文件夾名還要檢查三個位置pom.xml的groupId、啟動類上的SpringBootApplication掃描根包、MyBatis的mapper-locations路徑。這三處漏一處啟動時都會報Invalid bound statement (not found)一類的錯。第二件改庫名與表前綴。把express表改成yp_express這樣帶個人標識的名字。建表腳本同步改實體類上的TableName(express)注解也要改。這一步的意義不是防查重而是讓答辯老師掃一眼表名就知道這套庫是經(jīng)過你重構(gòu)的而不是原zip直接拿來用。第三件重寫關(guān)鍵注釋。不是把每個類注釋拉長而是把“為什么這么寫”補清楚。以下面這段為例/** * 快遞入庫接口 * 為什么返回封裝對象 ResponseResult * 前端需要統(tǒng)一拿到 code/msg/data 三個字段便于統(tǒng)一處理超時和錯誤提示。 * 為什么在 Service 層生成取件碼 * Controller 只做參數(shù)綁定與登錄校驗避免把業(yè)務(wù)規(guī)則堆在接口層。 */這樣做之后哪怕代碼骨架與原zip高度相似你也多了一層“能回答設(shè)計考量”的能力。答辯問“哪些是你寫的”你可以指著注釋和狀態(tài)機講這處的接口結(jié)構(gòu)是我定的表索引是我加的取件碼生成邏輯是我重寫的。底氣比背一篇文章足得多。5. 跑zip的避坑清單解壓、環(huán)境、數(shù)據(jù)三輪排查這類項目的啟動失敗九成集中在文件損壞、端口與靜態(tài)資源、數(shù)據(jù)庫連接、業(yè)務(wù)狀態(tài)錯亂這幾類。下面按排查順序?qū)懰慕M高頻坑每一條都給現(xiàn)象、原因和解決方式。5.1 解壓就報錯eocd問題與文件損壞的三個檢查現(xiàn)象解壓時報invalid zip archive: could not find eocd或者提示壓縮包末端缺少結(jié)束標記有的文件能解壓但漏文件。原因zip本身沒下載完整網(wǎng)盤下載中途斷流殺毒軟件把包內(nèi)某個文件當風險文件直接隔離打包工具生成的zip與本地解壓工具版本不兼容。解決先看文件大小和下載頁面標注做對比差幾MB基本就是沒下完換 7-Zip 或命令行unzip -t 文件名.zip測試完整性比圖形界面報錯更明確還是不行就清掉下載緩存重新下載別在同一個源上反復(fù)試。畢業(yè)生拿到包的第一步其實是這個別一開始就在代碼層排查。5.2 界面打不開或404先按四層排查別急著改代碼現(xiàn)象啟動日志顯示Tomcat已啟動瀏覽器訪問http://localhost:8080/login卻404或頁面白屏。排查順序 第一層看端口。如果端口被占用啟動日志里會有Port 8080 was already in use。Windows用netstat -ano | findstr 8080找占用PIDtaskkill /PID xxx /F結(jié)束進程或直接改server.port為8081。 第二層看上下文路徑。server.servlet.context-path如果被配置過訪問要帶前綴比如/demo/login別憑直覺訪問根路徑。 第三層看靜態(tài)資源位置。純HTML放在src/main/resources/static下可直接用/login.html訪問如果頁面在templates目錄走的是模板引擎渲染必須匹配Controller里的GetMapping路徑。 第四層看攔截器。很多zip寫了登錄攔截器所有未登錄請求都會被302到登錄頁頁面表現(xiàn)為白屏或反復(fù)跳轉(zhuǎn)。這時候看瀏覽器Network面板里的301/302狀態(tài)碼比看后端日志更快。攔截器代碼要確認靜態(tài)資源放行Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) // 放行登錄與靜態(tài)資源其余全部攔截 .addPathPatterns(/**) .excludePathPatterns(/login, /api/login, /css/**, /js/**, /images/**); }參數(shù)說明excludePathPatterns如果漏了/css/**和/js/**頁面HTML能打開但樣式全丟演示效果很差登錄接口路徑以你controller里的實際映射為準。5.3 數(shù)據(jù)庫連不上與中文亂碼改三處就不再翻車現(xiàn)象啟動報Access denied for user rootlocalhost或Unknown database express_db數(shù)據(jù)寫入后中文變成問號。原因與解決一口氣查三處。一是賬號密碼不對先用命令行mysql -uroot -p手動登錄驗證別在配置里反復(fù)猜二是MySQL 8要用com.mysql.cj.jdbc.Driver別用com.mysql.jdbc.Driver后者在新版驅(qū)動下會報加載失敗三是庫可能不存在手動建庫CREATE DATABASE express_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;建庫時明確指定utf8mb4再用數(shù)據(jù)庫客戶端導入建表腳本中文列名和數(shù)據(jù)都不會亂。最后檢查數(shù)據(jù)源URL里有沒有characterEncodingutf8這個參數(shù)不寫連接層默認用服務(wù)器字符集中文寫入MySQL大概率亂碼。改動順序建議是庫字符集 → 連接串 → 表字符集一層層確認。5.4 取件碼重復(fù)、狀態(tài)錯亂、依賴沖突三個黑匣子的定位思路取件碼重復(fù)。原因往往是生成方法用了純Random.nextInt(100000)且沒查重或查重語句寫在錯誤分支里形同虛設(shè)。解決方法是把生成邏輯改成第3章的“先查再寫”同時給pickup_code加唯一索引兜底。排查時直接執(zhí)行select pickup_code, count(*) from express group by pickup_code having count(*) 1;一條SQL就能看出是歷史臟數(shù)據(jù)還是新生成的沖突。狀態(tài)錯亂比如快遞沒被取走卻出現(xiàn)在已出庫列表。原因幾乎都是出庫接口直接update express set status3 where id?不校驗前置狀態(tài)或者前端雙擊請求兩個請求先后進來第二個依然進入更新邏輯。解決方法是出庫方法里按phone pickup_code鎖記錄、判斷狀態(tài)、加Transactional重復(fù)取件第二次必須返回“已取件”。依賴沖突典型報錯是ClassNotFoundException: com.mysql.cj.jdbc.Driver但代碼里明明有依賴。原因是pom.xml中多個MySQL驅(qū)動坐標沖突。解決方式是在項目根目錄執(zhí)行mvn dependency:tree -Dincludesmysql:mysql-connector-java看實際生效版本把多余坐標注釋掉。這類問題不能靠反復(fù)重啟碰運氣日志第一行堆棧是唯一權(quán)威。6. 答辯前做一次自檢用冒煙腳本把完整流程過一遍演示前最后一次排練我一般不再手動點頁面而是寫一個冒煙腳本把“登錄—入庫—重復(fù)入庫失敗—取件—重復(fù)取件失敗”整條鏈路自動跑一遍。這個方法是從聯(lián)調(diào)環(huán)境里學來的手動點擊容易漏步驟腳本每次都從同一個初始狀態(tài)出發(fā)結(jié)果可復(fù)現(xiàn)。#!/bin/bash # 冒煙測試驗證菜鳥驛站快遞分發(fā)系統(tǒng)核心鏈路 # 使用方法先啟動項目再執(zhí)行 bash smoke_test.sh BASEhttp://localhost:8080 # 1. 管理員登錄拿token TOKEN$(curl -s -X POST $BASE/api/login -H Content-Type: application/json \ -d {username:admin,password:123456} \ | sed -n s/.*token:\([^]*\).*/\1/p) # 2. 入庫一筆快遞響應(yīng)里提取取件碼 TRACKINGSM$(date %s) RESP$(curl -s -X POST $BASE/api/express/in -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {\trackingNo\:\$TRACKING\,\phone\:\13900001111\,\shelfNo\:\A-01\}) CODE$(echo $RESP | sed -n s/.*pickupCode:\([^]*\).*/\1/p) echo 入庫結(jié)果$RESP # 3. 同一單號再次入庫預(yù)期返回重復(fù)錯誤 echo 重復(fù)入庫結(jié)果 curl -s -X POST $BASE/api/express/in -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {\trackingNo\:\$TRACKING\,\phone\:\13900001111\,\shelfNo\:\A-01\} echo # 4. 取件預(yù)期成功 echo 取件結(jié)果 curl -s -X POST $BASE/api/express/take -H Content-Type: application/json \ -d {\phone\:\13900001111\,\pickupCode\:\$CODE\} echo # 5. 重復(fù)取件預(yù)期失敗 echo 重復(fù)取件結(jié)果 curl -s -X POST $BASE/api/express/take -H Content-Type: application/json \ -d {\phone\:\13900001111\,\pickupCode\:\$CODE\} echo邏輯說明腳本第1步從登錄接口取token第2步用時間戳拼單號保證每次運行不撞歷史數(shù)據(jù)第3步驗證單號唯一約束第4和第5步驗證狀態(tài)機的冪等判斷。如果項目接口路徑和字段名與腳本不一致改BASE、/api/login、/api/express/in、/api/express/take這幾處即可。參數(shù)說明sed -n s/.*token:\([^]*\).*/\1/p是輕量正則提取不依賴jqdate %s生成秒級時間戳確保測試單號不重復(fù)。跑這個腳本時注意觀察第3步必須返回“重復(fù)”類提示、第5步必須返回“已取件”類提示兩步失敗說明核心約束沒生效答辯前必須修。核心鏈路用腳本驗證過我還會把數(shù)據(jù)庫重置為空庫再跑一次確保從干凈環(huán)境啟動也能通過。這樣做的好處不是腳本本身多高級而是它逼著你把“環(huán)境怎么搭、數(shù)據(jù)怎么重置、接口怎么調(diào)用”全部落實成動作而不是臨時記在腦子里。答辯現(xiàn)場總會有意外腳本能兜住系統(tǒng)翻車剩下的就靠你對每個方法、每個狀態(tài)的熟悉程度來兜底了。希望這份自檢腳本幫到你祝答辯順利。本文還有配套的精品資源點擊獲取