ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot医院陪诊系统开发实战:预约、排班与并发控制

SpringBoot医院陪诊系统开发实战:预约、排班与并发控制 去年春天我陪家里老人在市三甲医院做一套全身体检从早上九点排到下午三点中间跨了五个科室推着轮椅在医院里来回跑整个人累到虚脱。回来之后我就在想像我们这样能抽出时间陪同的年轻人算幸运的那些子女在外地、长辈独自就医的情况怎么办随手搜了一下发现陪诊服务在一二线城市已经不算新鲜但大部分需求还是靠熟人介绍和小卡片对接信息不透明、没有标准化流程、出了问题也没处追溯。这就是我决定把 SpringBoot医院陪诊系统 作为毕业设计选题的直接原因——一方面确实有真实需求另一方面这个题目足以把 SpringBoot 的核心技术栈完整串起来做成一个能落地的中型 Web 系统。这个系统到底做什么简单说就是患者或家属在微信小程序里预约陪诊服务选择医院、科室、服务类型和陪诊时间段平台按规则推荐可接单的陪诊员陪诊员接单、按时到医院打卡、陪同就诊、记录流程节点服务结束后双方互评管理员在后台做全程监管。整个业务闭环涉及预约、支付、订单、排班、消息通知、评价等多个模块技术点上覆盖了 SpringBoot 自动装配、MyBatis-Plus、Redis、SpringTask、JWT 鉴权、微信小程序对接等是典型的业务有想象空间、技术有施展空间的毕设选题。如果你正打算做类似的管理系统类毕设或者想理解一个多角色平台型项目从 0 到 1 怎么落地这篇内容应该能给你一套完整的参考思路。1. 陪诊服务到底怎么拆从线下痛点抽象出线上业务模型1.1 先搞清楚陪诊场景里的三类人和三个核心动作做系统之前最怕的就是上来就建表写代码搞到最后发现业务流程都还没理顺。陪诊服务本质上是个撮合交易平台跟滴滴打车的逻辑很像乘客发布需求、司机接单、平台抽成监管只不过把场景换成了医院就医。我在设计之前把参与方拆成了三类角色每一类角色的诉求完全不同患者/家属核心诉求是找到靠谱的人帮我完成就医陪护。这里有个容易被忽略的细节——很多时候下单的并不是患者本人而是远在外地的子女。这就意味着系统在下单环节必须支持就诊人和下单人分离比如我在小程序里给老家父亲下单下单账号是我的就诊人信息填我父亲的。陪诊员核心诉求是稳定接单、公平派单、服务记录可查。陪诊员不是平台员工更像是平台上的独立服务者所以需要一套简单的入驻审核和服务评价机制。平台管理员核心诉求是看得见所有订单流转、能处理纠纷、能统计运营数据。管理端不需要太花哨订单列表、陪诊员审核、服务分类管理、数据统计这几个模块是必须的。三个核心动作则是预约下单 → 陪诊履约 → 服务评价。整个数据库设计和接口设计都围绕这条主线展开其他都是辅助。1.2 服务范围和数据字典要提前定义好医院场景有个特点——陪诊服务不是单一标准的。同样叫陪诊有的是全程陪同就诊有的只是帮忙取报告单有的需要代排队取药有的要求陪诊员陪同做无痛胃镜这类检查必须有人陪同签字。所以系统里一定要有 服务分类 这个概念我在表设计时单独建了一张 service_category 表目前定义了四种服务类型服务类型适用场景时长估算计价逻辑全程陪诊异地就医、老年人、孕妇3~6 小时按时段医院等级普通门诊陪诊熟悉流程但需要陪同挂号看诊2~3 小时按时长计费检查陪伴需家属陪同的检查胃镜等1~3 小时按次计费代跑腿业务取报告、取药、代缴费30~60 分钟按单计费这个数据字典非常关键因为订单定价、陪诊员接单偏好、后台统计都依赖它。比如有的陪诊员不愿意接代跑腿的活客单价低有的则专门接这种活因为时间灵活这些在后续做推荐和筛选时都是可用的维度。1.3 县域医院和异地就医的延伸思考毕设评审老师特别喜欢问一个问题你这个系统除了预约陪诊还有什么亮点我当时的思路是把系统定位在异地就医场景和县域医院服务真空上。一二线大三甲医院陪诊服务竞争激烈但三四线城市和县域医院基本没人做而恰恰是这些地方的老年人就医时最需要帮助——不会用自助机、不认路、听不懂医嘱。这个点一出来整个系统的社会价值就从方便提升到了填补空缺在评委那里的印象分会高不少。2. 技术选型背后的逻辑为什么是 SpringBoot而不是别的框架2.1 SpringBoot 的自动装配原理决定了它最适合这种项目很多人在毕设开题时纠结用 SSH 还是 SSM 还是 SpringBoot我的建议是不要犹豫直接 SpringBoot。原因不光是毕业设计流行用它更在于它解决了传统 SSM 项目里最繁琐的配置问题。SpringBoot 的核心机制是自动装配我在答辩时被问到过好几次SpringBoot 为什么能做到开箱即用这里把我整理过的答案分享出来SpringBootApplication 是一个组合注解其中最关键的是 EnableAutoConfiguration。这个注解会通过 AutoConfigurationImportSelector 读取 META-INF 下的 AutoConfiguration.imports 文件SpringBoot 2.7 之前是 spring.factories把里面列出的所有自动配置类加载到容器中。每个自动配置类上都有 ConditionalOnClass、ConditionalOnMissingBean 这样的条件注解比如我引入了 spring-boot-starter-web类路径下存在 DispatcherServlet 这个类SpringBoot 才会自动创建 SpringMVC 相关 Bean我引入了 druid-spring-boot-starter它检测到 Druid 数据源类存在才会自动装配数据源。这就是我只要引依赖、写配置就能跑起来的根本原因。这段原理一定要搞清楚因为它是 SpringBoot 框架介绍里必考的面试题也是毕设答辩出现频率最高的问题不光要会用,还要能说出底层逻辑。2.2 整体技术栈清单和替换方案我的项目最终用的技术栈如下这套组合在近几年的毕业设计里非常主流参考资料好找遇到问题也容易搜到解决方案后端框架SpringBoot 2.7.xJDK 1.8没有上 3.x因为 JDK 版本和部分依赖兼容性在 2.7 上最稳ORM 框架MyBatis-Plus 3.5.x核心价值是单表 CRUD 不用写 SQL分页插件直接开箱用多表关联时配合 TableName、TableField 注解和 Wrapper 构造器数据库MySQL 5.7如果需要可视化展示可以使用 Navicat生产环境可以换成 MySQL 8.x 但要注意驱动版本缓存与分布式锁Redis 5.x为什么用 Redis 不是 Caffeine因为我要用到分布式锁SETNX 命令和带过期时间的缓存这是 Caffeine 做不了的安全认证JWT Spring Interceptor比 Shiro 轻量比 Spring Security 好理解适合毕设定时任务Spring Scheduled配置一行注解就能用用来处理超时未支付订单自动关闭、每日待办消息推送前端微信小程序原生 Vue3 管理后台Element Plus小程序端给患者和陪诊员用Vue3 给管理员用接口文档Swagger 或 knife4j评审老师可以通过页面直接查看所有接口有几个替换方案我也列出来供参考如果你觉得 JWT 自己实现太麻烦可以引入 Sa-Token代码量会再省一些数据库层面如果不想自己搭环境可以使用 Docker 运行 MySQL 和 Redis如果前端不想写 Vue3管理后台直接使用若依等成熟脚手架改造也可以但需要注意二次开发的工作量并不小。2.3 小程序端为什么是非做不可的入口医院陪诊面向的人群有一个显著特征实际使用陪诊服务的人年龄普遍偏大但是付费下单的人往往是他们的子女。这意味着客户端入口必须满足两个条件一是转发方便子女给父母下单后把信息转发给陪诊员二是无需下载安装老年用户不可能为此装一个 App。微信小程序天然满足这两点。小程序端我规划了三个 Tab首页服务分类和陪诊员列表、订单页用户订单、陪诊员接单台切换、个人中心身份切换、实名认证、钱包。这里有一个设计小细节同一个小程序里患者和陪诊员共用一套登录体系通过角色字段区分。用户下单时是患者视角陪诊员进入接单台时是接单视角切换角色不退出登录体验会流畅很多。3. 数据库设计订单、排班、流程节点三张核心表怎么串起整个陪诊业务数据库设计是这类系统最见功力的部分。我曾经见过同学做的系统建了 40 多张表但大部分字段都是摆设也见过只建 5 张表把所有信息塞进去的后期扩展全是问题。陪诊系统我最终确定了 12 张表核心以三张为主订单表、陪诊员排班表、陪诊流程节点表。3.1 订单表状态字段是整个系统的灵魂订单表设计的合理与否直接决定后端代码能不能写得顺畅。我先把核心字段列出来CREATE TABLE t_order ( id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, order_no VARCHAR(32) NOT NULL COMMENT 订单编号规则YH日期随机数, user_id BIGINT(20) NOT NULL COMMENT 下单用户ID, patient_name VARCHAR(32) NOT NULL COMMENT 就诊人姓名, patient_phone VARCHAR(11) DEFAULT NULL COMMENT 就诊人电话, attendant_id BIGINT(20) DEFAULT NULL COMMENT 陪诊员ID, service_type_id BIGINT(20) NOT NULL COMMENT 服务分类ID, hospital_name VARCHAR(64) NOT NULL COMMENT 医院名称, department_name VARCHAR(64) DEFAULT NULL COMMENT 科室名称, visit_date DATE NOT NULL COMMENT 预约日期, time_slot TINYINT(1) NOT NULL COMMENT 时段1上午 2下午 3全天, illness_desc VARCHAR(500) DEFAULT NULL COMMENT 病情描述, status TINYINT(1) NOT NULL DEFAULT 0 COMMENT 状态0待支付 1待接单 2已接单 3服务中 4待确认 5已完成 6已取消 7退款中 8已退款, pay_status TINYINT(1) NOT NULL DEFAULT 0 COMMENT 支付状态0未支付 1已支付 2已退款, amount DECIMAL(10,2) NOT NULL COMMENT 订单金额, cancel_reason VARCHAR(255) DEFAULT NULL COMMENT 取消原因, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT 逻辑删除标记, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_attendant_date_slot (attendant_id, visit_date, time_slot) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT陪诊订单表;这里重点说一下状态流的设计。我见过很多同学在订单表里就一个 status 字段然后写代码时用魔法数字写死后期改状态逻辑的时候把代码翻了个底朝天。正确的做法是在后端定义一个枚举类 OrderStatusEnum把所有状态和允许的流转方向集中管理public enum OrderStatusEnum { WAIT_PAY(0, 待支付), WAIT_ACCEPT(1, 待接单), ACCEPTED(2, 已接单), SERVING(3, 服务中), WAIT_CONFIRM(4, 待确认), FINISHED(5, 已完成), CANCELLED(6, 已取消), REFUNDING(7, 退款中), REFUNDED(8, 已退款); private final Integer code; private final String desc; OrderStatusEnum(Integer code, String desc) { this.code code; this.desc desc; } // 状态流转合法性校验返回 false 表示不允许从 from 状态流转到 to 状态 public static boolean canTransition(Integer from, Integer to) { // 正常流程0-1-2-3-4-5 // 取消流程0/1 - 62/3/4 - 7 - 8 // 超时取消1 - 6 switch (from) { case 0: return to.equals(1) || to.equals(6); case 1: return to.equals(2) || to.equals(6); case 2: return to.equals(3) || to.equals(7); case 3: return to.equals(4) || to.equals(7); case 4: return to.equals(5) || to.equals(7); case 7: return to.equals(8) || to.equals(5); default: return false; } } }有了这个枚举之后所有订单状态流转的方法里第一行就写校验逻辑非法流转直接抛业务异常状态机就不会被改乱了。这个细节在答辩时讲出来评委能立刻感觉到你不是在机械地堆代码。3.2 陪诊员排班表解决谁在什么时间可接单的核心矛盾排班表是整个系统里我踩坑最多的表。最开始我的设计是只在订单里存 attendant_id但很快就发现一个问题陪诊员接单时怎么知道自己在某个时段已经接了单平台推荐陪诊员时怎么过滤掉已有安排的所以单独建了排班表记录陪诊员每天每个时段是否可接单CREATE TABLE t_attendant_schedule ( id BIGINT(20) NOT NULL AUTO_INCREMENT, attendant_id BIGINT(20) NOT NULL COMMENT 陪诊员ID, work_date DATE NOT NULL COMMENT 排班日期, time_slot TINYINT(1) NOT NULL COMMENT 时段1上午 2下午 3全天, status TINYINT(1) DEFAULT 1 COMMENT 0不可接 1可接, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_att_date_slot (attendant_id, work_date, time_slot) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT陪诊员排班表;排班表的数据由陪诊员自己维护在接单台里标记哪天忙、哪天休息也可以由管理员代为设置。下单时先查询排班表推荐时先过滤排班表接单时再次校验排班状态形成了一个三层校验的机制。3.3 流程节点表让服务过程每一步都有据可查还有一张表容易被忽略但它是让系统区别于只是一个下单工具的关键——陪诊流程节点表。陪诊员到达医院后需要在小程序上操作打卡系统会记录每个关键节点的时间到达打卡到达医院取号完成开始看诊检查完成取药完成服务结束CREATE TABLE t_visit_record ( id BIGINT(20) NOT NULL AUTO_INCREMENT, order_id BIGINT(20) NOT NULL COMMENT 订单ID, attendant_id BIGINT(20) NOT NULL COMMENT 陪诊员ID, node_type VARCHAR(32) NOT NULL COMMENT 节点类型ARRIVE / REGISTER / VISIT / CHECK / MEDICINE / END, node_desc VARCHAR(128) DEFAULT NULL COMMENT 节点描述, photo_url VARCHAR(255) DEFAULT NULL COMMENT 现场照片URL可选, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT陪诊流程节点表;这个表有两个作用。对用户来说家属远在异地通过订单详情页能看到陪诊员每一步的操作时间会安心很多对平台来说如果发生纠纷到了说没到、做了说没做这类问题通过流程记录一眼就能查清楚。运营角度这是个很强的信用背书功能同时毕设系统里也是加分项。3.4 其他辅助表的职责边界除了上面三张核心表还有一些辅助表需要注意职责划分。用户表和陪诊员表分开建不要把陪诊员信息都塞到用户表里因为陪诊员有认证状态、评分、服务次数、身份证号这些额外属性而且一个用户理论上未来可能既是患者又是陪诊员。服务分类表用一张独立的表管理不写死在枚举里方便管理员在后台随时添加新的服务类型。评价表关联订单 ID 和陪诊员 ID一个订单只允许一条评价。用逻辑删除字段统一管理防止误删数据。4. 后端核心实现的几个硬骨头并发下单、状态流转、超时关闭4.1 下单接口的并发控制怎么做到同一时段不被重复占用先说说我踩过的一个真实坑。系统开发完进入自测阶段我开了两个浏览器窗口用同一个陪诊员的账号同时下单同一个时段的订单结果两个订单都下单成功了。这对平台来说是不能接受的因为陪诊员一天上午只有半天时间不可能同时服务两位患者。问题出在查排班和更新订单之间不是原子操作。两台机器同时读到排班状态是可接然后都执行了下单逻辑最后都写入了订单表。解决办法是双保险第一层用 Redis 分布式锁下单之前先锁住陪诊员某个时段的资源第二层在数据库层面通过排班表的唯一索引和更新条件做兜底。Override Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateDTO dto) { String lockKey lock:attendant: dto.getAttendantId() : dto.getVisitDate() : dto.getTimeSlot(); boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofSeconds(10)); if (!locked) { throw new BizException(该陪诊员此时段刚被预约请重新选择); } try { // 校验陪诊员是否存在且认证通过 // 校验排班表 status 1 // 生成订单号、插入订单 // 扣减 Redis 中该时段的可用量如果做了库存设计 return order; } finally { redisTemplate.delete(lockKey); } }这里有个重要细节锁的粒度。我用的是陪诊员日期时段作为 key锁的范围刚好覆盖一次资源竞争而不是把整个下单流程都锁死这样对并发性能的影响最小。数据库层的兜底则是在排班表更新时带上条件 UPDATE ... WHERE status 1如果影响行数为 0说明这个时段已经被人抢了同样抛出冲突提示。提示分布式锁的过期时间一定要设置合理我在开发时设的是 10 秒。如果业务逻辑执行超过 10 秒会导致锁提前失效、并发请求穿透进来。极端情况可以引入 Redisson 看门狗机制但对毕设来说合理设置过期时间已经足够了。4.2 状态流转不落地时所有操作都只改变内存里的数字订单状态的服务层实现我前面提到过用枚举统一管理但还有一个隐藏的坑在高并发和异常环境下update 语句不能只用 status 字段匹配而是要用当前状态或期望状态做条件。举例来说陪诊员接单操作应该执行UPDATE t_order SET attendant_id #{attendantId}, status 2 WHERE id #{orderId} AND status 1如果 UPDATE 影响行数为 0说明这张订单的状态已经不是待接单了要么被其他陪诊员抢了要么已经取消。这种情况下如果代码不检查返回值就直接返回成功就会出现两个陪诊员同时接同一单的问题这在我测试时也真实遇到过。所有状态变更的 SQL 都必须带上当前状态条件这是状态机设计落实到数据层的第一步。我还遇到了一个延时的问题患者支付成功后订单应该从待支付变成待接单但因为支付回调处理线程和用户查询线程不是同一个事务用户刷新页面时经常看到状态没变。解决办法是在支付回调里增加一个延迟刷新比如支付完成后 500ms 再查一次最新状态或者在小程序端做轮询。这类问题不涉及复杂架构但做系统时一定要意识到 多线程 事务带来的状态可见性问题。4.3 超时未支付订单的自动关闭一个真实调度器长什么样这个功能我用 Spring Scheduled 来实现。场景是这样的用户提交了陪诊订单但一直没支付这个订单就会一直占着陪诊员那个时段的名额导致其他想下单的人流单。产品上定的规则是 15 分钟未支付自动取消并把排班释放出来。最开始我的实现很初级每 30 秒查一次所有订单遍历判断是否超时。上线后表里订单多了越来越慢。后来优化成一条 SQL 批量查询超时订单Component public class OrderTimeoutTask { Scheduled(cron 0/30 * * * * ?) public void cancelTimeoutOrders() { LocalDateTime deadline LocalDateTime.now().minusMinutes(15); ListOrder timeoutOrders orderMapper.selectList( new LambdaQueryWrapperOrder() .eq(Order::getStatus, OrderStatusEnum.WAIT_PAY.getCode()) .lt(Order::getCreateTime, deadline) .last(LIMIT 200) ); for (Order order : timeoutOrders) { // 查询排班表将对应时段的 status 改回 1 // 更新订单状态为 CANCELLED // 记录取消原因超时未支付 } } }注意这里引用了 LambdaQueryWrapper这是 MyBatis-Plus 的常用写法可以避免在代码里拼 SQL 字符串也方便后期维护。定时任务在开发环境默认是单机跑的不会有问题但如果未来扩展到多实例部署同一个任务会被多个节点重复执行此时可以用 Redis 分布式锁做一个简单的任务级互斥只允许一个节点去处理超时订单这也是面试时很容易被追问的扩展点。我在前期还被问过一个问题就是为什么不用 XXL-Job 这种分布式任务调度框架答案是毕设阶段 Spring Scheduled 够用了如果直接把问题抛给业务说清楚取舍比强行堆框架要好得多。5. 推荐和运营功能怎么用已有数据做出一个让评委眼前一亮的金牌陪诊员5.1 加权评分模型不用机器学习也能做出智能推荐毕设项目里如果直接说我加了 AI 算法会被评委提问追问到崩溃但如果完全不做智能化又显得平庸。我的做法是做一个基于多因子的加权评分推荐不涉及神经网络逻辑透明、效果直接可见。核心思路是这样的用户在下单页选择服务类型和日期时段后系统会拉出所有符合条件的陪诊员排班可用、认证通过然后按分数排序public class AttendantScoreCalculator { // 综合分 评价分权重40% 服务量权重20% 响应速度权重25% 连续服务稳定性15% public BigDecimal calculateScore(AttendantScoreDTO dto) { BigDecimal score BigDecimal.ZERO; score score.add(dto.getAvgRating().multiply(BigDecimal.valueOf(0.4))); score score.add(minMaxScale(dto.getServiceCount(), 0, 200) .multiply(BigDecimal.valueOf(0.2))); score score.add(BigDecimal.valueOf(100) .subtract(dto.getAvgAcceptSeconds()) .max(BigDecimal.ZERO) .multiply(BigDecimal.valueOf(0.25))); score score.add(dto.getServiceStability().multiply(BigDecimal.valueOf(0.15))); return score; } private BigDecimal minMaxScale(Integer value, int min, int max) { // 极差归一化将服务量映射到 0-100 区间 int bounded Math.max(min, Math.min(max, value)); return BigDecimal.valueOf((bounded - min) * 100.0 / (max - min)); } }这个算法好解释也足够让评分和接单量综合体现排名。同时前台排序默认按综合分降序换成管理员后台则可以看到不同维度排序比如只看接单量或只看评分。一套简单的打分规则项目立刻从管理系统变得有了平台运营的味道。5.2 时段热度统计与错峰建议一个很轻量的数据洞察功能另外我依托订单数据增加了时段热度统计效果远超预期。逻辑也很简单按医院科室日期维度查询订单统计每个时段的预约量低于平均预约量的时段标记为低峰时段在下单页给用户一个提示该医院周日上午预约量较少错峰就诊体验更佳。这个数据的价值在于既帮助用户选择了不那么拥挤的时间段也帮陪诊员找到空闲时段增加接单量对平台则是更均衡地利用了服务资源。数据量不大时可以直接用一条带 GROUP BY 的 SQL 完成统计不引入任何新的技术栈却能体现出你对业务数据的理解能力。5.3 评价体系里的一个细节默认好评要在什么时机触发评价模块容易被简单地做成用户打分存表但我发现评价时机其实很讲究。如果服务完成的瞬间马上提醒用户评价用户可能还没离开医院心情受排队环境影响打分普遍偏低如果拖太久用户又失去了评价动力。我的设计是订单状态变为已完成后系统先不发评价提醒等当天晚上 20:00 再通过定时任务检查所有已完成但未评价的订单向用户发送一条服务评价提醒如果 48 小时后仍未评价系统自动给一个默认好评并在评价内容中标记系统默认。这个设计在答辩时可以作为运营细节讲说明你考虑的不只是功能实现还有用户心理和评价数据质量的问题。评价数据质量直接影响前面推荐算法的准确性所以这一环是有业务闭环的意义的。6. 开发中真实踩过的坑从环境配置到并发一致性6.1 yml 明文密码和 heapdump 泄露的教训这个坑是在项目收尾阶段发现的。为了让代码打包后可以自由切换环境我把数据库密码、Redis 密码直接写在 application.yml 里后来在同学之间演示代码时发现别人能一眼看到我数据库的账密虽然只是测试环境但也觉得不妥。解决的方案是引入 jasypt-spring-boot-starter 对配置内容做加密spring: datasource: url: ENC(9x3dKf8Lq1BzTg2XsVb7abc123...) username: ENC(abc123...) password: ENC(def456...)加密的过程是在测试类里用 Jasypt 的 API 生成密文再把启动参数中的解密密钥通过环境变量注入。这样即使源码泄露、配置文件被看到、甚至有人打了 heapdump 快照从中提取配置内容也拿不到真正的明文密码。说到 heapdump 这个点是因为之前看到过 SpringBoot Actuator 暴露 heapdump 接口导致敏感信息泄露的文章所以安全类的配置值得做好别图省事。6.2 MyBatis-Plus 逻辑删除和唯一索引打架这个问题折磨了我一个晚上。我在排班表上建了 UNIQUE KEYattendant_id, work_date, time_slot同时加了 deleted 逻辑删除字段。当陪诊员修改排班、把某个时段从可接改成不可接再改回可接时逻辑上应该新增一条记录但由于该唯一键不受逻辑删除控制第一次修改没问题第二次再重建同一时段时就会触发重复键异常。最后的解决方案有两种一是把 deleted 字段纳入唯一索引但 SQL 里 NULL 值有特殊性实现要小心二是保留唯一的逻辑主键保证同一陪诊员同一日期同一时段在表里只允许有一条记录状态变化就 UPDATE 这一条而不是新增。我最终选了第二种因为实现最简单而且更符合排班表的业务语义。这里也提醒看到这篇内容的朋友设计表结构时逻辑删除和唯一约束之间是需要提前规划好的。6.3 时段和时区就是看不见的 bug 源头项目里大量使用 LocalDateTimeMySQL 的 DATETIME 类型不包含时区信息当时我的云服务器和数据库服务器默认时区不一致导致用户在小程序端看到的预约日期到了后端查询时整整差了一天。排查时先从接口返回的数据开始看发现同一个时间在三个不同角色视角下出现三种结果最后才定位到 JDBC 连接的时区参数。解决是在 JDBC URL 里显式配置url: jdbc:mysql://localhost:3306/hospital_accompany?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8同时代码里统一用 LocalDateTime所有涉及日期的参数都以yyyy-MM-dd的字符串在前端传输不在前端做时区转换。这个教训总结成经验的话就是分布式系统里时间问题不要靠自觉要明确统一标准和转换边界。6.4 接口幂等性和防重放一个容易被忽视的订单重复提交问题陪诊订单涉及金额所以接口幂等性必须考虑。用户点击立即支付时如果网络抖动导致请求重试后端如果处理不好就会生成多个订单。我为下单接口增加了一个幂等令牌的机制用户进入下单页时先向后端申请一个 requestId下单时携带这个 requestId后端在 Redis 里以 requestId 为 key 使用 SETNX 做幂等标记重复提交直接返回已经存在的订单信息。这样处理之后重复点击、网络重传、前端路由切换回来再次提交都不会产生脏数据。这个点如果写到论文里也算是一个小的技术亮点——把防重放和幂等设计落到了实际业务里。7. 部署验证与后续演进建议整个系统跑通之后我最终的部署方案是这样的小程序前端用微信开发者工具上传体验版管理后台的 Vue3 前端构建后通过 Nginx 托管后端打成一个 Jar 包通过 systemd 守护进程跑在服务器上MySQL 和 Redis 各自独立运行。后端的端口我固定用的 8080Nginx 做了反向代理和静态资源分离小程序请求走的是后端接口域名。部署完之后我给自己列了一个功能优先级表按优先级高且成本低到优先级低且成本高做了排序功能当前状态演进方向预约下单 支付已完成可对接微信支付/支付宝沙箱陪诊流程打卡已完成可增加实时位置共享和路线导航评价体系已完成可增加陪诊员申诉机制金牌陪诊推荐简化版可升级为协同过滤或向量召回管理后台基础版可增加退款审批流、财务对账消息通知站内消息可对接微信订阅消息模板如果你也是拿这个题目做毕业设计我的个人体会是不要一味追求功能多而是把少数几个核心功能做到逻辑严谨、边界清晰。状态机封装、并发控制、幂等设计、定时任务这四个技术点哪怕页面样式朴素一些也足够支撑一篇有含金量的设计论文。真正被评审认可的关键不是功能列表有多长而是你在每一个细节上有没有想清楚为什么这么设计。最后再分享一个小技巧项目里如果用了 Redis 做缓存可以在启动类或者配置类里加一个 Banner 生成器把自定义的字体 Logo 打印在日志上这个细节虽然不影响功能但能让老师在看启动日志时觉得项目完成度比较高。
返回列表