尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java微信小程序挂号项目:后端状态同步与并发扣减实战

Java微信小程序挂号项目:后端状态同步与并发扣减实战 简介一套基于Java与微信小程序的医院预约挂号系统源码包面向有课设/毕设需求或想学习前后端分离项目的开发者。系统后台采用SpringBootMyBatisPlusMySQLRedis前台基于uni-appVue构建覆盖用户注册登录、预约挂号、核酸检测预约记录、医院公告、科室排班、就诊人管理等功能管理员与医生后台分别集成用户管理、排班管理、预约记录、科室疾病职位医生管理、公告管理等模块。资源共185个文件以113个vue组件、41个js逻辑脚本、10个scss样式以及json配置文件为主压缩包整体517KB结构清晰便于按模块学习与二次开发。已有2377人学习下载适合据此理解医院预约流程设计、角色权限划分及微信小程序端与Java后端的交互方式直接导入数据库并配置即可运行。1. 这个 Java 微信小程序挂号项目价值在后端的状态同步把 Java 医院预约挂号系统这套源码解压之后大多数人会被小程序端密密麻麻的页面和 Java 端一堆 controller 吓到误以为工作量全在前端。实际把一个完整流程跑通就会发现真正的难点都在后端挂号要做号源原子扣减停诊要处理已经产生的预约微信登录要维护自己的会话任何一个环节偷懒项目就只能停留在演示层面。这套代码整理的是一条面向课程设计与小型真实项目的完整闭环覆盖小程序挂号、医生排班、预约订单和后台接口适合拿来当 java 课程设计案例源码研读也很适合放在 java 后端完整成长路线上练手。只要把后端、MySQL 和小程序三端在本地打通它的价值就超过大部分只贴界面的课设。2. 先按业务拆项目再读代码预约系统的模块与数据模型2.1 把挂号预约拆成四个业务模块拿到任何 Spring Boot 小程序的预约源码我习惯先把 IDE 关掉在一张纸上把业务画出来。医院预约挂号听起来就一个动作“挂号”实际上牵扯到两个端、四类角色患者在小程序里浏览医生排班、选时段、提交预约、取消或查看记录后端负责校验号源、写订单、更新排班余量管理端虽然很多课设只留接口但医生维护、停诊、放号这些操作总归要有人触发数据库则要保证“号源不超卖”这个底线。把业务拆开之后代码才会呈现出清晰的脉络用户模块患者授权登录。后端拿 openid 关联用户不存明文密码也不该把前端传来的用户信息当凭证。医生与科室模块维护科室、医生姓名、职称、擅长领域通常是后台 CRUD 加上小程序端的只读列表。排班模块一个医生在某个日期的上午或下午放出多少号剩余多少号。这是整个系统的库存表。预约模块生成预约单、取消预约、标记就诊完成、处理过期未就诊。它和排班模块之间是强耦合任何状态变化都必须同步修改号源余量。管理端有的项目附带一个网页后台有的只提供接口。我在实际项目中见过不少只做了小程序端却把管理端砍掉的课设验收时照样能过但你自己练手时补一个简单的管理接口对理解数据权限帮助很大。前后端分离是这个项目的默认形态小程序端只负责渲染页面和发请求所有业务判断都收回到 Java 端。这么做的好处是以后想再套一个 H5 页面或者独立的管理端后端接口可以直接复用不用改动业务层。2.2 解压之后先认目录三类文件分别对应什么角色课程设计项目的压缩包结构通常比较规整解压后一般会遇到下面这种布局。注意不同版本文件名会有些差异但大类基本一致hospital-register/ ├── sql/ │ └── init.sql ├── server/ │ ├── pom.xml │ └── src/main/ │ ├── java/com/hospital/register/ │ │ ├── controller/ │ │ ├── service/ │ │ ├── mapper/ │ │ ├── model/ │ │ └── config/ │ └── resources/ │ ├── application.yml │ └── mapper/ └── miniprogram/ ├── app.js ├── app.json ├── utils/ │ └── request.js └── pages/ ├── index/ ├── booking/ ├── order/ └── mine/拿到这个目录后我建议的阅读顺序是先打开 sql/init.sql再读 application.yml然后从 controller 层往下追。不要一上来就翻 service 实现因为你不先搞清楚它连接的是哪张表、字段名是什么读业务代码会到处撞墙。server 目录是标准 Spring Boot 工程pom.xml 里依赖数量不会太多核心就是 web、mybatis、mysql-connector 这几个。resources/mapper 下是 MyBatis 的 XML 映射文件课程设计最常见的写法是把一句一句 SQL 写在这里业务复杂到一定程度时XML 里的 SQL 会比 service 代码还长。config 包通常放跨域配置、拦截器配置和微信参数配置。miniprogram 目录就是小程序本体。app.json 里登记了所有页面路由utils/request.js 是对 wx.request 的封装pages 下每个页面由 wxml、wxss、js、json 四个文件组成。如果你习惯用 uniapp 开发微信小程序也可以把这个原生目录里的页面逻辑迁移到 uni-app 项目里接口语义保持不变只是把 wx.request 换成 uni.request前端在跨端复用时的改动会小很多。2.3 数据模型排班表和预约表是核心建表约束别偷懒数据库是这个项目最不该省力的地方。因为预约业务有一个天然特性同一个医生号源同一时刻可能有多个人在抢。如果建表时不在数据库层加约束光靠 Java 代码去判断剩余号源并发一上来就会翻车。典型的核心表大致如下表名作用关键字段备注doctor医生信息id, name, title, department_id, intro科室可以独立建表department科室信息id, name简单场景可并入 doctor 表schedule排班信息id, doctor_id, work_date, period, total_num, left_numperiod 区分上午下午appointment预约订单id, patient_id, schedule_id, appt_date, status, create_timestatus 标识预约状态patient患者信息id, openid, nickname, avataropenid 唯一admin后台用户id, username, password课设里常见其中 schedule 表是最容易被忽略的设计点。很多初版设计只有 doctor 表和 appointment 表让用户直接在前端选医生、填时间后端完全没有库存概念。这样做的结果就是预约单随便建随便删没有一个地方能回答“周五上午还有几个号”。schedule 表的建表语句建议这样做CREATE TABLE schedule ( id INT NOT NULL AUTO_INCREMENT, doctor_id INT NOT NULL COMMENT 医生ID, work_date DATE NOT NULL COMMENT 出诊日期, period TINYINT NOT NULL COMMENT 1上午 2下午, total_num INT NOT NULL DEFAULT 20 COMMENT 放号总数, left_num INT NOT NULL DEFAULT 20 COMMENT 剩余号源, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0停诊, PRIMARY KEY (id), UNIQUE KEY uk_doctor_date_period (doctor_id, work_date, period) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT医生排班表;unique key 是最重要的一行它保证同一个医生在同一天同一个半天只能有一条排班记录从根上把“重复放号”这个脏数据挡在数据库外面。left_num 这个字段在后续并发扣减中会被当成条件使用千万别建表时图省事只放 total_num。appointment 表的核心是状态字段一般用 tinyint 存。常见约定是 0 待就诊、1 已完成、2 已取消、3 已过期。不要用字符串存“待就诊”“已完成”既浪费空间又容易拼错后面在 Java 代码里定义一个状态常量类所有业务判断只跟数字打交道枚举或常量类二选一都行。3. 服务端把预约落库Spring Boot 三层与并发控制3.1 预约接口的最小闭环Controller、Service、Mapper 各管一段用 Spring Boot 写预约接口标准做法就是 controller 收参数、service 做业务、mapper 操作数据库三层各管一段。先看 controller 层RestController RequestMapping(/api/appointment) public class AppointmentController { Autowired private AppointmentService appointmentService; Autowired private LoginSession loginSession; PostMapping(/book) public Result book(RequestBody BookRequest req, RequestHeader(X-Token) String token) { Integer patientId loginSession.getUserIdByToken(token); if (patientId null) { return Result.error(401, 登录已过期请重新进入小程序); } return appointmentService.book(req, patientId); } }这里有几个参数要说明。RequestBody 负责把前端 POST 过来的 JSON 反序列化成 BookRequest 对象前提是 JSON 里的字段名和 Java 属性名一致比如前端传 scheduleIdJava 类里就得有 scheduleId 属性。X-Token 是约定的会话头小程序每次请求都在 header 里带上它后端从这个头里还原出当前用户是谁。小型项目可以自己用 Map 模拟会话生产环境一般换成 redis 来管理。再看 service 层这是预约接口最关键的地方Transactional(rollbackFor Exception.class) public Result book(BookRequest req, Integer patientId) { Schedule schedule scheduleMapper.selectById(req.getScheduleId()); if (schedule null) { return Result.error(排班不存在); } if (schedule.getStatus() ! 1) { return Result.error(该排班已停诊); } int updated scheduleMapper.decreaseLeftNum(schedule.getId()); if (updated 0) { return Result.error(号源已满请选择其他时段); } Appointment app new Appointment(); app.setPatientId(patientId); app.setScheduleId(schedule.getId()); app.setApptDate(schedule.getWorkDate()); app.setStatus(0); appointmentMapper.insert(app); return Result.ok(app.getId()); }Transactional 表示这个方法在一个事务里执行任何一步抛异常都会整体回滚包括已经扣掉的号源。这里有一个非常重要的顺序先扣号源再插入预约单。如果先查余量、再插入、再扣减中间会留下并发窗口。scheduleMapper.decreaseLeftNum 执行的 SQL 是条件更新它把扣减和判断合并成一个原子操作这一段的详细分析放在后面避坑章节展开。3.2 参数校验与业务规则停诊、过期号源、重复预约入门课设最常见的毛病是 controller 里只有 SQL 增删改查没有任何业务规则。预约系统至少要过三关。第一关是参数基础校验。scheduleId 必须大于 0patientId 必须存在。这些可以在 controller 层用手动判断也可以加 javax.validation 注解课程设计用前者更直观出错时也更容易从日志里定位。第二关是状态校验。排班可能被管理端停诊停诊之后按钮虽然在前端被置灰但接口必须再校验一次因为有人可以直接构造请求绕过页面。第三关是时间校验。至少禁止预约过去的日期更细一点还要约定号源过期规则比如上午的号过了 11 点就不可约。重复预约的检查要放在事务开头可以按 patient_id 和 schedule_id 查 appointment 表Appointment exist appointmentMapper.selectByPatientAndSchedule(patientId, scheduleId); if (exist ! null exist.getStatus() 0) { return Result.error(您已预约过该时段请勿重复提交); }这里的 status 条件意思是只有待就诊状态的预约算有效预约。如果用户先取消再重新预约预约记录虽然还在但状态是已取消不应该拦住。这也是为什么预约状态必须用数字而非直接删除记录取消操作在业务上要留痕。日期格式化也容易踩坑。前端传日期一般用 YYYY-MM-DD 字符串Java 端如果直接用 Date 接收时区一错日期就偏移一天。我的建议是直接用 String 接收日期字段MySQL 的 DATE 字段照样能存避免 LocalDateTime 和 Date 在序列化时来回折腾。小程序端选的日期是用户本地时间后端不要擅自转时区转错了会出现用户明明约的是下周一的号库里存成了上周日。3.3 微信登录从 code 到 openid会话要自己维护小程序登录和后端登录是两套体系。小程序端通过 wx.login() 拿到的是一个临时 code这个 code 需要交给后端由后端拿着 appid、secret 去微信服务器换 openid。openid 才能识别用户code 本身什么都查不到。public LoginResult wxLogin(String code, String nickname, String avatar) { String url https://api.weixin.qq.com/sns/jscode2session ?appid appId secret appSecret js_code code grant_typeauthorization_code; String resp restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(resp); String openid json.getString(openid); if (openid null) { throw new BizException(微信登录失败请稍后重试); } Patient patient patientMapper.selectByOpenid(openid); if (patient null) { patient new Patient(); patient.setOpenid(openid); patient.setNickname(nickname); patient.setAvatar(avatar); patientMapper.insert(patient); } String token UUID.randomUUID().toString().replace(-, ); loginSession.put(token, patient.getId()); return new LoginResult(token, patient); }这个方法里有四个关键点。appId 和 appSecret 来自微信公众平台是固定值但不能写在小程序前端代码里因为小程序代码打包后任何人都能打开看到secret 一旦泄露就等于登录接口裸奔。code 是一次性的只能用一次用完后自己会过期所以前端不能反复拿同一个 code 来调用。nickname 和 avatar 是前端在登录时顺手传过来的用来给首次注册的用户补全资料但后端不能把它们当成身份凭证身份凭证永远只有 openid。session 的维护方式课程设计最常见的做法是上面这种登录成功后生成一个 UUID token放到一个 ConcurrentHashMap 里键是 token值是 patientId。这个方案能跑但服务重启后所有登录态都会失效小程序端需要重新登录。想做得更稳把这个 Map 换成 Redis 就行token 的 key 和过期时间都不用改。如果按 java 后端完整成长路线来要求自己建议把会话管理单独抽一个 LoginService不要把 map 直接放进 Controller 里。后续接 redis、接多端登录、接 token 刷新都只改这一个类不用动业务代码。4. 小程序端与 Java 后端对接的完整参数链路4.1 统一封装 wx.requestbaseUrl、header、错误处理小程序端如果每个页面都直接写 wx.request项目一大就会散落几百个请求调用改一个 baseUrl 要全局替换。所以解压后第一件事就是确认有没有 utils/request.js 这个封装文件没有就自己补一个// utils/request.js const BASE_URL http://127.0.0.1:8080/api; function request(path, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method || GET, data: data || {}, header: { Content-Type: application/json, X-Token: wx.getStorageSync(token) || }, success(res) { if (res.statusCode 200 res.data.code 0) { resolve(res.data.data); } else if (res.data.code 401) { wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); reject(res.data); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail() { wx.showToast({ title: 网络异常请检查后端服务, icon: none }); reject(new Error(network error)); } }); }); } module.exports { request, BASE_URL };这个封装的核心是三个约定。第一个是 path 拼接页面里只需要写 request(/doctor/list, GET)不用管接口是 http 还是 https。第二个是 X-Token 自动携带后端靠这个头认人登录成功之后把 token 存进 storage后续每个请求自动带上。第三个是响应统一处理code 为 0 时把业务数据返回给页面401 时清除 token 并跳回登录页避免每个页面各自处理错误导致代码重复。BASE_URL 这个配置在你从开发者工具切到真机调试时会反复修改。127.0.0.1 只适合模拟器真机上要换成电脑在局域网里的 IP比如 http://192.168.1.101:8080/api前提是手机和电脑在同一个 WiFi 下。如果你申请并配置了 HTTPS 域名那就全项目只改这一处页面代码完全不用动。4.2 预约页面的号源选择与提交参数预约页面的交互通常是先选科室再选医生然后选日期最后在上午或下午两个时段里挑一个还有余号的排班。页面 JS 的核心逻辑是从后端拿 schedule 列表根据 left_num 判断哪些能点// pages/booking/booking.js const { request } require(../../utils/request); Page({ data: { doctorList: [], selectedDoctorId: , dateList: [], scheduleList: [] }, onLoad() { this.loadDoctors(); this.buildDateList(); }, async loadDoctors() { const list await request(/doctor/list, GET); this.setData({ doctorList: list }); }, buildDateList() { const dates []; for (let i 0; i 7; i) { const d new Date(Date.now() i * 86400000); const s ${d.getFullYear()}-${String(d.getMonth() 1).padStart(2, 0)}-${String(d.getDate()).padStart(2, 0)}; dates.push(s); } this.setData({ dateList: dates }); }, async onDateChange(e) { const date e.detail.value; const doctorId this.data.selectedDoctorId; if (!doctorId) return; const list await request(/schedule/list?doctorId${doctorId}date${date}, GET); this.setData({ scheduleList: list }); }, async onBook(e) { const scheduleId e.currentTarget.dataset.id; try { await request(/appointment/book, POST, { scheduleId }); wx.showToast({ title: 预约成功, icon: success }); } catch (err) { // 错误提示已经在 request 封装里统一处理 } } });参数说明onDateChange 里请求的 doctorId 和 date 是 URL 查询参数后端接口对应形如 /schedule/list?doctorId1date2024-05-20 的 GET 请求Spring Boot 里用 RequestParam Integer doctorId 接收注意名称要完全对齐。提交预约时 POST 的 body 只有 scheduleId 一个字段这个 ID 是排班表的主键后端通过它反查到医生、日期和时段前端不需要把整套日期信息传过去少传参数就少一次出错机会。页面 WXML 里号源按钮的禁用逻辑一般写成 disabled{{item.leftNum 0}}已约满的按钮置灰。这里有一个边界要意识到你进入页面时号源显示还剩 2 个等你点提交时可能已经被别人抢完所以按钮置灰只是体验优化真正的余量判断永远在后端。前端置灰不能当成业务保障。4.3 前后端状态码约定前端才不迷路小程序和后端之间如果各写各的错误处理调试起来非常痛苦。靠谱的项目都会在返回格式上达成统一。后端所有接口统一返回 Result 对象结构是 code、msg、data 三个字段例如{ code: 0, msg: ok, data: { appointmentId: 12 } }code含义前端处理0成功正常渲染 data400业务错误用 msg 弹 toast不跳转401会话失效清理 token跳转登录页500系统异常弹“系统繁忙”配合后端日志排查预约状态码也建议在后端定义成常量0 待就诊、1 已完成、2 已取消、3 已过期。小程序“我的预约”页面只展示状态为 0 的待就诊订单已过期或已取消的记录折叠起来这个判断可以直接用数字比较前端不需要理解中文含义也就不容易因为文案改动而错乱。前端要特别小心 401 的重复跳转。很多课设只在微信登录失败时才处理 401但实际项目中同一时间多端登录、服务重启、token 过期都可能让后端返回 401。如果列表页同时发了三个接口三个接口都返回 401就会触发三次 wx.navigateTo页面栈乱掉。所以在 request 封装里用一个 flag 拦住只跳转一次这是我在真实小程序里踩过的不起眼但很实际的坑。5. 避坑与常见问题预约系统最容易翻车的 5 个点5.1 中文乱码与表名大小写数据库连接串里藏着第一个坑现象后端启动后小程序里看到医生姓名和科室名称全是问号或者控制台直接报 Table hospital.doctor doesnt exist但数据库里明明有 doctor 表。原因分两层。乱码的根子大概率是 JDBC 连接串没写字符集参数MySQL 建库的默认字符集又不是 utf8mb4。表报不存在的根子也很经典Windows 开发环境 MySQL 默认大小写不敏感Linux 生产环境默认敏感代码里用 Doctor 去查实际存在的 doctor 表Windows 能跑、Linux 上就炸。解决建库语句写成 CREATE DATABASE hospital DEFAULT CHARACTER SET utf8mb4连接串里固定加上 characterEncodingutf8mb4 和 useUnicodetrue。表名和字段名在 Java 代码、XML、SQL 三个地方保持完全一致统一小写加下划线。如果你只在本地演示大小写怎么都不报错但一部署到云服务器就现出原形这是典型的跨环境翻车。5.2 MySQL 8 驱动报 Public Key Retrieval 和时区错误现象换到 MySQL 8 后Spring Boot 启动时抛异常 caching_sha2_password 或 The server time zone value Öйú±ê׼ʱ¼ä is unrecognized项目还没起完就退了。原因MySQL 8 默认认证插件改成了 caching_sha2_password老的 mysql-connector-java 驱动不认识同时数据库服务器和 Java 应用之间的时区没有对齐。时区错误那段报错信息里出现乱码看起来像编码玄学其实只是 MySQL 和 JVM 对时区名解析不一致。解决pom.xml 里把 MySQL 驱动升到 8.0.x。连接串加参数常见配置是spring: datasource: url: jdbc:mysql://localhost:3306/hospital?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DriverallowPublicKeyRetrievaltrue 在本地开发是必要的它允许客户端在第一次连接时向服务端请求公钥从而完成缓存加密密码验证。注意这只是本地调试姿势生产环境建议用内网连接或 SSL不要为了省事把这个参数一直开着。serverTimezoneAsia/Shanghai 统一了时间不然后端 LocalDateTime 类型的字段存进去会差 8 小时查出来日志里全是错位时间。5.3 真机预览时请求一直失败开发者工具却正常现象小程序在开发者工具里所有接口正常拿手机扫码预览后页面能打开但所有列表都是空的请求全部失败。原因开发者工具默认勾选了“不校验合法域名”手机预览则强制走真实域名校验。如果你没有配置 request 合法域名而是直接把 baseUrl 指向局域网 IP真机自然拒绝。另外即使暂时绕过域名校验手机访问 127.0.0.1 指向的是手机自己不是开发电脑这是最常见的误解。解决本地联调时开发者工具里可以勾选“不校验合法域名”但这个配置不生效于真机。手机上要访问本机后端需要让后端监听 0.0.0.0并把小程序 baseUrl 改成电脑局域网 IP。java -jar server/target/register.jar --server.address0.0.0.0 --server.port8080提醒一句监听 0.0.0.0 意味着局域网内任何设备都能访问你的接口本地调试没问题连陌生网络时要记得改回 127.0.0.1。这个问题并不复杂但它每年都能拦住一批刚接触小程序开发的人因为报错信息不直接看起来像网络玄学实际就是地址和域名校验那一层。5.4 同一时段被两个用户同时预约成功号源变负数现象并发测试或真实抢号时一个只放了 5 个号的排班预约成功的订单数超过了 5 条left_num 变成负数正好卡在多人同时提交的瞬间。原因代码写成“先 select left_num再判断再 update”两个请求同时读到 left_num 为 1都认为还有号然后分别执行 update余量被扣成负数。这个场景在 Java 并发编程面试里非常典型就是“超卖问题”的变种八股文里讲了无数次落到项目里仍然是课设重灾区。解决把“判断余量”和“扣减余量”合并成一条原子 SQLUPDATE schedule SET left_num left_num - 1 WHERE id #{scheduleId} AND left_num 0这条 SQL 返回受影响行数值为 1 说明扣减成功值为 0 说明没号了直接抛业务异常不需要事先 select。配合事务即使后续插入预约单失败扣减也会回滚。如果想再保险一点appointment 表里对 schedule_id 加一个唯一索引某个患者同一个排班只能产生一条记录从数据层面把重复预约也堵住。这一改动是整套系统从“能演示”走向“能落地”的分水岭。5.5 微信登录偶发提示 code 无效现象用户正常使用时没有任何规律地报“微信登录失败”“code 无效”重启小程序又好了后端日志里出现 errcode 40029。原因wx.login 返回的 code 有效期很短而且只能用一次。最常见场景是前端在页面 onLoad 里请求登录接口接口超时后用户又下拉刷新或重新进入页面同一个 code 被请求了两次后端第一次消费掉之后第二次再用就报 40029。解决前端保证每次登录都调 wx.login 获取最新 code不要把 code 存下来复用。后端在换取 openid 前加一道防重判断最轻量的做法是用一个 Set 记录已消费的 codeif (loginSession.isCodeConsumed(code)) { throw new BizException(登录凭证已过期请重新进入小程序); } loginSession.markCodeConsumed(code);如果服务是多实例部署这个 Set 要换成 Redis 才能全局生效。单机本地联调用 Set 就够了。这个坑在模拟器上极少出现因为每次调试都是新会话只有真实用户连续操作时才会踩到属于上线后才会遇到的血泪经验。6. 把课设升级成可验证系统的三个落地习惯6.1 用并发脚本验证预约不超卖修完 5.4 的原子扣减后要证明它真的有效。常见做法是用一段并发脚本打预约接口不需要压测平台Node 18 及以上自带 fetch 就能跑// stress.js const token 你的登录token; const url http://127.0.0.1:8080/api/appointment/book; async function book(i) { const res await fetch(url, { method: POST, headers: { Content-Type: application/json, X-Token: token }, body: JSON.stringify({ scheduleId: 1 }) }); const data await res.json(); console.log(第${i}次: code${data.code} msg${data.msg || ok}); } Promise.all(Array.from({ length: 50 }, (_, i) book(i)));把 scheduleId 改成某个余量为 20 的排班跑完统计 success 次数结果应严格等于 20。多于 20 说明原子扣减没生效少于 20 说明哪里误加了去重限制。这个验证不依赖任何平台本地跑一遍就能建立信心。6.2 联调时只盯三个日志输出点后端日志别等报错才看。调试预约接口我只看三个点第一个是每次请求入参在 service 入口打一行包含 patientId 和 scheduleId 的日志第二个是扣减 SQL 的执行结果把 MyBatis SQL 打印打开观察 update 影响行数是 1 还是 0第三个是微信接口的原始返回如果换 code 失败这里能看到 errcode。mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这三个点串起来基本能覆盖预约流程 90% 的故障。参数错、库存错、微信错一眼就能定位是哪一层不用从前端到后端翻几十个文件。6.3 用种子数据让项目开箱可演示课设最容易遇到的尴尬是新机器拉下来后数据库是空的小程序里看不到任何医生。解决方案是在 sql/init.sql 里不只建表还要插入演示数据两个科室、三位医生、未来七天的排班、每个排班余量 20。这样任何一个人拿到项目执行一次脚本启动后端小程序立刻有内容可看。我自己的习惯是保留一个可重复执行的 data.sql里面用 INSERT ... ON DUPLICATE KEY UPDATE删库重建时不用手工补数据数据库从此不再是不可逆的黑匣子随时有后悔药可吃。说到底这套代码能不能从“课程设计”变成拿得出手的后端项目差别不在框架用得有多新而在这些细节是否经得起并发和误操作的考验。这些年我评估过不少预约类项目永远先看三处号源扣减是不是原子 SQL、微信登录是否复用 code、前端有没有把业务规则写在页面里。三处过关项目基本就能立住三处不过关早晚要返工。希望这份拆解和踩坑记录能帮你在动手前少走一段弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表