自助售票系統(tǒng)源碼拆解與二開指南)
簡(jiǎn)介一套面向Java Web與小程序開發(fā)者的SSM客運(yùn)自助售票小程序完整源碼包內(nèi)含整站前后端代碼、SQL腳本及畢業(yè)設(shè)計(jì)論文。項(xiàng)目采用Spring、SpringMVC、MyBatis經(jīng)典框架組合前端按微信小程序規(guī)范實(shí)現(xiàn)車次查詢、座位選擇、支付等購票流程后端提供穩(wěn)定API接口數(shù)據(jù)庫腳本遵循第三范式并考慮索引與緩存優(yōu)化可滿足快速迭代與性能需求。資源共1276個(gè)文件約14.87MB涵蓋vue、js、wxml、wxss等頁面邏輯文件java后臺(tái)服務(wù)、json/xml配置、sql數(shù)據(jù)腳本、docx論文及bat啟動(dòng)腳本結(jié)構(gòu)清晰便于部署學(xué)習(xí)。目前已有77人學(xué)習(xí)下載。適合正在做相關(guān)課題或希望掌握小程序全棧架構(gòu)的讀者通過源碼閱讀、腳本導(dǎo)入和文檔對(duì)照能快速理解從架構(gòu)設(shè)計(jì)到前后端協(xié)作的完整實(shí)現(xiàn)路徑并可直接用于二次開發(fā)與功能擴(kuò)展。1. 微信小程序SSM的客運(yùn)自助售票這套源碼真正的價(jià)值在哪朋友把一份「客運(yùn)自助售票小程序」的畢業(yè)設(shè)計(jì)源碼丟給我問能不能直接拿去做個(gè)小公司的內(nèi)部售票系統(tǒng)。我花了一晚上把代碼和SQL腳本理完結(jié)論是這類「微信小程序 SSM」的組合功能閉環(huán)完整、代碼量小、二開路徑清晰但能否落地取決于你先看懂那張訂單表而不是先打開Controller。本文以這套整站源碼為對(duì)象把數(shù)據(jù)庫、SSM后端、小程序前端的實(shí)現(xiàn)邏輯與踩坑點(diǎn)逐層拆開幫你判斷值不值得買、買回來怎么改。2. 先讀SQL腳本再談功能客運(yùn)數(shù)據(jù)表藏著業(yè)務(wù)邊界2.1 為什么第一步是看表而不是看代碼我不是先跑代碼的人。拿到「整站源碼sql腳本論文.zip」第一件事永遠(yuǎn)是打開SQL腳本看建表語句。原因很簡(jiǎn)單SSM項(xiàng)目逃不出Controller-Service-Mapper三層業(yè)務(wù)規(guī)則最后都落在表結(jié)構(gòu)和SQL上。這張客運(yùn)自助售票表怎么設(shè)計(jì)直接決定了你能不能改出退票、改簽、座位選擇等功能。常見做法是把表拆成四張核心表用戶表account、班次表bus、訂單表ticket_order、站點(diǎn)/線路表route或station。注意這里有個(gè)關(guān)鍵點(diǎn)班次表里通常不會(huì)存座位圖而是存「余票數(shù)」字段比如left_ticket。這意味著這套系統(tǒng)賣的是「無座票」或者「按數(shù)量扣減的票」不是「按座位號(hào)選座」。CREATE TABLE bus ( bus_id INT PRIMARY KEY AUTO_INCREMENT, route_id INT NOT NULL COMMENT 關(guān)聯(lián)線路ID, depart_station VARCHAR(50) NOT NULL COMMENT 出發(fā)站點(diǎn), arrive_station VARCHAR(50) NOT NULL COMMENT 到達(dá)站點(diǎn), depart_time DATETIME NOT NULL COMMENT 發(fā)車時(shí)間, arrive_time DATETIME NOT NULL COMMENT 到達(dá)時(shí)間, total_ticket INT DEFAULT 50 COMMENT 總票數(shù), left_ticket INT DEFAULT 50 COMMENT 余票數(shù), ticket_price DECIMAL(8,2) NOT NULL COMMENT 票價(jià), status TINYINT DEFAULT 1 COMMENT 1啟用 0停用 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;這段建表語句反映兩個(gè)信息第一線路信息被拆到了route表bus表通過route_id做關(guān)聯(lián)這是合理的同一條線路可以讓多輛車跑第二沒有座位維度total_ticket和left_ticket都是整數(shù)扣余票就是UPDATE bus SET left_ticket left_ticket - 1。這類設(shè)計(jì)足夠應(yīng)付畢業(yè)設(shè)計(jì)也足夠應(yīng)付小客運(yùn)站的賣票需求但如果你想賣「具體座位號(hào)」這張表不夠用得加一張seat維度表。再看訂單表CREATE TABLE ticket_order ( order_id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 業(yè)務(wù)訂單號(hào), user_id INT NOT NULL COMMENT 用戶ID, bus_id INT NOT NULL COMMENT 班次ID, ticket_count INT DEFAULT 1 COMMENT 購票張數(shù), total_price DECIMAL(8,2) NOT NULL COMMENT 總價(jià), status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已退票 3已檢票, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME NULL COMMENT 支付時(shí)間, INDEX idx_user (user_id), INDEX idx_bus (bus_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;order_no加了UNIQUE約束這是好習(xí)慣防止并發(fā)下單時(shí)產(chǎn)生重復(fù)訂單號(hào)。status字段用TINYINT表示狀態(tài)機(jī)0到3四個(gè)狀態(tài)配合pay_time記錄支付時(shí)間。注意沒有refund_time說明退票這個(gè)動(dòng)作被簡(jiǎn)化成「改狀態(tài)」如果你要統(tǒng)計(jì)退票時(shí)效得自己加上。2.2 從賬號(hào)表看用戶體系openid才是真正的鑰匙用戶表在客運(yùn)售票系統(tǒng)里比想象中簡(jiǎn)單因?yàn)樾〕绦蚨瞬恍枰脩裘艽a微信登錄拿到openid就夠了CREATE TABLE account ( user_id INT PRIMARY KEY AUTO_INCREMENT, nickname VARCHAR(50) DEFAULT , phone VARCHAR(20) DEFAULT , openid VARCHAR(64) NOT NULL UNIQUE, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;你會(huì)在表里看到openid字段它是微信用戶的唯一標(biāo)識(shí)。后端登錄接口拿到微信登錄憑證后會(huì)換取openid然后查表如果存在就直接登錄不存在就insert一條新記錄。這套邏輯在畢業(yè)設(shè)計(jì)和中小型系統(tǒng)中非常通用。字段phone留空沒問題因?yàn)樾〕绦蚶铽@取手機(jī)號(hào)需要企業(yè)認(rèn)證個(gè)人開發(fā)者用不了。這是客運(yùn)自助售票小程序最常被忽略的邊界乘客要憑手機(jī)號(hào)接收發(fā)車通知那得接短信服務(wù)或者讓用戶在小程序里自己填手機(jī)號(hào)。設(shè)計(jì)時(shí)要留這個(gè)口子別等上線了才發(fā)現(xiàn)沒地方存手機(jī)號(hào)。2.3 余票扣減的并發(fā)問題表結(jié)構(gòu)解決不了的坑票務(wù)系統(tǒng)繞不開并發(fā)扣減比如同一個(gè)班次只剩最后一張票兩個(gè)人同時(shí)下單。如果你在Service層寫成「查詢余票→判斷0→扣減」在高并發(fā)下會(huì)超賣。Override Transactional(rollbackFor Exception.class) public boolean createOrder(CreateOrderDTO dto) { Bus bus busMapper.selectById(dto.getBusId()); if (bus.getLeftTicket() dto.getTicketCount()) { throw new BizException(余票不足); } busMapper.decreaseTicket(bus.getBusId(), dto.getTicketCount()); // 插入訂單... return true; }這段看起來沒問題但兩個(gè)請(qǐng)求同時(shí)讀到left_ticket1時(shí)你判斷兩次都通過扣減后變成-1。解決辦法有兩個(gè)層面第一SQL層面加條件WHERE left_ticket #{count}讓數(shù)據(jù)庫兜底第二悲觀鎖SELECT ... FOR UPDATE鎖行。畢業(yè)設(shè)計(jì)源碼通常只會(huì)做到第一層就是decreaseTicket的SQL里帶上判斷條件UPDATE bus SET left_ticket left_ticket - #{count} WHERE bus_id #{busId} AND left_ticket #{count}用更新行數(shù)判斷是否扣減成功如果返回0說明余票不夠。這是不加鎖也能防超賣的最小可行方案也是這類項(xiàng)目里最常見、最推薦的做法。一上線就想用Redis分布式鎖的是過度設(shè)計(jì)四張表的項(xiàng)目扛不住那個(gè)復(fù)雜度。2.4 SQL腳本導(dǎo)入三個(gè)容易被忽略的坑導(dǎo)入sql腳本通常用Navicat或者命令行。命令行導(dǎo)入時(shí)要注意編碼問題很多畢業(yè)設(shè)計(jì)的SQL腳本是用Windows記事本寫的默認(rèn)GBK編碼直接source導(dǎo)入會(huì)出現(xiàn)中文亂碼mysql -u root -p --default-character-setutf8mb4 bus_system bus_system.sql第二個(gè)坑是sql腳本里沒寫CREATE DATABASE你得手動(dòng)建庫。常見做法是這樣CREATE DATABASE IF NOT EXISTS bus_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE bus_system;第三個(gè)坑是MySQL版本兼容性。這份源碼如果用的是MySQL 5.7腳本里可能會(huì)用到datetime默認(rèn)值CURRENT_TIMESTAMP這個(gè)MySQL 5.5以上就支持但如果腳本里出現(xiàn)了utf8mb4_unicode_ci之類排序規(guī)則MySQL 5.6以下不認(rèn)建議直接用MySQL 5.7或8.0。3. SSM后端怎么拆登錄態(tài)、下單事務(wù)與支付模擬3.1 三層架構(gòu)與包結(jié)構(gòu)先定位再動(dòng)手SSM是SpringSpringMVCMyBatis的組合源碼的包結(jié)構(gòu)基本是固定套路com.example.bus ├── controller // 接口層只做參數(shù)接收和返回 ├── service // 業(yè)務(wù)邏輯層事務(wù)邊界在這里 ├── dao // MyBatis的Mapper接口 ├── entity // 數(shù)據(jù)庫實(shí)體類 ├── dto // 接口出入?yún)?duì)象 ├── interceptor // 登錄攔截器 └── common // 統(tǒng)一返回結(jié)果、異常處理、工具類改代碼時(shí)記住一個(gè)原則Controller里別寫業(yè)務(wù)邏輯。很多畢業(yè)設(shè)計(jì)為了趕工把余票判斷寫在Controller里導(dǎo)致事務(wù)失效。判斷一個(gè)SSM項(xiàng)目是否規(guī)范就看Controller里有沒有直接調(diào)用xxxMapper。3.2 小程序登錄態(tài)token是一張通行證微信小程序的登錄流程是wx.login()拿到code → 發(fā)給后端 → 后端調(diào)用微信接口換取openid → 后端生成token返回小程序 → 小程序后續(xù)請(qǐng)求都帶token。后端通常用一個(gè)攔截器統(tǒng)一驗(yàn)證tokenpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isEmpty(token)) { throw new BizException(401, 未登錄); } // 解析token得到userId放入ThreadLocal或request域 Integer userId JwtUtil.parseToken(token); if (userId null) { throw new BizException(401, 登錄已過期); } request.setAttribute(currentUserId, userId); return true; } }兩個(gè)參數(shù)是實(shí)際調(diào)優(yōu)重點(diǎn)token有效期設(shè)置多長畢業(yè)設(shè)計(jì)里常見做法是7天因?yàn)樾〕绦蛴脩舨粫?huì)頻繁登錄太短會(huì)煩人。token里存什么只存userId就夠了不要存完整用戶信息免得token體積大且數(shù)據(jù)不一致。在SpringMVC里注冊(cè)攔截器要排除登錄和查詢接口mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/api/login/ mvc:exclude-mapping path/api/bus/list/ mvc:exclude-mapping path/api/bus/detail/ /mvc:interceptor /mvc:interceptors登錄接口和班次查詢接口放行其余接口都要登錄態(tài)。這里有個(gè)細(xì)節(jié)下單接口要不要登錄當(dāng)然要否則別人知道busId就能幫你買票。但很多源碼把下單接口漏在攔截器外面這是安全檢查時(shí)最容易翻車的地方拿到代碼后第一件事就是檢查interceptor的exclude清單。3.3 下單事務(wù)多張表的一致性怎么保證客運(yùn)購票的下單動(dòng)作涉及三張表訂單表insert一條記錄、班次表update余票、可能還有支付記錄表insert。這三步必須在一個(gè)事務(wù)里否則會(huì)出現(xiàn)「訂單生成了但余票沒扣」或者反過來。Override Transactional(rollbackFor Exception.class) public OrderVO submitOrder(OrderRequest req) { // 1. 校驗(yàn)班次狀態(tài) Bus bus busMapper.selectByIdForUpdate(req.getBusId()); // 行鎖 if (bus null || bus.getStatus() ! 1) { throw new BizException(404, 班次不存在或停運(yùn)); } // 2. 扣減余票 int rows busMapper.decreaseTicket(req.getBusId(), req.getTicketCount()); if (rows 0) { throw new BizException(余票不足); } // 3. 生成訂單號(hào)并插入訂單 String orderNo OrderNoGenerator.generate(B, bus.getId()); TicketOrder order new TicketOrder(); order.setOrderNo(orderNo); order.setUserId(req.getUserId()); order.setBusId(req.getBusId()); order.setTicketCount(req.getTicketCount()); order.setTotalPrice(bus.getTicketPrice().multiply(new BigDecimal(req.getTicketCount()))); order.setStatus(0); // 待支付 int orderRows orderMapper.insert(order); if (orderRows ! 1) { throw new BizException(訂單創(chuàng)建失敗); } // 4. 返回訂單信息前端跳轉(zhuǎn)支付頁 return orderAdapter.toVO(order); }第2步用selectByIdForUpdate加行鎖這是悲觀鎖方案。加了Transactional之后鎖要等事務(wù)提交才釋放所以submitOrder里不能有耗時(shí)操作比如調(diào)用外部接口或Thread.sleep。這里有一個(gè)關(guān)鍵細(xì)節(jié)JSON序列化時(shí)BigDecimal類型前端會(huì)變成number精度可能丟失尤其票價(jià)是19.90這種。常見做法是后端把價(jià)格轉(zhuǎn)成字符串返回或者前端用parseFloat再顯示。我在改造中一般會(huì)在DTO里把金額字段定義為String顯示時(shí)直接用下單時(shí)再轉(zhuǎn)回BigDecimal。3.4 支付模擬沒有微信支付商戶號(hào)時(shí)怎么辦畢業(yè)設(shè)計(jì)源碼里「支付」兩個(gè)字通常是模擬的因?yàn)檎鎸?shí)微信支付需要商戶號(hào)個(gè)人開發(fā)者根本沒有。模擬支付的做法是訂單創(chuàng)建后狀態(tài)是0待支付前端點(diǎn)擊「確認(rèn)支付」后調(diào)一個(gè)接口把狀態(tài)改成1已支付。PutMapping(/order/pay/{orderNo}) public Result payOrder(PathVariable String orderNo, HttpServletRequest request) { Integer currentUserId (Integer) request.getAttribute(currentUserId); TicketOrder order orderMapper.selectByOrderNo(orderNo); if (order null || !order.getUserId().equals(currentUserId)) { throw new BizException(訂單不存在); } if (order.getStatus() ! 0) { throw new BizException(訂單狀態(tài)不允許支付); } order.setStatus(1); order.setPayTime(new Date()); orderMapper.updateById(order); return Result.success(); }這是模擬支付接口的核心邏輯校驗(yàn)訂單歸屬→校驗(yàn)狀態(tài)→改狀態(tài)→記錄支付時(shí)間。如果你真想接入真實(shí)微信支付替代方案是在pay接口里先調(diào)微信支付統(tǒng)一下單API拿到prepay_id再返回小程序端拉起支付面板收到支付回調(diào)后修改訂單狀態(tài)??釉谟谥Ц痘卣{(diào)和訂單狀態(tài)更新不是一個(gè)事務(wù)回調(diào)可能延遲或丟失需要加一個(gè)定時(shí)任務(wù)對(duì)賬——這就超出這篇范圍了但你要有這個(gè)概念模擬支付改兩行代碼容易接真實(shí)支付是個(gè)完整的工程。4. 微信小程序端怎么對(duì)接后端從請(qǐng)求封裝到頁面狀態(tài)4.1 請(qǐng)求封裝統(tǒng)一token注入和錯(cuò)誤處理微信小程序的網(wǎng)絡(luò)請(qǐng)求用wx.request但原生寫法每個(gè)頁面都要重復(fù)寫header和錯(cuò)誤處理所以源碼里通常有個(gè)request.js工具類const BASE_URL http://localhost:8080/api; const 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, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 401) { wx.redirectTo({ url: /pages/login/login }); reject(res); } else if (res.statusCode 200 res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message || 請(qǐng)求失敗, icon: none }); reject(res.data); } }, fail: (err) { wx.showToast({ title: 網(wǎng)絡(luò)異常請(qǐng)檢查后端服務(wù), icon: none }); reject(err); } }); }); };這段代碼有幾個(gè)設(shè)計(jì)點(diǎn)需要說明第一token從wx.getStorageSync讀取在header里用Authorization傳遞Server端攔截器取的是同一個(gè)header名第二后端統(tǒng)一返回{code: 0, message: , data: {...}}這種結(jié)構(gòu)前端只認(rèn)code0是成功避免HTTP狀態(tài)碼和業(yè)務(wù)狀態(tài)碼混在一起第三401時(shí)跳轉(zhuǎn)登錄頁這是個(gè)兜底行為。4.2 班次查詢頁面日期不準(zhǔn)是因?yàn)闀r(shí)區(qū)問題客運(yùn)購票的核心頁面是「查詢班次」用戶選出發(fā)地、到達(dá)地、日期然后拉取列表。這里的日期參數(shù)有一個(gè)非常經(jīng)典的坑new Date() 轉(zhuǎn)出來的字符串是 2026-02-14T08:00:00.000Z后端解析LocalDateTime時(shí)差了8小時(shí)。正確做法是前端格式化好再傳function formatDate(date) { const y date.getFullYear(); const m (date.getMonth() 1).toString().padStart(2, 0); const d date.getDate().toString().padStart(2, 0); return ${y}-${m}-$drlblxrh1; } // 查詢班次列表 const dateStr formatDate(this.data.selectedDate); const res await request(/bus/list, GET, { departStation: this.data.departStation, arriveStation: this.data.arriveStation, departDate: dateStr });后端接口接收departDate2026-02-14之后需要把日期轉(zhuǎn)成當(dāng)天的起始時(shí)間范圍再查否則數(shù)據(jù)庫里的depart_time是帶時(shí)分秒的用等值匹配會(huì)查不到select idselectByCondition resultTypecom.example.bus.entity.Bus SELECT * FROM bus WHERE depart_station #{departStation} AND arrive_station #{arriveStation} AND depart_time gt; #{startTime} AND depart_time lt; #{endTime} AND status 1 ORDER BY depart_time ASC /selectstartTime和endTime在Service層拼傳入的日期加00:00:00和23:59:59。這個(gè)邊界處理是班次查詢接口最常見的改bug現(xiàn)場(chǎng)很多人查不到當(dāng)天的車次最先懷疑表數(shù)據(jù)有問題實(shí)際上時(shí)間范圍沒拼對(duì)。4.3 下單頁與訂單列表狀態(tài)驅(qū)動(dòng)的頁面渲染小程序端購票流程有四個(gè)頁面流轉(zhuǎn)班次列表 → 確認(rèn)訂單 → 支付結(jié)果 → 訂單列表。不要在多個(gè)頁面間靠wx.navigateTo傳一整對(duì)象正確的做法是只傳orderNo或busId進(jìn)下個(gè)頁面再請(qǐng)求一次詳情。// 選擇班次后跳轉(zhuǎn)確認(rèn)訂單頁 goConfirm(e) { const bus e.currentTarget.dataset.bus; // list-item里綁定的數(shù)據(jù) wx.navigateTo({ url: /pages/confirm/confirm?busId${bus.busId}ticketCount1 }); }訂單列表頁有個(gè)使用體驗(yàn)優(yōu)化點(diǎn)下拉刷新要重新請(qǐng)求數(shù)據(jù)而不是復(fù)用頁面棧里的舊數(shù)據(jù)。微信小程序頁面的onShow生命周期在navigateBack回來后會(huì)觸發(fā)所以訂單支付成功返回列表時(shí)列表要在onShow里重新拉取否則狀態(tài)停留在「待支付」。onShow() { this.loadOrders(); },這類流量小的小程序不需要考慮數(shù)據(jù)緩存和虛擬列表但onShow觸發(fā)請(qǐng)求是個(gè)好習(xí)慣讓數(shù)據(jù)保持最新。如果需要離線展示就把接口返回storage一下下次onLoad先讀緩存再靜默刷新這是后面還能升級(jí)的方向。5. 部署與排查客運(yùn)小程序從源碼到聯(lián)調(diào)的5個(gè)真實(shí)踩坑5.1 真機(jī)預(yù)覽時(shí)接口不通域名白名單卡住現(xiàn)象模擬器里請(qǐng)求后端接口正常手機(jī)預(yù)覽時(shí)所有請(qǐng)求全部失敗console報(bào)url not in domain list。原因微信小程序真機(jī)環(huán)境強(qiáng)制校驗(yàn)白名單所有請(qǐng)求域名必須在小程序后臺(tái)配置為合法域名且必須是HTTPS。本地開發(fā)的http://localhost:8080自然不在名單里。解決開發(fā)階段在微信開發(fā)者工具右上角「詳情」→「本地設(shè)置」里勾選「不校驗(yàn)合法域名」手機(jī)預(yù)覽時(shí)啟用「真機(jī)調(diào)試」而不是「預(yù)覽」模式。等到上線前把后端接口域名配成HTTPS并加到小程序后臺(tái)的request合法域名里。注意這個(gè)配置修改要小程序管理員審核生效一般有幾分鐘延遲別剛提交完就急著測(cè)。5.2 登錄攔截器失效下單接口裸奔現(xiàn)象用Postman直接調(diào)下單接口不傳token也能下單成功。原因攔截器配置的exclude-mapping范圍寫大了/api/**包住了下單接口或者攔截器根本沒注冊(cè)。解決拿到代碼先全局搜exclude-mapping和WebMvcConfigurer逐個(gè)核對(duì)哪些接口該放行。放行清單一般只有登錄接口和班次查詢接口其余一律攔。驗(yàn)證時(shí)可以寫個(gè)最簡(jiǎn)單的小程序測(cè)試頁不發(fā)token請(qǐng)求一下能用就是大bug。5.3 數(shù)據(jù)庫里的時(shí)間比本地快了8小時(shí)現(xiàn)象插入訂單的create_time比服務(wù)器當(dāng)前時(shí)間快了8小時(shí)。原因MySQL連接的useUnicodetruecharacterEncodingutf8沒有配serverTimezoneAsia/Shanghai時(shí)區(qū)被當(dāng)成了UTC。解決數(shù)據(jù)庫連接串改成下面這樣同時(shí)確認(rèn)MySQL服務(wù)端時(shí)區(qū)是東八區(qū)執(zhí)行SHOW VARIABLES LIKE %time_zone%;檢查。jdbc.urljdbc:mysql://localhost:3306/bus_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse5.4 小程序端改完代碼不生效現(xiàn)象改了request.js里的BASE_URL重新編譯后請(qǐng)求還是打到老地址。原因微信開發(fā)者工具對(duì)JS文件有緩存CtrlS不一定觸發(fā)全量編譯。解決點(diǎn)擊工具欄「編譯」按鈕旁邊的小三角選「清緩存并完整編譯」或者直接關(guān)掉工具重新打開。提交代碼前養(yǎng)成習(xí)慣最后測(cè)一次「清緩存編譯」別讓隊(duì)友拉代碼時(shí)踩緩存坑。5.5 訂單表沒有索引列表頁越用越慢現(xiàn)象訂單量到幾千條后接口返回明顯變慢要800ms以上。原因訂單表只建了主鍵索引查詢user_id維度時(shí)全表掃描。小項(xiàng)目早期數(shù)據(jù)量小感知不明顯但客運(yùn)站一天幾百單很正常一周就上萬條。解決給user_id、bus_id、create_time單獨(dú)加索引。這類表不需要聯(lián)合索引因?yàn)椴樵儣l件通常是user_id status或者bus_id depart_time在user_id和bus_id上建單列索引就能滿足建聯(lián)合索引反而浪費(fèi)空間ALTER TABLE ticket_order ADD INDEX idx_user_status (user_id, status);6. 二開技巧把固定線路改成可配置順帶學(xué)會(huì)驗(yàn)證SSM接口的正確姿勢(shì)畢業(yè)設(shè)計(jì)源碼里的線路通常是用SQL腳本寫死的比如「北京-上?!姑刻靸砂?。實(shí)際用起來站長要維護(hù)發(fā)車時(shí)間、改票價(jià)、增開班次所以二開第一件事就是把班次表管理做成個(gè)配置頁面。后端加一個(gè)管理端接口把班次表設(shè)計(jì)成按日期生成每日班次而非直接修改基礎(chǔ)班次表。常見做法是新增一張bus_plan表存模板每天凌晨或首次查詢時(shí)根據(jù)模板日期生成當(dāng)天實(shí)際班次這樣能支持節(jié)假日臨時(shí)加開又不用動(dòng)基礎(chǔ)表CREATE TABLE bus_plan ( plan_id INT PRIMARY KEY AUTO_INCREMENT, route_id INT NOT NULL, bus_no VARCHAR(20) NOT NULL COMMENT 車牌號(hào)或車次, depart_time VARCHAR(5) NOT NULL COMMENT HH:mm, arrive_time VARCHAR(5) NOT NULL COMMENT HH:mm, valid_from DATE NOT NULL, valid_to DATE NOT NULL, ticket_price DECIMAL(8,2) NOT NULL, weekday_mask VARCHAR(7) DEFAULT 1111111 COMMENT 周一到周日是否運(yùn)行1運(yùn)行 0停運(yùn) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;小程序端要響應(yīng)這個(gè)變動(dòng)班次列表請(qǐng)求帶上日期后端把當(dāng)天的班次從bus_plan里算出來而不是查bus表。驗(yàn)證方法上我習(xí)慣在SSM項(xiàng)目里做三件事第一步用Postman跑一遍異常路徑比如傳個(gè)不存在的busId下單看是否會(huì)報(bào)錯(cuò)而不是返回null第二步看SQL日志通過控制臺(tái)打印的MyBatis日志確認(rèn)每個(gè)接口只發(fā)了預(yù)期數(shù)量的SQL避免N1查詢第三步打開Spring的事務(wù)日志logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManagerDEBUG確認(rèn)下單接口真正開啟了事務(wù)而查詢接口沒有。這套源碼我改過不止一次最深的一條教訓(xùn)是別迷信整站源碼這四個(gè)字進(jìn)入項(xiàng)目的第一天就檢查攔截器配了哪些放行、事務(wù)注解落在哪一層、SQL腳本的編碼對(duì)不對(duì)——這三處不出問題項(xiàng)目基本就穩(wěn)了一半。我在交付自己負(fù)責(zé)的系統(tǒng)時(shí)也是這樣先把這三處定下來再動(dòng)業(yè)務(wù)代碼希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取