免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Spring Boot 2 + Vue 3 + MyBatis-Plus + MySQL 8.0 挂号系统实战

Spring Boot 2 + Vue 3 + MyBatis-Plus + MySQL 8.0 挂号系统实战 先交代一下背景。前阵子帮一家社区诊所搭了一套挂号就诊系统技术栈就是标题里这套Spring Boot 2 Vue 3 MyBatis-Plus MySQL 8.0。整个项目做下来从数据库设计到前后端联调再到部署上线踩了不少坑也沉淀了一些比较顺手的写法。这套组合在2024、2025年的中小型医疗信息化项目里非常典型Spring Boot 2 依然是企业级后端主力Vue 3 的 Composition API 开发效率确实比 Vue 2 高一大截MyBatis-Plus 省掉了大量单表 CRUD 模板代码MySQL 8.0 的窗口函数和 JSON 支持也让统计和扩展方便很多。如果你正准备做毕业设计、课程设计或者公司要快速搭一个轻量级的挂号平台这篇文章应该能帮你省下至少两周的摸索时间。我写这篇文章不只是罗列源码结构而是把设计和落地过程中真正重要的决策点讲清楚为什么这么选、表怎么建模、号源怎么防超卖、权限怎么控制、部署有哪些隐藏坑。内容偏实操会直接给建表 SQL、核心代码片段和排查思路跟着走基本能复现一套能跑起来的系统。1. 系统整体设计与技术选型思路1.1 为什么是 Spring Boot 2 而不是 Spring Boot 3选 Spring Boot 2.7.x 而不是 3.x核心原因就三个JDK 版本兼容、第三方生态成熟度、学习资料覆盖率。Spring Boot 3 强制要求 JDK 17这本身没问题但很多医院、诊所现有的运维环境还停留在 JDK 8。一套系统要落地往往不只是代码能跑还要考虑客户服务器上的 Java 环境能不能动。Spring Boot 2.7 是 2.x 的最后一个版本支持 JDK 8也支持 JDK 11兼容性最好。另外Spring Boot 3 基于 Jakarta EE 9很多老项目的依赖坐标要从javax改成jakarta如果团队之前积累了不少基于javax.servlet的代码迁移成本不低。在第三方组件方面Spring Boot 2.7 对 MyBatis-Plus、Druid、ShardingSphere、Knife4j 这些国内开发高频使用的库支持得最稳。尤其 MyBatis-Plus 的mybatis-plus-boot-starter在 3.5.x 版本对 Boot 2 的分页插件、自动填充、乐观锁支持都非常丝滑放到 Boot 3 上偶尔会遇到拦截器不生效或者ClassNotFoundException的问题。还有一点是学习门槛。目前大部分高校和培训机构讲 Spring Boot 还是以 2.x 为主碰到报错网上一搜全是现成答案。用 Spring Boot 3 写项目遇到冷门问题可能翻半天 GitHub Issue 才能解决。对于业务系统来说稳定大于激进Boot 2.7 是当下最务实的选择。1.2 Vue 3 组合式 API 与 Vue 2 选项式的分水岭前端这块直接上了 Vue 3 Vite Element Plus。Vue 3 最大的变化是 Composition API它把同一个业务逻辑的响应式数据、计算属性、监听器、方法聚合在一起而不是像 Vue 2 的 Options API 那样强制分到data、methods、computed不同的区块里。举个实际感受。挂号页面里有一个「选择科室 - 选择医生 - 选择号源时段」的联动逻辑。Vue 2 的写法是watch科室 ID然后去改医生列表数据Vue 3 里直接写一个computed根据当前selectedDeptId过滤出医生列表数据流更直观改动时也不用担心watch顺序错乱的问题。script setup import { ref, computed, watch } from vue const selectedDeptId ref() const doctorList ref([]) const availableDoctors computed(() doctorList.value.filter(d d.deptId selectedDeptId.value) ) watch(selectedDeptId, async (newVal) { if (!newVal) return isLoading.value true try { const { data } await getScheduleByDept(newVal) doctorList.value data } finally { isLoading.value false } }) /scriptVite 替代 Webpack 之后冷启动速度非常明显开发时几乎秒开HMR 也是毫秒级更新。Element Plus 是 Element UI 的 Vue 3 版本表格、表单、弹窗、日期选择器这些后台管理高频组件都有和 Vue 3 的响应式系统配合得也很好。如果你现在还在纠结要不要从 Vue 2 迁到 Vue 3我的建议是直接迁。Vue 2 已经停止维护新项目再用它就是在给未来挖坑。1.3 MyBatis-Plus 和 MySQL 8.0 的实际价值MyBatis-Plus 不是替代 MyBatis而是 MyBatis 的增强工具。它最核心的价值是把单表 CRUD 的模板代码彻底消灭。BaseMapper接口里已经内置了selectById、selectList、insert、updateById、deleteById这些方法你只要定义一个继承它的接口什么都不用写就能用。public interface PatientMapper extends BaseMapperPatient { }配合Wrapper条件构造器复杂的动态查询也能用代码表达不用写 XMLLambdaQueryWrapperPatient wrapper new LambdaQueryWrapper(); wrapper.like(StrUtil.isNotBlank(keyword), Patient::getName, keyword) .eq(Patient::getDeleted, 0) .orderByDesc(Patient::getCreateTime); IPagePatient page patientMapper.selectPage(new Page(current, size), wrapper);MySQL 8.0 相比 5.7 有几个明显的优势。默认字符集是utf8mb4存 emoji 和生僻字不会再报Incorrect string value错误支持窗口函数做挂号统计、排名这类查询时不用写复杂的子查询支持WITH公共表表达式分页和复杂统计的 SQL 更清晰。8.0 还引入了caching_sha2_password认证插件如果项目中用的是老版本驱动比如 5.1.x 的 MySQL Connector/J连接会报Public Key Retrieval is not allowed这个问题在部署章节会专门说。2. 核心数据模型与数据库设计2.1 用户体系与基础档案的三表联动医院挂号系统的数据模型核心有两块基础档案和业务流水。基础档案包括用户、患者、科室、医生、排班业务流水包括挂号单和就诊记录。先看最基础的三张表系统用户表、患者档案表、医生信息表。这三张表不能混否则后面扩展会很难受。系统用户表sys_user存的是登录账号和密码这是登录认证的主体CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(50) NOT NULL COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, real_name VARCHAR(50) DEFAULT NULL COMMENT 真实姓名, role TINYINT NOT NULL DEFAULT 0 COMMENT 角色: 0-管理员 1-医生 2-患者, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态: 0-禁用 1-正常, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除标记, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表;患者档案表patient_info则专门存诊疗过程中需要的医疗信息比如身份证号、过敏史、医保卡号。医生信息表doctor_info关联所属科室、职称、擅长领域后续在排班和挂号列表里展示。三张表通过sys_user.id做外键关联。之所以分开是因为一个系统用户不一定有患者档案比如管理员一个患者档案也可能在极端情况下被多个家庭成员账号关联比如老人不会用手机子女帮忙挂号。这种设计在业务扩展时更从容。2.2 科室、医生与排班号源从哪里来科室表department和医生表doctor_info是挂号的前置依赖。科室表字段比较常规科室名称、科室位置、科室简介、是否开放挂号。医生表要额外加一个dept_id和title用于前端做科室联动筛选。排班表schedule是号源生成的源头设计时要考虑两个维度时间和号源数量。CREATE TABLE schedule ( id BIGINT NOT NULL AUTO_INCREMENT, doctor_id BIGINT NOT NULL COMMENT 医生ID, dept_id BIGINT NOT NULL COMMENT 科室ID, work_date DATE NOT NULL COMMENT 出诊日期, period_type TINYINT NOT NULL COMMENT 时段: 1-上午 2-下午, start_time TIME NOT NULL COMMENT 开始时间, end_time TIME NOT NULL COMMENT 结束时间, total_num INT NOT NULL DEFAULT 0 COMMENT 总号源数, remain_num INT NOT NULL DEFAULT 0 COMMENT 剩余号源数, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态: 0-停诊 1-放号 2-约满, PRIMARY KEY (id), KEY idx_doctor_date (doctor_id, work_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT医生排班表;关键设计点在remain_num。每次用户挂号成功时除了插入一条挂号单记录还要同步扣减remain_num。这里必须确保扣减操作的原子性防止两个人同时挂最后一个号导致超卖。后面写实现时我会给出具体的 SQL。2.3 挂号单的状态机与超时释放机制挂号单表registration是系统的核心业务表记录每一笔挂号操作。CREATE TABLE registration ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 挂号单号, patient_id BIGINT NOT NULL COMMENT 患者档案ID, user_id BIGINT NOT NULL COMMENT 操作账号ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, dept_id BIGINT NOT NULL COMMENT 科室ID, schedule_id BIGINT NOT NULL COMMENT 排班ID, reg_date DATE NOT NULL COMMENT 就诊日期, period_type TINYINT NOT NULL COMMENT 时段, order_status TINYINT NOT NULL DEFAULT 0 COMMENT 状态: 0-待就诊 1-已就诊 2-已取消 3-已退号, visit_no INT DEFAULT NULL COMMENT 就诊序号, remark VARCHAR(255) DEFAULT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_patient_status (patient_id, order_status), KEY idx_schedule (schedule_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT挂号单表;状态流转看起来简单但实际落地要处理三个边界场景取消挂号的余号回补用户取消挂号时order_status变成 2同时排班表的remain_num要 1。这个操作必须放在同一个数据库事务里。过期未支付释放如果系统支持在线支付但用户未支付需要定时任务把超过 15 分钟的待支付订单自动取消释放号源。停诊处理医生临时停诊时需要把该排班和所有待就诊状态的挂号单统一处理通常是电话通知后手动退号。这个状态机的设计直接决定业务是否闭环一定要在开发前理清楚不要边写边改。3. 后端核心模块实现3.1 通用 CRUD 服务MyBatis-Plus 的最佳实践MyBatis-Plus 的IService和ServiceImpl提供了一套通用 CRUD 模板。比如患者管理的 Service 接口可以这样写public interface PatientService extends IServicePatient { PageResultPatient pageQuery(PatientQuery query); boolean addPatient(Patient patient); boolean updatePatient(Patient patient); }ServiceImpl 里直接继承ServiceImplPatientMapper, Patient基础的save、removeById、page方法都自动具备了。真正需要手写的只有业务逻辑例如新增患者前检查身份证号是否重复Override public boolean addPatient(Patient patient) { LambdaQueryWrapperPatient wrapper new LambdaQueryWrapper(); wrapper.eq(Patient::getIdCard, patient.getIdCard()); Long count baseMapper.selectCount(wrapper); if (count 0) { throw new BusinessException(身份证号已存在); } return save(patient); }这里有一个实际经验要分享不要把所有查询都交给 MP。复杂报表统计、多表关联查询还是建议写 XML。比如「按科室统计每日挂号量」这种需求用 MP 的 Wrapper 会写得很别扭但用 MySQL 8.0 的窗口函数直接一条 SQL 解决SELECT d.dept_name, DATE(r.reg_date) AS reg_day, COUNT(*) AS reg_count, SUM(COUNT(*)) OVER (PARTITION BY d.dept_name ORDER BY DATE(r.reg_date)) AS cumulative_count FROM registration r JOIN department d ON r.dept_id d.id WHERE r.order_status IN (0, 1) GROUP BY d.dept_name, DATE(r.reg_date) ORDER BY d.dept_name, reg_day;所以我在项目里定了两条规矩单表操作无脑用 MP多表统计一律走自定义 SQL。3.2 JWT Spring Security 认证授权落地Spring Security 在 Java Web 项目里几乎是标配但它配置繁琐也是出了名的。这个项目采用 JWT Spring Security 的方案核心是让无状态接口可以识别你是谁。流程是用户登录成功后后端生成一个带过期时间的 JWT Token 返回给前端。前端把 Token 存在localStorage或 Pinia 里每次请求在Authorization头带上。后端用 OncePerRequestFilter 拦截请求解析 Token把用户信息放进SecurityContext。配置 Spring Security 时最核心的代码是SecurityFilterChainBean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/login, /api/auth/register, /doc.html).permitAll() .requestMatchers(/api/admin/**).hasRole(ADMIN) .requestMatchers(/api/doctor/**).hasRole(DOCTOR) .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }关于角色权限这里踩过的一个坑是 Spring Security 的hasRole会自动加上ROLE_前缀。如果数据库角色字段存的是ADMIN那hasRole(ADMIN)会检查ROLE_ADMIN如果存的是自定义格式就要用hasAuthority。这个细节看似小但报错时很难排查。密码加密统一用 BCryptPasswordEncoder不要用 MD5。MD5 是哈希算法不是加密算法而且彩虹表查起来太容易了。BCrypt 每次加密的结果都不同即使两个用户密码相同密文也不同安全性靠谱很多。3.3 挂号核心流程事务 乐观锁防超卖挂号是这个系统里并发要求最高的操作。两个用户同时点击余号为 1 的号源只能有一个人成功。实现方案有两种常见做法悲观锁SELECT FOR UPDATE 锁定排班记录事务结束后释放。适合并发量极高的场景但会锁行影响性能。乐观锁在排班表加version字段更新时检查版本号。适合中低并发场景不锁行。医院挂号系统的并发量通常每秒几十到几百乐观锁完全够用。更新语句是核心Transactional(rollbackFor Exception.class) public RegistrationResult register(RegisterRequest request) { Schedule schedule scheduleMapper.selectById(request.getScheduleId()); if (schedule.getRemainNum() 0) { throw new BusinessException(号源已挂完); } if (schedule.getStatus() ! 1) { throw new BusinessException(该时段已停诊或约满); } int rows scheduleMapper.updateRemainNumWithVersion( schedule.getId(), schedule.getVersion() ); if (rows 0) { throw new BusinessException(号源被抢完了请刷新重试); } Registration registration buildRegistration(request, schedule); registrationMapper.insert(registration); return RegistrationResult.from(registration); }对应的 Mapper XML 里的更新 SQL 是update idupdateRemainNumWithVersion UPDATE schedule SET remain_num remain_num - 1, version version 1 WHERE id #{id} AND remain_num 0 AND version #{version} /update这个 WHERE 条件里同时带了remain_num 0和version #{version}等于双保险即使两个请求同时提交数据库行锁会让其中一个更新失败rows 0就能感知到并返回友好提示。事务边界也要注意Transactional注解必须加到 public 方法上而且要通过外部调用进来否则 Spring AOP 代理不生效。同一个类里方法之间调用this.register()会让事务失效这是新人最容易犯的错。3.4 定时任务与统计报表挂号系统离不开定时任务取消超时未支付的订单、每天早上生成号源、定时统计就诊数据。Spring 自带的Scheduled足够应付不需要引入 Quartz。自动取消超时待支付订单Scheduled(fixedDelay 60_000) public void cancelTimeoutOrders() { LocalDateTime deadline LocalDateTime.now().minusMinutes(15); LambdaUpdateWrapperRegistration wrapper new LambdaUpdateWrapper(); wrapper.eq(Registration::getOrderStatus, 4) // 待支付 .lt(Registration::getCreateTime, deadline) .set(Registration::getOrderStatus, 2); // 改为已取消 ListRegistration timeoutOrders registrationMapper.selectList(wrapper); if (!timeoutOrders.isEmpty()) { timeoutOrders.forEach(order - { scheduleMapper.increaseRemainNum(order.getScheduleId()); }); } }注意这里要先把超时订单查出来再循环回补号源。如果直接批量更新状态而不回补号源号源就会越来越少。还可以扩展一个每日定时任务每天晚上 8 点生成第二天的号源把排班表的remain_num重置为total_num。或者用 ES 做搜索引擎但这套系统用 MySQL 就够了。4. 前端 Vue3 Element Plus 实操要点4.1 工程化初始化与 Axios 请求封装前端项目用 Vite 初始化一行命令搞定npm create vitelatest frontend -- --template vue然后安装依赖npm install element-plus axios pinia vue-routerVite 的vite.config.js里配置开发服务器代理解决跨域问题这里要注意代理不只是为了解决跨域还是为了让前端请求路径和后端接口路径解耦export default defineConfig({ plugins: [vue()], server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })Axios 封装时最重要的统一响应拦截器。后端约定返回格式为{ code, message, data }前端统一处理code 200和code ! 200的情况避免每个页面都重复做错误提示。下面这个封装是个人项目中比较顺手的版本import axios from axios import { ElMessage } from element-plus const service axios.create({ baseURL: /api, timeout: 15000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { ElMessage.error(error.response?.data?.message || 网络异常) return Promise.reject(error) } ) export default service4.2 挂号页面的联动交互与表单校验挂号是患者端最高频的页面交互必须顺滑。页面上有三个联动选择科室 - 医生 - 号源时段。数据结构上我建议在 Pinia 里维护一个registrationStore因为用户可能在挂号页和确认页之间来回跳临时选择的数据需要跨组件共享。// store/registration.js import { defineStore } from pinia export const useRegistrationStore defineStore(registration, { state: () ({ selectedDept: null, selectedDoctor: null, selectedSchedule: null, patient: null }), actions: { setDept(dept) { this.selectedDept dept this.selectedDoctor null this.selectedSchedule null } } })选中科室后自动清空医生和号源选择这个逻辑放在 action 里而不是组件里可以保证不管在哪个页面触发切换状态都保持一致。表单校验方面Element Plus 的el-form用rules配置校验规则。这里要注意自定义校验函数比如校验身份证号是否合法不光要正则匹配还要校验最后一位校验码避免 18 位数字乱填也能通过。还有一个细节挂号时要让用户选择就诊人。如果当前登录用户没有维护患者档案要弹出一个动态表单让他先创建档案。动态表单的「添加一行」功能可以这样实现template div v-for(item, index) in formList :keyindex el-input v-modelitem.name placeholder就诊人姓名 / el-input v-modelitem.idCard placeholder身份证号 / el-button clickaddRow添加/el-button el-button v-ifformList.length 1 clickremoveRow(index)删除/el-button /div /template script setup import { reactive } from vue const formList reactive([{ name: , idCard: }]) function addRow() { formList.push({ name: , idCard: }) } function removeRow(index) { formList.splice(index, 1) } /script4.3 路由守卫与按钮级权限控制前端的权限控制不是安全边界但能极大提升用户体验。方案是根据用户角色动态生成路由。具体思路是登录后拿到用户角色用router.addRoute动态注册该角色能访问的路由表。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token) { if (to.path /login) { next() } else { next(/login) } return } if (to.path /login) { next(/) return } next() })按钮级权限的核心是自定义指令。比如删除用户的按钮只有管理员才能看到app.directive(permission, { mounted(el, binding) { const required binding.value const role localStorage.getItem(role) if (required ! role) { el.parentNode?.removeChild(el) } } })这个写法如果要更优雅可以把角色信息放到 Pinia 持久化插件里刷新页面也不会丢。4.4 前后端接口规范与联调经验前后端联调最怕的不是 bug而是接口文档不一致。这个项目里强制约定了一套统一响应格式{ code: 200, message: 操作成功, data: { list: [], total: 0 } }分页接口固定用page和size参数返回固定的list和total字段。这样前端分页组件完全不用改一套代码通用所有列表页。接口路径也统一规则/api/admin/、/api/doctor/、/api/patient/ 开头一眼能看出模块和权限。后端用 Knife4j 生成接口文档地址固定/doc.html开发环境和测试环境都开着前端随时可以对着文档调试不用每次跑来问字段含义。5. MySQL 8.0 环境搭建与部署踩坑记录5.1 MySQL 8.0 安装与驱动兼容问题MySQL 8.0 的安装本身不复杂但有几个坑新手经常踩。Windows 上安装时要注意选择Server Only还是Full开发机建议选 Full但生产环境一定选 Server Only。安装到最后一步会要求设置 root 密码和认证方式这时候如果选了Use Legacy Authentication后续驱动连接会更顺滑选了默认的caching_sha2_password则必须确保驱动版本是 8.0 以上。Docker 部署 MySQL 8.0 是最推荐的方式一条命令搞定docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDyourpassword \ -e MYSQL_DATABASEhospital \ -v /data/mysql:/var/lib/mysql \ mysql:8.0用 Docker 容器的好处是隔离环境干净卸载也不留垃圾。但记得把数据目录挂载出来否则容器删除数据就没了。JDBC 连接串要注意加时区参数不加的话可能会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这种乱码错误spring.datasource.urljdbc:mysql://localhost:3306/hospital?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue5.2 典型报错排查与解决方案整理几个这个项目里实际碰到的高频报错希望能帮你少走弯路。报错一Public Key Retrieval is not allowed这个错误在使用 MySQL 8.0 老版本 JDBC 驱动时非常典型。原因是在caching_sha2_password认证模式下客户端首次连接需要向服务器请求公钥但连接串默认不允许这样做。解决方式是在连接串里加allowPublicKeyRetrievaltrue。但更推荐的方案是升级驱动到mysql-connector-java8.0.33 或使用新坐标com.mysql:mysql-connector-j这样连接串配置更标准。报错二MyBatis-Plus 分页不生效分页插件 3.5.x 版本的配置方式如下Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(50L); interceptor.addInnerInterceptor(pagination); return interceptor; }如果这个 Bean 没配置selectPage会把所有数据查出来内存分页数据量一上来直接内存溢出。报错三Vue 项目引入 Element Plus 样式不生效Element Plus 按需引入时如果用的是unplugin-auto-import和unplugin-vue-components需要在vite.config.js里配置import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ] })不配置的话组件虽然渲染了但样式全没了查半天都找不到原因。报错四Invalid bound statement (not found)Mapper 接口定义了方法但找不到对应的 SQL通常是两种原因XML 文件没放在resources/mapper目录下或者application.yml里没配置 mapper 扫描路径。mybatis-plus.mapper-locationsclasspath*:mapper/**/*.xml5.3 文档与部署系统性交付的关键打包部署这块容易被忽略但却是项目能不能真正交付的关键。后端打包用的是 Maven直接mvn clean package -DskipTests生成 jar 包生产环境用nohup java -jar hospital-server.jar启动。如果需要修改端口启动命令带参数nohup java -jar hospital-server.jar --server.port8080 \ --spring.profiles.activeprod \ --spring.datasource.urljdbc:mysql://... \ app.log 21 前端打包是npm run build产物在dist目录。最方便的做法是在 Nginx 里配置静态资源服务并把/api反向代理到后端server { listen 80; server_name hospital.example.com; location / { root /www/hospital-frontend; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里配置try_files $uri $uri/ /index.html是必要的Vue Router 用 history 模式时刷新任意子路由页面都得能回到 index.html否则会 404。项目文档我习惯用 Markdown 写到docs/目录包含环境准备文档、数据库初始化 SQL 脚本、接口文档入口、部署手册。这样任何一个新人拿到项目都能在半天内跑起来。6. 可扩展的方向与我的实战心得这套挂号就诊系统做完之后我发现它的扩展能力强得超出预期。它不是一个静态的毕业设计而是一个可以不断往里面加模块的基础框架。如果要在真实医院环境中落地至少要补三块内容在线支付挂号单表增加pay_status和pay_time字段对接微信支付或支付宝。这里要注意支付回调必须是后端主动查询核对不能完全依赖前端跳转结果否则金额对不上很麻烦。消息通知挂号成功、停诊提醒、就诊前一天提醒可以通过短信或微信模板消息推送。定时任务每天扫一遍第二天的挂号单筛选出待就诊状态的记录批量发通知。电子病历与报告查询在就诊环节增加病历录入、检验报告上传患者端可以查看历史病历。这块是医院信息化真正的刚需虽然是很大的工程但表设计上完全能兼容。最后说几句真心话。我在这套系统上踩过最大的坑不是某个具体技术细节而是东拼西凑的代码堆在一起没人能维护。技术栈老练与否真的不是第一位的第一位是思路清晰、模块边界合理、数据建模严谨。Spring Boot 2 Vue 3 MyBatis-Plus MySQL 8.0 这套组合胜在每一层都有成熟的生态支撑遇到问题能找到现成解决方案这才是做业务系统最看重的。如果你是自己学习建议不要只看源码就完事自己动手把挂号流程、号源扣减、权限控制这几个核心场景从零写一遍。写不出来的地方再回头看项目代码收获会大得多。项目里附带的那份文档也值得认真读一遍很多设计和取舍在文档里讲得比代码清楚。
返回列表