約系統(tǒng)實戰(zhàn)開發(fā))
寵物美容預(yù)約是線下寵物門店非常典型的業(yè)務(wù)場景客戶需要提前挑選服務(wù)項目、選擇門店和美容師、確定到店時間門店則需要根據(jù)人力排班安排接待。傳統(tǒng)的人工預(yù)約方式容易出現(xiàn)信息遺漏、時間段沖突、客戶到店等待時間過長等問題。本文以“基于微信小程序的寵物美容預(yù)約系統(tǒng)”為例從需求分析、架構(gòu)設(shè)計、數(shù)據(jù)庫設(shè)計、后端接口開發(fā)到小程序前端實現(xiàn)完整梳理一套可以實際運行的預(yù)約系統(tǒng)開發(fā)流程。適合正在做微信小程序課程設(shè)計、畢業(yè)設(shè)計或者想在真實項目中搭建預(yù)約類業(yè)務(wù)功能的開發(fā)者閱讀。整篇文章會圍繞“用戶端小程序 后端接口 數(shù)據(jù)庫”展開包含完整建表 SQL、Spring Boot 后端核心代碼、原生微信小程序前端代碼以及聯(lián)調(diào)部署階段的常見問題。你可以把它當作一個可復用的預(yù)約系統(tǒng)最小閉環(huán)在此基礎(chǔ)上繼續(xù)擴展商家端、支付、消息通知等能力。1. 項目背景與需求分析1.1 業(yè)務(wù)場景與痛點寵物美容服務(wù)和普通商品購買不同它強依賴“人”和“時間”。一位美容師同時段只能服務(wù)一到兩只寵物剪刀、吹水機、洗護臺都存在物理上限因此門店必須提前知道什么時間段有客戶來才能合理排班、準備洗護物料。如果只靠微信群接龍、電話登記或到店排隊會出現(xiàn)幾個典型問題客戶和門店對時間理解不一致經(jīng)常出現(xiàn)“我約了下午兩點門店沒記錄”的扯皮。高峰期時段被重復占用門店無法直觀看到哪個時段已經(jīng)約滿。客戶信息、寵物信息每次都要手填體驗差且容易記錯品種、體重。預(yù)約記錄散落在聊天記錄里無法追溯也無法統(tǒng)計門店經(jīng)營數(shù)據(jù)。預(yù)約系統(tǒng)的核心價值就是把“客戶想約的時間”和“門店可服務(wù)的時間”放到同一個透明池子里進行匹配通過數(shù)據(jù)庫鎖和事務(wù)保證同一個時段不會被重復預(yù)約。1.2 功能模塊拆解面向普通用戶的微信小程序端主要包含以下功能模塊功能模塊說明微信登錄通過 wx.login 獲取 code后端調(diào)用微信接口換取 openid完成靜默注冊登錄首頁服務(wù)展示展示寵物美容服務(wù)項目包括洗澡、美容造型、SPA 等附帶價格和預(yù)計時長寵物檔案管理用戶維護自己的寵物信息包括寵物名、類型、品種、體重、備注預(yù)約時直接選擇預(yù)約下單選擇寵物、服務(wù)項目、預(yù)約日期和時段生成預(yù)約訂單預(yù)約記錄查看歷史預(yù)約了解訂單狀態(tài)支持取消未開始的預(yù)約個人中心展示用戶信息、門店地址、聯(lián)系電話、營業(yè)時間等商家端一般做成獨立的管理后臺或者商家版小程序本文重點介紹用戶端閉環(huán)同時會在后端保留商家核銷的狀態(tài)流轉(zhuǎn)邏輯方便后續(xù)擴展。1.3 角色與核心業(yè)務(wù)流程系統(tǒng)涉及兩個核心角色普通用戶和門店管理員。核心預(yù)約流程如下用戶打開小程序自動完成微信登錄。用戶在“寵物”頁維護寵物檔案首次使用需要先添加寵物。用戶在首頁選擇美容服務(wù)項目點擊“立即預(yù)約”。用戶選擇要服務(wù)的寵物、預(yù)約日期、可預(yù)約時段填寫備注。系統(tǒng)校驗時段是否可約生成預(yù)約訂單并占用對應(yīng)時段名額。用戶到店后商家在管理端核銷訂單訂單狀態(tài)從“待服務(wù)”變?yōu)椤耙逊?wù)”。如果用戶無法到店可以在訂單開始前取消預(yù)約系統(tǒng)釋放時段名額。這里的難點在第 5 步多個用戶同時搶同一個時段時必須保證“時段名額扣減”和“訂單生成”是原子操作否則就會出現(xiàn)超賣。2. 系統(tǒng)架構(gòu)與技術(shù)選型2.1 技術(shù)棧說明本文選用的技術(shù)棧是原生微信小程序 Spring Boot MySQL這也是目前課程設(shè)計和中小型項目中比較主流、參考資料最多的一套組合。層級技術(shù)選型說明前端原生微信小程序WXML 負責頁面結(jié)構(gòu)WXSS 負責樣式JS 負責交互邏輯后端Spring Boot 2.x提供 RESTful 接口處理登錄、預(yù)約等業(yè)務(wù)ORMMyBatis-Plus簡化單表 CRUD核心業(yè)務(wù)仍然使用自定義 SQL 控制并發(fā)數(shù)據(jù)庫MySQL 5.7 / 8.0存儲用戶、寵物、服務(wù)項目、時段、預(yù)約訂單數(shù)據(jù)本地聯(lián)調(diào)微信開發(fā)者工具 Postman小程序端聯(lián)調(diào)和后端接口測試如果你的項目更偏向輕量快速也可以把后端換成微信云開發(fā)直接用云函數(shù) 云數(shù)據(jù)庫實現(xiàn)。兩種方案各有優(yōu)劣云開發(fā)省去服務(wù)器運維適合快速上線傳統(tǒng)后端更便于理解分層架構(gòu)適合學習框架設(shè)計和面試答辯。2.2 整體架構(gòu)與請求流程整個系統(tǒng)可以理解成三條鏈路登錄鏈路小程序wx.login拿到臨時 code → 請求后端登錄接口 → 后端用 code 換取 openid → 查庫或注冊用戶 → 返回用戶信息。數(shù)據(jù)查詢鏈路小程序通過封裝好的 request 工具請求后端接口后端查詢數(shù)據(jù)庫返回統(tǒng)一格式 JSON。預(yù)約鏈路小程序提交預(yù)約參數(shù) → 后端校驗寵物歸屬、服務(wù)狀態(tài)、時段余量 → 事務(wù)內(nèi)生成訂單并扣減時段名額 → 返回訂單號。后端接口統(tǒng)一返回{ code, message, data }結(jié)構(gòu)小程序端在 request 封裝里統(tǒng)一處理業(yè)務(wù)碼和網(wǎng)絡(luò)異常業(yè)務(wù)頁面只需要關(guān)注成功回調(diào)中的 data 即可。2.3 項目目錄結(jié)構(gòu)后端工程采用標準 Spring Boot 分層結(jié)構(gòu)pet-grooming-server ├── pom.xml └── src/main/java/com/pet ├── PetGroomingApplication.java ├── common │ ├── Result.java │ └── ServiceException.java ├── config │ └── RestTemplateConfig.java ├── controller │ ├── UserController.java │ ├── ServiceController.java │ └── AppointmentController.java ├── entity │ ├── User.java │ ├── Pet.java │ ├── ServiceItem.java │ ├── AppointmentSlot.java │ └── Appointment.java ├── mapper │ ├── UserMapper.java │ ├── PetMapper.java │ ├── ServiceItemMapper.java │ ├── AppointmentSlotMapper.java │ └── AppointmentMapper.java └── service ├── UserService.java └── AppointmentService.java小程序端目錄結(jié)構(gòu)miniprogram ├── app.js ├── app.json ├── app.wxss ├── utils │ └── request.js └── pages ├── index // 首頁服務(wù)列表 ├── booking // 預(yù)約下單頁 ├── pet // 寵物檔案頁 ├── order // 預(yù)約記錄頁 └── profile // 個人中心3. 數(shù)據(jù)庫設(shè)計3.1 表關(guān)系概覽數(shù)據(jù)庫設(shè)計是預(yù)約系統(tǒng)的地基。本文設(shè)計五張核心表關(guān)系如下sys_user用戶表通過 openid 唯一標識微信用戶。pet寵物表一個用戶可擁有多只寵物多對一關(guān)系。service_item服務(wù)項目表比如“小型犬洗澡”“貓咪美容”。appointment_slot預(yù)約時段表存儲某天某個時間段可預(yù)約數(shù)量。appointment預(yù)約訂單表關(guān)聯(lián)用戶、寵物、服務(wù)項目、時段。時段單獨建表而不是在訂單表里直接填一個時間字符串是因為預(yù)約系統(tǒng)需要“庫存”概念。每個時段有 total_count 和 booked_count 兩個字段下單時已經(jīng)約滿的時段不能繼續(xù)選擇。3.2 核心表字段設(shè)計要點用戶表使用openid作為唯一業(yè)務(wù)鍵不要用自增 id 作為微信登錄的判定條件因為同一用戶重新授權(quán)后 id 不會變但不同用戶不可能共享 openid。寵物表必須冗余user_id作為歸屬標識后端在預(yù)約接口中要校驗“這只寵物是否屬于當前用戶”防止越權(quán)預(yù)約別人的寵物。預(yù)約訂單表使用獨立的order_no訂單號而不是依賴自增主鍵。訂單號可以用時間戳 隨機數(shù)生成也可以使用雪花算法方便后續(xù)對接物流、支付和對賬。時段表需要唯一索引(merchant_id, slot_date, start_time)從數(shù)據(jù)庫層面保證同一天同一個開始時間不會出現(xiàn)兩條重復配置。3.3 建表 SQL 與初始化數(shù)據(jù)CREATE DATABASE IF NOT EXISTS pet_grooming DEFAULT CHARACTER SET utf8mb4; USE pet_grooming; -- 用戶表 CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主鍵, openid VARCHAR(64) NOT NULL COMMENT 微信openid, nickname VARCHAR(50) DEFAULT COMMENT 昵稱, avatar_url VARCHAR(255) DEFAULT COMMENT 頭像, phone VARCHAR(20) DEFAULT COMMENT 手機號, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB COMMENT用戶表; -- 寵物表 CREATE TABLE pet ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主鍵, user_id BIGINT NOT NULL COMMENT 所屬用戶ID, name VARCHAR(30) NOT NULL COMMENT 寵物名稱, category VARCHAR(20) NOT NULL COMMENT 寵物類型dog/cat/其他, breed VARCHAR(30) DEFAULT COMMENT 品種, age INT DEFAULT 0 COMMENT 年齡月, gender TINYINT DEFAULT 0 COMMENT 0未知 1公 2母, weight DECIMAL(5,2) DEFAULT 0 COMMENT 體重kg, remark VARCHAR(255) DEFAULT COMMENT 備注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB COMMENT寵物表; -- 服務(wù)項目表 CREATE TABLE service_item ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主鍵, merchant_id BIGINT NOT NULL DEFAULT 1 COMMENT 門店ID, name VARCHAR(50) NOT NULL COMMENT 服務(wù)名稱, description VARCHAR(255) DEFAULT COMMENT 服務(wù)說明, price DECIMAL(10,2) NOT NULL COMMENT 價格, duration INT NOT NULL DEFAULT 60 COMMENT 預(yù)計時長分鐘, image VARCHAR(255) DEFAULT COMMENT 圖片, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT服務(wù)項目表; -- 預(yù)約時段表 CREATE TABLE appointment_slot ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主鍵, merchant_id BIGINT NOT NULL COMMENT 門店ID, slot_date DATE NOT NULL COMMENT 日期, start_time VARCHAR(10) NOT NULL COMMENT 開始時間 HH:mm, end_time VARCHAR(10) NOT NULL COMMENT 結(jié)束時間 HH:mm, total_count INT DEFAULT 1 COMMENT 可預(yù)約數(shù)量, booked_count INT DEFAULT 0 COMMENT 已預(yù)約數(shù)量, status TINYINT DEFAULT 1 COMMENT 1可約 0關(guān)閉, PRIMARY KEY (id), UNIQUE KEY uk_slot (merchant_id, slot_date, start_time) ) ENGINEInnoDB COMMENT預(yù)約時段表; -- 預(yù)約訂單表 CREATE TABLE appointment ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主鍵, order_no VARCHAR(32) NOT NULL COMMENT 訂單編號, user_id BIGINT NOT NULL COMMENT 用戶ID, pet_id BIGINT NOT NULL COMMENT 寵物ID, merchant_id BIGINT NOT NULL DEFAULT 1 COMMENT 門店ID, service_item_id BIGINT NOT NULL COMMENT 服務(wù)項目ID, slot_id BIGINT NOT NULL COMMENT 時段ID, appointment_date DATE NOT NULL COMMENT 預(yù)約日期, start_time VARCHAR(10) DEFAULT COMMENT 開始時間, end_time VARCHAR(10) DEFAULT COMMENT 結(jié)束時間, amount DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 金額, status TINYINT NOT NULL DEFAULT 1 COMMENT 1待服務(wù) 2已服務(wù) 3已取消, remark VARCHAR(255) DEFAULT COMMENT 用戶備注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB COMMENT預(yù)約訂單表;初始化幾條服務(wù)項目數(shù)據(jù)INSERT INTO service_item (merchant_id, name, description, price, duration) VALUES (1, 小型犬洗澡, 包含洗澡、吹干、耳部清潔, 68.00, 60), (1, 中型犬洗澡, 包含洗澡、吹干、耳部清潔, 88.00, 90), (1, 貓咪精致美容, 包含洗澡、吹干、修剪指甲, 108.00, 90), (1, 狗狗造型設(shè)計, 包含洗澡、修剪造型、指甲修剪, 168.00, 120);初始化某一天的預(yù)約時段這里以 2025 年為例實際使用時應(yīng)由商家后臺按需生成INSERT INTO appointment_slot (merchant_id, slot_date, start_time, end_time, total_count, booked_count) VALUES (1, 2025-03-10, 09:00, 11:00, 2, 0), (1, 2025-03-10, 10:00, 12:00, 2, 0), (1, 2025-03-10, 13:00, 15:00, 2, 0), (1, 2025-03-10, 14:00, 16:00, 2, 0);需要說明的是slot_date和start_time只是示例數(shù)據(jù)你可以根據(jù)門店實際營業(yè)時間生成后 7 天的時段。生產(chǎn)環(huán)境中通常用定時任務(wù)每天滾動生成未來 N 天的時段避免人工維護。4. 環(huán)境準備與項目初始化4.1 注冊小程序賬號與 AppID開發(fā)微信小程序之前需要到微信公眾平臺注冊一個小程序賬號類型選擇“個人”或“企業(yè)”都可以。注冊完成后在“開發(fā)管理 → 開發(fā)設(shè)置”頁面可以看到 AppID 和 AppSecret。這里有兩個注意事項個人主體的小程序部分能力受限但開發(fā)預(yù)約系統(tǒng)的基本功能沒有問題。AppSecret 非常敏感只能保存在后端服務(wù)器配置中絕不能寫在小程序前端代碼里否則任何人都能通過你的 AppSecret 獲取用戶信息。如果暫時不想注冊賬號也可以使用微信開發(fā)者工具里的“測試號”但測試號無法體驗完整的登錄能力建議正式開發(fā)時使用真實 AppID。4.2 安裝微信開發(fā)者工具從微信官方渠道下載穩(wěn)定版微信開發(fā)者工具安裝后使用小程序賬號掃碼登錄。創(chuàng)建項目時選擇“小程序”而不是“小游戲”后端服務(wù)選擇“不使用云服務(wù)”AppID 填入 4.1 節(jié)獲取的 ID。開發(fā)階段登錄接口需要真實 AppID 和 AppSecret 才能換取 openid因此建議在application.yml中配置自己的參數(shù)。4.3 后端環(huán)境準備后端需要準備 JDK、Maven、MySQL。版本不需要完全固定以下組合在本文測試環(huán)境中可以正常工作實際開發(fā)時請以本機環(huán)境為準JDK 1.8 或 11Maven 3.6MySQL 5.7 或 8.0IDEA 或 Eclipse先用 3.3 節(jié) SQL 在 MySQL 中建好數(shù)據(jù)庫和表。注意如果本機 MySQL 密碼不是123456后續(xù)配置application.yml時要做對應(yīng)修改。5. 后端接口設(shè)計與實現(xiàn)5.1 項目依賴與基礎(chǔ)配置創(chuàng)建一個 Spring Boot 工程pom.xml核心依賴如下。版本號建議以你創(chuàng)建工程時的 Spring Boot 版本為準不要盲目復制。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesapplication.yml配置文件server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_grooming?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl wechat: appid: 你的AppID secret: 你的AppSecretmap-underscore-to-camel-case開啟后數(shù)據(jù)庫字段user_id可以自動映射到實體屬性userId省去大量手動映射。log-impl配置會在控制臺打印 SQL方便開發(fā)階段排查問題生產(chǎn)環(huán)境建議關(guān)閉或改為異步日志。5.2 統(tǒng)一返回結(jié)果封裝所有接口統(tǒng)一返回ResultT這樣前端只需要關(guān)注一種結(jié)構(gòu)也方便后續(xù)增加全局異常處理。// 文件路徑src/main/java/com/pet/common/Result.java package com.pet.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }再定義一個簡單的業(yè)務(wù)異常類方便在 Service 中主動拋出錯誤// 文件路徑src/main/java/com/pet/common/ServiceException.java package com.pet.common; public class ServiceException extends RuntimeException { public ServiceException(String message) { super(message); } }生產(chǎn)項目中還應(yīng)該配合RestControllerAdvice做統(tǒng)一異常處理避免異常堆棧直接暴露給前端。這里為了保持示例簡潔只保留了最小閉環(huán)。5.3 微信登錄接口微信登錄流程是很多新手容易繞暈的地方。簡單理解就是小程序端拿不到 openid只能通過wx.login拿到一個臨時憑證 code后端拿著 code 去微信服務(wù)器換 openid。code 有效期很短且只能使用一次所以不存在把 code 泄露給別人的風險。需要注意調(diào)用微信接口時需要配置 RestTemplate 這個 Bean。先寫配置類// 文件路徑src/main/java/com/pet/config/RestTemplateConfig.java package com.pet.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; Configuration public class RestTemplateConfig { Bean public RestTemplate restTemplate() { return new RestTemplate(); } }UserService 實現(xiàn)// 文件路徑src/main/java/com/pet/service/impl/UserServiceImpl.java package com.pet.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.pet.common.ServiceException; import com.pet.entity.User; import com.pet.mapper.UserMapper; import com.pet.service.UserService; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; Service public class UserServiceImpl implements UserService { Resource private RestTemplate restTemplate; Resource private ObjectMapper objectMapper; Resource private UserMapper userMapper; Value(${wechat.appid}) private String appId; Value(${wechat.secret}) private String secret; Override Transactional(rollbackFor Exception.class) public User login(String code) throws Exception { String url https://api.weixin.qq.com/sns/jscode2session ?appid appId secret secret js_code code grant_typeauthorization_code; String response restTemplate.getForObject(url, String.class); JsonNode node objectMapper.readTree(response); String openid node.path(openid).asText(); if (openid.isEmpty()) { throw new ServiceException(微信登錄失敗請檢查 appid 和 secret 配置); } User user userMapper.selectOne( new LambdaQueryWrapperUser().eq(User::getOpenid, openid) ); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用戶); userMapper.insert(user); } return user; } }這里要重點解釋一下為什么用openid而不是自增 id 判斷老用戶每個微信用戶在小程序下的 openid 是唯一的用戶刪除小程序后再次進入openid 也不會變因此 openid 天然可以作為業(yè)務(wù)主鍵。自增 id 只是數(shù)據(jù)庫內(nèi)部主鍵不能用于識別用戶身份。Transactional保證查詢和插入的原子性。生產(chǎn)項目中登錄成功后建議生成自己的 token 返回給前端后續(xù)請求攜帶 token服務(wù)端通過 token 識別用戶而不是每次都把 userId 放在請求參數(shù)里。本文示例為了展示業(yè)務(wù)主流程簡化了這一步在第 9 節(jié)會專門強調(diào)安全改造方案。5.4 服務(wù)項目接口服務(wù)項目接口比較簡單查詢所有上架項目即可// 文件路徑src/main/java/com/pet/controller/ServiceController.java package com.pet.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.pet.common.Result; import com.pet.entity.ServiceItem; import com.pet.mapper.ServiceItemMapper; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; RestController RequestMapping(/api/service) public class ServiceController { Resource private ServiceItemMapper serviceItemMapper; GetMapping(/list) public ResultListServiceItem list() { ListServiceItem list serviceItemMapper.selectList( new LambdaQueryWrapperServiceItem() .eq(ServiceItem::getStatus, 1) .orderByAsc(ServiceItem::getId) ); return Result.success(list); } }LambdaQueryWrapper是 MyBatis-Plus 提供的類型安全查詢構(gòu)造器使用實體方法引用而不是字符串字段名編譯期就能發(fā)現(xiàn)字段拼寫錯誤。這個接口返回的數(shù)據(jù)會直接渲染到小程序首頁。5.5 預(yù)約下單接口與并發(fā)控制預(yù)約下單是整個系統(tǒng)的核心接口也是技術(shù)上最有含量的一塊。先看業(yè)務(wù)校驗順序校驗寵物是否存在且屬于當前用戶。校驗服務(wù)項目是否存在且已上架。鎖定預(yù)約時段判斷余量。生成訂單扣減時段余量。第三步需要特別處理。如果兩個用戶同時預(yù)約同一個時段直接采用“先查 booked_count 再 update”的方式可能兩個請求都讀到剩余 1結(jié)果兩個訂單都成功數(shù)據(jù)上就出現(xiàn)了超賣。解決方式是在事務(wù)中給時段記錄加行鎖。AppointmentSlotMapper 中定義如下方法// 文件路徑src/main/java/com/pet/mapper/AppointmentSlotMapper.java package com.pet.mapper; import com.baomidou.mybatisplus.core.mapper