崙?zhàn):路演報(bào)名系統(tǒng)的時(shí)間沖突檢測(cè)與前后端分離設(shè)計(jì))
說(shuō)實(shí)話每年到這個(gè)節(jié)點(diǎn)都會(huì)有不少準(zhǔn)備畢業(yè)設(shè)計(jì)的同學(xué)來(lái)問(wèn)同一個(gè)問(wèn)題有沒(méi)有那種“看起來(lái)有技術(shù)含量、但又不是人人都能講清”的小程序項(xiàng)目翻了一圈開源倉(cāng)庫(kù)和二手交易平臺(tái)大部分所謂的畢設(shè)源碼都是“能跑就行”的玩具唯獨(dú)最近看到的這個(gè)題目讓我多停留了幾秒——“基于Node.js的演唱會(huì)路演時(shí)間進(jìn)程安排報(bào)名小程序的設(shè)計(jì)與實(shí)現(xiàn)”。這題目不是那種爛大街的“XX管理系統(tǒng)”它把“路演計(jì)劃編排”“報(bào)名時(shí)間沖突檢測(cè)”“前后端分離”這幾個(gè)點(diǎn)揉在了一起既具備完整的業(yè)務(wù)閉環(huán)又有能拿出去講的算法亮點(diǎn)很適合作為畢設(shè)選題或入門全棧的練手項(xiàng)目。這篇文章我不講空話直接拆解這個(gè)項(xiàng)目到底做了什么、數(shù)據(jù)庫(kù)怎么設(shè)計(jì)、時(shí)間沖突算法的核心邏輯怎么實(shí)現(xiàn)、前后端聯(lián)調(diào)時(shí)哪些坑能要命最后結(jié)合答辯演示的真實(shí)經(jīng)驗(yàn)把從0到1的完整路徑給你捋一遍。內(nèi)容會(huì)偏開發(fā)實(shí)戰(zhàn)附帶Node.js和小程序端的關(guān)鍵代碼思路適合正在猶豫選型或已經(jīng)搭建了一半、想查漏補(bǔ)缺的同學(xué)參考。1. 為什么選這個(gè)題目需求拆解與項(xiàng)目定位1.1 演唱會(huì)路演場(chǎng)景的真實(shí)痛點(diǎn)很多人第一次看到這個(gè)題目第一反應(yīng)是“這不就是個(gè)報(bào)名小程序嗎和投票、問(wèn)卷有啥區(qū)別”這么想就大錯(cuò)特錯(cuò)了。演唱會(huì)路演不是單場(chǎng)活動(dòng)它通常是一個(gè)持續(xù)多天、多場(chǎng)地、多時(shí)間段排布的系列計(jì)劃。比如某高校的校園歌手巡回路演會(huì)在周五、周六、周日三天內(nèi)分下午場(chǎng)和晚間場(chǎng)在A區(qū)、B區(qū)、C區(qū)三個(gè)場(chǎng)地輪換舉行。這就引出了第一個(gè)核心訴求——時(shí)間進(jìn)程安排。運(yùn)營(yíng)人員需要把整個(gè)路演周期內(nèi)的所有場(chǎng)次預(yù)先排布好每個(gè)場(chǎng)次有明確的開始時(shí)間、結(jié)束時(shí)間、場(chǎng)地、容納人數(shù)。而普通報(bào)名小程序只解決“報(bào)一個(gè)活動(dòng)”這個(gè)項(xiàng)目解決的是“在復(fù)雜的時(shí)間網(wǎng)格中幫用戶選定一組互不沖突的路演場(chǎng)次”。第二個(gè)痛點(diǎn)更實(shí)際同一時(shí)間段可能有多個(gè)場(chǎng)地同時(shí)開演。用戶選了周六下午A區(qū)的場(chǎng)次還想再報(bào)周六下午B區(qū)的場(chǎng)次系統(tǒng)層面的報(bào)名記錄當(dāng)然可以成功但人沒(méi)法分身。所以“時(shí)間沖突檢測(cè)”是這類報(bào)名系統(tǒng)的隱形剛需也是這個(gè)題目區(qū)別于普通增刪改查項(xiàng)目的核心加分點(diǎn)。1.2 技術(shù)選型背后的考量后端選Node.js而不是Java SpringBoot或Python Django從畢設(shè)角度來(lái)看有幾個(gè)實(shí)際理由前后端語(yǔ)言棧統(tǒng)一小程序端用JavaScript服務(wù)端也用JavaScript降低了學(xué)習(xí)成本調(diào)試時(shí)心智負(fù)擔(dān)小很多。Node.js生態(tài)里有大量現(xiàn)成的中間件比如express框架、jsonwebtoken做登錄態(tài)、mysql2做數(shù)據(jù)庫(kù)連接池基本三五天就能把后端骨架搭出來(lái)。異步IO模型對(duì)報(bào)名這種高并發(fā)讀寫的場(chǎng)景天生友好雖然畢設(shè)不會(huì)真遇到百萬(wàn)流量但你能在論文里講清楚“為什么選它”這本身就是答辯加分項(xiàng)。前端自然是微信小程序原生開發(fā)。不要一上來(lái)就引分包、引第三方組件庫(kù)原生小程序足夠完成列表渲染、表單提交、用戶授權(quán)登錄這些功能而且打包體積小、審查方便。整個(gè)系統(tǒng)按“小程序客戶端 Node.js服務(wù)端 MySQL數(shù)據(jù)庫(kù)”的三層結(jié)構(gòu)來(lái)組織就是非常標(biāo)準(zhǔn)的全棧項(xiàng)目形態(tài)。2. 數(shù)據(jù)表設(shè)計(jì)與接口規(guī)劃三張核心表?yè)纹鹫麄€(gè)業(yè)務(wù)2.1 用戶表、場(chǎng)次表、報(bào)名表的字段設(shè)計(jì)這套系統(tǒng)最核心的數(shù)據(jù)結(jié)構(gòu)其實(shí)只有三張表用戶表、路演場(chǎng)次表、報(bào)名記錄表。設(shè)計(jì)得好壞直接決定后期寫代碼時(shí)是順暢還是反復(fù)返工。用戶表不復(fù)雜關(guān)鍵是存好微信登錄需要的字段。我用過(guò)幾個(gè)開源項(xiàng)目最常見的坑是把用戶備注、用戶類型、用戶狀態(tài)這些無(wú)關(guān)字段全都塞進(jìn)主表搞到最后字段比業(yè)務(wù)邏輯還長(zhǎng)。CREATE TABLE users ( id int NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL, nickname varchar(64), avatar_url varchar(255), phone varchar(20), created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;路演場(chǎng)次表是全局業(yè)務(wù)的核心它承載的就是標(biāo)題里所謂“時(shí)間進(jìn)程安排”的靜態(tài)信息。每個(gè)場(chǎng)次必須有開始時(shí)間、結(jié)束時(shí)間、場(chǎng)地地址、最大人數(shù)、當(dāng)前已報(bào)名人數(shù)以及一個(gè)狀態(tài)字段用來(lái)標(biāo)記“報(bào)名中/已截止/已取消”。這里有個(gè)容易忽略的點(diǎn)路演通常包含多個(gè)系列場(chǎng)次建議加一個(gè)series_id字段來(lái)標(biāo)識(shí)它們屬于同一輪路演活動(dòng)這樣后臺(tái)按系列批次查詢、前端按線路展示時(shí)間線都會(huì)非常方便。報(bào)名記錄表是三張表里最需要?jiǎng)幽X子的因?yàn)樗瑫r(shí)關(guān)聯(lián)了用戶和場(chǎng)次還承擔(dān)著沖突檢測(cè)和容量控制的功能。一個(gè)用戶在同一時(shí)間段只能報(bào)名一個(gè)場(chǎng)次這個(gè)規(guī)則如果靠業(yè)務(wù)代碼每次去“查一遍已有記錄再判斷”在并發(fā)場(chǎng)景下必然出漏洞。所以除了業(yè)務(wù)層校驗(yàn)數(shù)據(jù)庫(kù)層的唯一索引和事務(wù)約束也必須頂上。CREATE TABLE registrations ( id int NOT NULL AUTO_INCREMENT, user_id int NOT NULL, session_id int NOT NULL, status tinyint DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_session (user_id, session_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;uk_user_session這個(gè)唯一索引能保證同一用戶對(duì)同一場(chǎng)次只能有一條記錄再配合status字段做“已報(bào)名”和“已取消”的狀態(tài)流轉(zhuǎn)比硬刪除記錄要安全得多后面出報(bào)表和統(tǒng)計(jì)也方便。2.2 服務(wù)端接口清單按業(yè)務(wù)角色劃分接口設(shè)計(jì)遵循“客戶端只管展示和收集數(shù)據(jù)業(yè)務(wù)規(guī)則全部下沉到服務(wù)端”的原則。整體來(lái)看可以分成三類第一類是用戶模塊。小程序端調(diào)用wx.login拿到臨時(shí)code向后端/api/auth/login換取用戶身份后端拿著code請(qǐng)求微信接口得到openid再簽發(fā)自定義token返回給小程序。后續(xù)請(qǐng)求都帶著token用中間件解析出userId。第二類是路演場(chǎng)次模塊包含后臺(tái)上傳場(chǎng)次、修改場(chǎng)次、場(chǎng)次列表查詢、場(chǎng)次詳情查詢。絕大部分畢設(shè)同學(xué)會(huì)把上傳和修改做成一個(gè)接口其實(shí)分開更清晰/api/session的POST代表新增PUT代表更新DELETE代表刪除。列表查詢要支持按日期篩選、按場(chǎng)地篩選還要返回每場(chǎng)剩余名額。第三類是報(bào)名模塊這是整套系統(tǒng)的咽喉。核心接口就三個(gè)提交報(bào)名、取消報(bào)名、查詢我的報(bào)名列表。提交報(bào)名時(shí)服務(wù)端要做三件事——判斷場(chǎng)次是否存在且狀態(tài)為報(bào)名中判斷場(chǎng)次是否還有余量判斷該用戶在同一時(shí)間段是否已有其他有效報(bào)名。前兩個(gè)用SQL就能查第三個(gè)要寫專門的沖突檢測(cè)邏輯下一章我單獨(dú)展開寫。3. 路演時(shí)間進(jìn)程安排的算法核心沖突檢測(cè)與容量控制3.1 為什么需要一套獨(dú)立的沖突檢測(cè)邏輯很多人一開始會(huì)想每場(chǎng)演出都對(duì)應(yīng)一條帶固定時(shí)間范圍的記錄我判斷沖突只需要查一下“該用戶已報(bào)場(chǎng)次中有沒(méi)有開始時(shí)間等于現(xiàn)在想報(bào)場(chǎng)次的開始時(shí)間”不就行了嗎這就是典型的業(yè)務(wù)想當(dāng)然。路演通常按“時(shí)間網(wǎng)格”運(yùn)作但場(chǎng)次之間不一定完全對(duì)齊比如A場(chǎng)地從14:00到15:30B場(chǎng)地從15:00到16:00這兩個(gè)場(chǎng)次就有半小時(shí)的重疊。如果只做“開始時(shí)間相等”判斷這半小時(shí)的重疊就會(huì)漏掉。真正正確的沖突判斷是兩個(gè)區(qū)間是否重疊用的是區(qū)間重疊數(shù)學(xué)原理假設(shè)已報(bào)名場(chǎng)次的時(shí)間區(qū)間是 [A開始, A結(jié)束]待報(bào)名場(chǎng)次是 [B開始, B結(jié)束]當(dāng)且僅當(dāng) A開始 B結(jié)束 且 B開始 A結(jié)束 時(shí)兩個(gè)場(chǎng)次時(shí)間重疊反過(guò)來(lái)思考更簡(jiǎn)單如果B的開始時(shí)間已經(jīng)晚于A的結(jié)束時(shí)間說(shuō)明完全錯(cuò)開如果A的開始時(shí)間已經(jīng)晚于B的結(jié)束時(shí)間也說(shuō)明完全錯(cuò)開。兩種都不滿足那就是重疊。3.2 沖突檢測(cè)的代碼實(shí)現(xiàn)按這個(gè)思路沖突檢測(cè)的核心函數(shù)可以寫成這樣async function checkTimeConflict(userId, targetStart, targetEnd) { // 查出該用戶所有有效報(bào)名對(duì)應(yīng)的場(chǎng)次時(shí)間 const sql SELECT s.start_time, s.end_time FROM registrations r JOIN sessions s ON r.session_id s.id WHERE r.user_id ? AND r.status 1 ; const userSessions await query(sql, [userId]); const targetStartTs new Date(targetStart).getTime(); const targetEndTs new Date(targetEnd).getTime(); for (const session of userSessions) { const startTs new Date(session.start_time).getTime(); const endTs new Date(session.end_time).getTime(); // 區(qū)間重疊判定 if (startTs targetEndTs targetStartTs endTs) { return { conflict: true, conflictSession: session }; } } return { conflict: false }; }這個(gè)函數(shù)走的是“先查出用戶所有有效報(bào)名再在內(nèi)存中逐個(gè)判斷”的思路。畢設(shè)級(jí)別的數(shù)據(jù)量下完全沒(méi)有性能問(wèn)題而且它讓業(yè)務(wù)邏輯更直觀——你甚至可以順手查出沖突的具體場(chǎng)次信息返回給前端用來(lái)提示“您已在該時(shí)間報(bào)名了XX場(chǎng)次”體驗(yàn)好非常多。在提交報(bào)名接口里這個(gè)函數(shù)要和“余量檢查”組合使用。推薦這樣做首先用事務(wù)開啟然后SELECT capacity, registered_count FROM sessions WHERE id ? FOR UPDATE鎖定該場(chǎng)次行接著比較剩余名額是否大于0再調(diào)用沖突檢測(cè)函數(shù)全部通過(guò)則插入報(bào)名記錄并更新場(chǎng)次的已報(bào)名人數(shù)最后提交事務(wù)。用FOR UPDATE行鎖的目的是為了防止兩個(gè)用戶同時(shí)看到剩余1個(gè)名額然后同時(shí)搶報(bào)成功的臟數(shù)據(jù)。市面上很多畢設(shè)源碼連事務(wù)都沒(méi)開全這個(gè)細(xì)節(jié)你在設(shè)計(jì)文檔里寫清楚了答辯老師問(wèn)你“如何處理并發(fā)場(chǎng)景”時(shí)就能穩(wěn)穩(wěn)接住。3.3 容量控制的邊界處理人數(shù)上限不能只靠查詢判斷。比較穩(wěn)妥的做法是把“已報(bào)名人數(shù)”冗余存放在sessions表里每次報(bào)名成功后registered_count 1取消報(bào)名后- 1。這樣列表頁(yè)展示剩余名額只需查一張表不需要每次都COUNT(*)一次。但冗余字段有個(gè)明顯的連帶問(wèn)題一旦某條報(bào)名記錄被硬刪除或者狀態(tài)更新邏輯漏了一段registered_count就會(huì)出現(xiàn)偏差。我的處理習(xí)慣是每次更新報(bào)名狀態(tài)都在同一個(gè)事務(wù)里同步更新sessions表的計(jì)數(shù)。如果發(fā)現(xiàn)不一致寫一個(gè)定時(shí)對(duì)賬腳本把registrations表按場(chǎng)次聚合后的數(shù)量回寫修正。這個(gè)思路很小但能從實(shí)際上保證數(shù)據(jù)不飄。還有一個(gè)設(shè)計(jì)上的取舍場(chǎng)次滿員后要不要設(shè)計(jì)候補(bǔ)隊(duì)列從畢設(shè)范圍來(lái)看加候補(bǔ)隊(duì)列會(huì)讓系統(tǒng)復(fù)雜不止一個(gè)量級(jí)而用戶并不會(huì)真的在意這個(gè)功能。我的建議是做一個(gè)“滿員則禁止報(bào)名”的硬決策把剩余名額為0的場(chǎng)次在前端直接置灰顯示后端收到報(bào)名請(qǐng)求也直接返回“該場(chǎng)次已滿員”干凈利落。4. 小程序端實(shí)現(xiàn)從路演列表到報(bào)名閉環(huán)4.1 首頁(yè)路演列表與時(shí)間線展示小程序端第一屏就是路演場(chǎng)次列表這個(gè)頁(yè)面承擔(dān)的不只是“數(shù)據(jù)列表展示”更是一個(gè)讓用戶快速理解“還有哪些時(shí)間段可報(bào)名”的時(shí)間軸。我采用的布局是頂部日期篩選欄底下按日期分組展示各場(chǎng)次卡片??ㄆ系暮诵男畔?chǎng)次名稱、開始時(shí)間、結(jié)束時(shí)間、地點(diǎn)、剩余名額。這個(gè)剩余名額字段務(wù)必要活著顯示因?yàn)橛脩魶Q策時(shí)對(duì)“還有沒(méi)有位置”非常敏感。通過(guò)wx.request請(qǐng)求后端列表接口拿到數(shù)組后在onLoad里用setData渲染。這里有個(gè)繪制上的小技巧不要手動(dòng)用wx:if和wx:for去寫大量重復(fù)判斷可以預(yù)先在服務(wù)端把每個(gè)場(chǎng)次整理成統(tǒng)一結(jié)構(gòu){ id, title, startTime, endTime, location, remaining, status }小程序端只負(fù)責(zé)循環(huán)綁定。業(yè)務(wù)判斷越少放到客戶端越不容易出現(xiàn)狀態(tài)不同步的問(wèn)題。4.2 場(chǎng)次詳情頁(yè)與時(shí)間沖突提示點(diǎn)擊列表中的場(chǎng)次卡片可以進(jìn)入詳情頁(yè)。詳情頁(yè)除了展示更完整的介紹信息最核心的交互是底部固定的“立即報(bào)名”按鈕。報(bào)名按鈕點(diǎn)擊后小程序端先做一次本地檢查如果當(dāng)前用戶在登錄態(tài)下沒(méi)有phone那就得先引導(dǎo)去授權(quán)手機(jī)號(hào)。這里不需要在前端做過(guò)多校驗(yàn)真正的業(yè)務(wù)規(guī)則都在后端前端只需要把sessionId傳過(guò)去。后端返回沖突信息時(shí)前端要能優(yōu)雅地展示。我見過(guò)一些項(xiàng)目的做法是直接彈一個(gè)“您已報(bào)名過(guò)”用戶完全不知道為什么不能報(bào)。改進(jìn)后的做法是后端在沖突響應(yīng)里附帶沖突場(chǎng)次的名稱和具體時(shí)間小程序端彈出一個(gè)自定義modal標(biāo)題寫“時(shí)間沖突提醒”正文寫“您已報(bào)名了{(lán)name}{startTime}該時(shí)段無(wú)法報(bào)名其他場(chǎng)次請(qǐng)選擇其他時(shí)間段的場(chǎng)次”用戶理解起來(lái)毫無(wú)障礙這個(gè)交互細(xì)節(jié)在答辯演示的時(shí)候很能抓眼球。4.3 “我的報(bào)名”與取消報(bào)名邏輯“我的報(bào)名”頁(yè)面就是查詢當(dāng)前用戶的所有有效報(bào)名記錄按場(chǎng)次時(shí)間倒序排列。每條記錄右側(cè)預(yù)留“取消報(bào)名”按鈕。取消報(bào)名的邏輯比想象中要多考慮一層取消之后該場(chǎng)次的注冊(cè)人數(shù)要立刻減一釋放出來(lái)的名額應(yīng)該即時(shí)在下一次列表刷新中體現(xiàn)。所以取消接口里的兩個(gè)操作必須放在同一個(gè)事務(wù)中否則會(huì)出現(xiàn)“報(bào)名記錄顯示已取消但場(chǎng)次剩余名額沒(méi)恢復(fù)”的數(shù)據(jù)不一致情況。還有一個(gè)細(xì)節(jié)如果用戶已經(jīng)報(bào)名了多個(gè)場(chǎng)次取消其中一個(gè)之后再去報(bào)名同時(shí)間段的其他場(chǎng)次這時(shí)沖突檢測(cè)函數(shù)又會(huì)被觸發(fā)。也就是說(shuō)用戶取消報(bào)名后的狀態(tài)變化會(huì)影響后續(xù)所有沖突判斷這也是為什么我把沖突檢測(cè)設(shè)計(jì)成動(dòng)態(tài)查詢而不是在用戶報(bào)名時(shí)就固化規(guī)則。5. 聯(lián)調(diào)時(shí)必踩的三個(gè)坑域名、時(shí)間格式與并發(fā)報(bào)名5.1 微信開發(fā)者工具里的域名校驗(yàn)問(wèn)題新手必踩的第一個(gè)坑就是小程序端請(qǐng)求本地接口時(shí)一直報(bào)“url not in domain list”。微信小程序的安全策略非常嚴(yán)格——真機(jī)運(yùn)行時(shí)所有wx.request請(qǐng)求的域名必須在微信公眾平臺(tái)后臺(tái)配置為HTTPS合法域名且不能是IP或localhost。在畢設(shè)開發(fā)階段主流做法是打開微信開發(fā)者工具在“詳情-本地設(shè)置”中勾選“不校驗(yàn)合法域名、web-view業(yè)務(wù)域名、TLS版本以及HTTPS證書”。勾上之后本地開發(fā)環(huán)境就能直接請(qǐng)求http://localhost:3000。這里要特別注意這個(gè)設(shè)置只在開發(fā)者工具里生效真機(jī)預(yù)覽時(shí)一定要把后端的接口域名換成已備案且支持HTTPS的真實(shí)域名或者退而求其次在演示當(dāng)天全程用開發(fā)者工具的模擬器否則現(xiàn)場(chǎng)掃碼一打開就白屏。5.2 時(shí)間格式不統(tǒng)一導(dǎo)致的區(qū)間判斷錯(cuò)亂我在開發(fā)前對(duì)時(shí)間格式的統(tǒng)一只花了一分鐘拍腦袋決定結(jié)果在寫沖突檢測(cè)時(shí)直接掉坑里。小程序端傳上來(lái)的時(shí)間格式既可能是2025-06-20 14:00:00這樣的字符串也可能是時(shí)間戳數(shù)字?jǐn)?shù)據(jù)庫(kù)里查出來(lái)的又是另一種格式。直接在JS里比較字符串或數(shù)字得到的結(jié)果容易張冠李戴。最終確定的方案后端所有接口統(tǒng)一接收時(shí)間戳數(shù)據(jù)庫(kù)字段用datetime類型存儲(chǔ)查詢后統(tǒng)一轉(zhuǎn)成Date對(duì)象再計(jì)算。在前端展示時(shí)由小程序端自行用時(shí)間格式化的函數(shù)處理成6月20日 14:00這種可讀文本。這個(gè)約定一旦定下來(lái)所有接口的輸入輸出都按同一套規(guī)則走后續(xù)排查問(wèn)題會(huì)省掉大量時(shí)間。5.3 并發(fā)報(bào)名下的數(shù)據(jù)一致性畢設(shè)展示時(shí)通常演示不了“幾百人同時(shí)報(bào)名”的場(chǎng)景。但答辯老師一定會(huì)追問(wèn)一個(gè)問(wèn)題“如果同時(shí)有很多人報(bào)名同一個(gè)場(chǎng)次剩余名額剛好就一個(gè)你怎么保證系統(tǒng)不會(huì)超賣”這就是上一章提到的FOR UPDATE發(fā)揮作用的地方。我在測(cè)試階段專門寫過(guò)一個(gè)并發(fā)腳本用Node.js的Promise.all同時(shí)發(fā)起20個(gè)報(bào)名請(qǐng)求目標(biāo)是最后一個(gè)名額。開啟事務(wù)加行鎖之前確實(shí)出現(xiàn)過(guò)兩三個(gè)人都提示報(bào)名成功的情況加上FOR UPDATE之后系統(tǒng)會(huì)讓后到的請(qǐng)求阻塞等待等前面的請(qǐng)求事務(wù)提交后再重新讀取行數(shù)據(jù)發(fā)現(xiàn)名額為0直接返回“該場(chǎng)次已滿員”。這個(gè)測(cè)試過(guò)程建議保留下來(lái)可以是日志截圖也可以是測(cè)試報(bào)告的表格。答辯時(shí)直接展示“并發(fā)壓測(cè)結(jié)果”比口頭解釋十句都管用導(dǎo)師看到你在設(shè)計(jì)階段就考慮了并發(fā)一致性整個(gè)論文的深度評(píng)價(jià)會(huì)明顯不一樣。6. 迭代過(guò)程里的業(yè)務(wù)打磨與答辯準(zhǔn)備6.1 從純報(bào)名工具升級(jí)為時(shí)間進(jìn)程管理平臺(tái)項(xiàng)目做到中期我意識(shí)到“報(bào)名”只是入口真正的價(jià)值在于“路演時(shí)間進(jìn)程安排”。所以除了小程序端面向普通用戶報(bào)名還要給運(yùn)營(yíng)人員留一個(gè)管理端的入口。為了不增加一個(gè)獨(dú)立后臺(tái)項(xiàng)目的復(fù)雜度我把管理端做成了小程序內(nèi)的一個(gè)“管理員模式”入口——僅當(dāng)openid命中管理員白名單時(shí)顯示。管理端里可以維護(hù)路演系列、批量生成場(chǎng)次時(shí)間表、調(diào)整某場(chǎng)次的人數(shù)容量。數(shù)據(jù)統(tǒng)計(jì)頁(yè)則用最樸素的列表展示每場(chǎng)已報(bào)名人數(shù)和滿員率不搞花哨的圖表。在論文里這部分對(duì)應(yīng)“系統(tǒng)的功能性設(shè)計(jì)”和“系統(tǒng)的可擴(kuò)展性設(shè)計(jì)”你有實(shí)打?qū)嵉捻?yè)面截圖和流程描述可寫比那些只做了前端頁(yè)面的項(xiàng)目厚重不少。6.2 演示Demo時(shí)容易被忽略的細(xì)節(jié)到了演示環(huán)節(jié)最容易翻車的不是業(yè)務(wù)功能而是演示順序和小細(xì)節(jié)。我的建議是按三條線走用戶報(bào)名閉環(huán)普通用戶登錄 → 逛路演列表 → 選場(chǎng)次報(bào)名 → 在“我的報(bào)名”里看到記錄 → 取消報(bào)名再重新報(bào)名。這條線要一氣呵成展示核心業(yè)務(wù)流的完整度。沖突檢測(cè)亮點(diǎn)提前在庫(kù)里安排兩個(gè)同一時(shí)間段不同場(chǎng)次的記錄演示時(shí)先報(bào)名A場(chǎng)次再嘗試報(bào)名B場(chǎng)次當(dāng)著導(dǎo)師面彈出時(shí)間沖突提示。這是全項(xiàng)目最亮眼的場(chǎng)景務(wù)必放在流程中段展示。管理端能力切到管理員模式新增后續(xù)場(chǎng)次、調(diào)整容量、查看看統(tǒng)計(jì)列表。管理端的操作最好提前錄屏備用因?yàn)楝F(xiàn)場(chǎng)操作管理入口容易因?yàn)闄?quán)限判斷失誤露怯。還有一個(gè)非常實(shí)用的小技巧演示前把開發(fā)者工具的緩存清掉重新編譯一遍避免code換session過(guò)期這種基礎(chǔ)錯(cuò)誤在臺(tái)上出現(xiàn)。7. 論文寫作與答辯提問(wèn)環(huán)節(jié)的應(yīng)對(duì)思路7.1 論文結(jié)構(gòu)怎么組織才不顯得空洞這類系統(tǒng)設(shè)計(jì)論文的基本盤沒(méi)什么花哨大體是緒論、需求分析、概要設(shè)計(jì)、詳細(xì)設(shè)計(jì)、系統(tǒng)實(shí)現(xiàn)、系統(tǒng)測(cè)試、總結(jié)。但很多同學(xué)的正文容易變成“偽代碼粘貼板”大段大段的代碼截圖直接貼進(jìn)去看起來(lái)字?jǐn)?shù)不少實(shí)際上沒(méi)任何信息量。我自己的寫作思路是把每一章的引領(lǐng)問(wèn)題先想清楚需求分析是“用戶到底需要一個(gè)什么樣的系統(tǒng)有哪些角色各自能做什么”概要設(shè)計(jì)是“系統(tǒng)分幾個(gè)模塊數(shù)據(jù)流向是怎樣的”詳細(xì)設(shè)計(jì)是“關(guān)鍵表結(jié)構(gòu)長(zhǎng)什么樣沖突檢測(cè)算法如何設(shè)計(jì)并證明其正確性”測(cè)試不能只寫“功能正?!彼膫€(gè)字要按功能點(diǎn)一一列出用例輸入、預(yù)期結(jié)果、實(shí)際結(jié)果、是否通過(guò)。7.2 高頻答辯問(wèn)題提前準(zhǔn)備好結(jié)合我做這類項(xiàng)目評(píng)審的經(jīng)驗(yàn)以下問(wèn)題命中率最高現(xiàn)成答案提前打好腹稿“你的時(shí)間沖突檢測(cè)是怎么實(shí)現(xiàn)的”答區(qū)間重疊判斷核心條件是已報(bào)名場(chǎng)次開始時(shí)間小于目標(biāo)場(chǎng)次結(jié)束時(shí)間且目標(biāo)場(chǎng)次開始時(shí)間小于已報(bào)名場(chǎng)次結(jié)束時(shí)間。“如何解決并發(fā)報(bào)名超賣”答事務(wù)開啟后對(duì)場(chǎng)次記錄行加鎖FOR UPDATE在鎖內(nèi)檢查余量并執(zhí)行報(bào)名插入提交后再釋放鎖同時(shí)配合唯一索引兜底。“為什么選Node.js而不是其他后端技術(shù)”答Node.js異步非阻塞I/O模型適應(yīng)報(bào)名系統(tǒng)高并發(fā)讀寫的場(chǎng)景前后端統(tǒng)一JavaScript語(yǔ)言棧降低開發(fā)維護(hù)成本生態(tài)成熟快速搭建可用的小型服務(wù)端?!叭绻脩羧∠麍?bào)名系統(tǒng)會(huì)發(fā)生什么”答在同一個(gè)事務(wù)里把報(bào)名記錄狀態(tài)置為取消并將對(duì)應(yīng)場(chǎng)次已報(bào)名人數(shù)減1釋放名額隨后該名額在列表頁(yè)表現(xiàn)為可報(bào)名狀態(tài)用戶也能再次報(bào)名同時(shí)間段其他場(chǎng)次。這些問(wèn)答不只是給評(píng)委聽的前提是你真的把代碼寫到那一步了。哪怕中途查過(guò)資料、看了一些開源項(xiàng)目的思路只要消化成自己的東西講出來(lái)自然就有說(shuō)服力。7.3 答辯演示時(shí)的著裝語(yǔ)氣與節(jié)奏技術(shù)之外的小事還是要提一句答辯時(shí)的角色感要立住。你是在向評(píng)委講解一個(gè)自己花了幾個(gè)月設(shè)計(jì)的作品不是在被審問(wèn)。先花兩分鐘用一句話陳述項(xiàng)目定位然后進(jìn)入演示每演示完一個(gè)鏈路停頓一秒鐘說(shuō)“這就是完整的XX流程”。最后留兩到三分鐘給評(píng)委提問(wèn)不要自己把時(shí)間占滿也不要一個(gè)問(wèn)題答完就沉默等下一個(gè)。演示結(jié)束的那一段如果你能把系統(tǒng)里某個(gè)具體設(shè)計(jì)的取舍講成一個(gè)“我在開發(fā)過(guò)程中發(fā)現(xiàn)……”的小故事比如“最開始我把沖突檢測(cè)放在前端做結(jié)果發(fā)現(xiàn)前端能繞過(guò)只好下沉到服務(wù)端”評(píng)委對(duì)項(xiàng)目的真實(shí)性判斷會(huì)高很多——這種話編不出來(lái)肯定是真的做過(guò)才會(huì)說(shuō)的。我自己的體會(huì)是這類“報(bào)名時(shí)間排期全棧小程序”的項(xiàng)目最大的收益不在于代碼量有多壯觀而在于它逼著你把業(yè)務(wù)建模、并發(fā)控制、前后端協(xié)作這些小但關(guān)鍵的工程問(wèn)題想了一遍。做完了你就不只是會(huì)調(diào)接口而是真的開始像一個(gè)工程師那樣去權(quán)衡設(shè)計(jì)方案了。