☰
社区医疗服务系统全栈实战:SpringBoot+Vue+MyBatis+MySQL开发指南
2026/10/14 4:17:24 网站建设 项目流程

做社区医疗服务系统这类全栈项目,我在实际带过不少人上手后有一个很深的感受:它不像电商、外卖那么“卷”,但因为业务链路足够完整——从居民建档、预约挂号、医生接诊、开药处方到药房库存,每一步都有清晰的输入输出关系,所以非常适合用来练手,也特别适合那些需要交一个“拿得出手的全栈管理系统”的人。今天就从项目拆解、技术选型、数据库设计、前后端实现到部署上线,把这个基于SpringBoot+Vue+MyBatis+MySQL的社区医疗服务系统完整捋一遍,并把我实际踩过的坑一并写清楚。

1. 项目画像:社区医疗服务系统到底在解决什么

1.1 基层医疗场景里的真实业务痛点

社区医院、街道卫生服务中心这类基层机构,面对的人群和大型三甲医院不太一样:以老年患者、慢性病患者、周边常住居民为主。他们来看病,很多不是疑难杂症,而是高血压开药、糖尿病复诊、感冒发烧、体检报告解读这类日常需求。但就是这些看似简单的需求,在没有信息系统的情况下往往乱成一团。

我见过一家社区服务站,患者档案还停留在纸质登记本上,医生开药靠人工去药房问“这个药有没有货”,收费靠手工计算,复诊时根本找不全患者以前的病历。这种模式下,患者每次来都要重新描述病史,医生开药前要花时间确认库存,药房和收费处信息不同步,高峰期排队能绕一圈。

管理系统要解决的,就是这条链路上的几个核心问题:档案数据化、号源可预约、病历电子化、药品库存可视。用一套系统把“患者-医生-药房-收费”串起来,数据的产生、流转、沉淀都在系统内完成。

1.2 三类用户角色与核心业务闭环

从使用角色来看,社区医疗服务系统通常划分成三类:

  • 管理员:负责系统配置、人员账号管理、药品目录维护、公告发布、数据统计看板,是系统的“总控台”。
  • 医生:处理日常接诊,包括查看预约列表、调取患者健康档案、书写门诊病历、开具处方、给出治疗建议。
  • 患者/居民:注册登录后可以维护个人档案,查看可预约的医生和排班,在线挂号,查看自己的历史病历和处方记录。

这三个角色不是孤立的,它们刚好组成一个完整的业务闭环。简单描述就是:管理员先录入医生信息和药品目录,患者在线上或者前台完成建档和挂号,医生在接诊时间看到待就诊列表,调取档案并写病历、开处方,处方通过后药房减库存,收费记录同步生成,患者再通过系统查看处方和缴费信息。系统的价值就在这里——每个角色上一步的输出,就是下一步的输入,数据全程可追踪。

1.3 这套代码适合谁用

目标用户大概分三类:

  • 正在准备毕业设计的学生:前后端分离、代码分层清晰、业务机制完整,拿来二次开发或者局部重做一些模块都很方便,答辩时也好讲清楚。
  • Java后端转行/初学者:这类系统不涉及特别复杂的中间件,却把SpringBoot、MyBatis、MySQL、Vue这四样东西全部串了起来,能真正理解“一个请求从前端页面到后端数据库再回显”的全链路。
  • 小型医疗机构的IT负责人或外包开发者:代码逻辑务实,表结构不算臃肿,可以在此基础上按机构实际流程改造。

标题里的“2025最新”,通常指技术栈尽量贴近当前主流:后端SpringBoot 2.7/3.x,前端Vue3 + Vite + Element Plus,数据库MySQL 5.7/8.0。用这套组合做出来,既不至于太老旧,也不会为了追新而上一些没必要的东西。

2. 技术选型复盘:为什么这套组合能长期霸榜

2.1 后端选SpringBoot:真正的“零XML”

早期做Java Web项目,SSH或者SSM那一套,光是springmvc.xml、mybatis.xml、web.xml几个配置文件就能耗掉两三天。SpringBoot把绝大多数配置变成了“约定优于配置”:内嵌Tomcat,直接java -jar启动,数据源、MyBatis、事务这些通过starter依赖即可自动配置。对单体和中小型项目来说,SpringBoot就是当下最务实的后端框架。

有人会问,SpringCloud微服务不是更有“高级感”吗?我的看法是:社区医疗服务系统属于典型的单体业务系统,并发量和对扩展性的要求远没到微服务的地步。硬拆微服务反而会引入服务注册、网关、分布式事务这些复杂度,对学习和交付都是负担。单体应用不代表“老土”,把一个单体工程做到分层清晰、模块解耦、容易维护,本身就是很好的工程能力。

2.2 持久层选MyBatis:手写SQL的灵活性是参数化报表的底气

MyBatis和JPA(Spring Data JPA)是我经常被问到的一个选择点。我的实际经验是:CRUD能简单,但业务一旦涉及统计报表、条件查询、多表关联,JPA的自动SQL就很容易变得很难控制。例如“统计近12个月内每个科室的预约人数”这种简单报表,MyBatis里写一段联表汇总SQL很直接,JPA反而要拼接Specification或者写JPQL,心里不是很有底。

MyBatis的半自动特性允许精确控制每一条SQL,同时通过动态SQL解决多条件筛选问题。配合通用Mapper或MyBatis-Plus使用,单表CRUD也能简化到几乎不用写XML。这套项目里我用的是原生MyBatis加Mapper接口方式,功能完整、依赖轻,而且网上学习资料极多,遇到问题基本搜得到答案。

2.3 前端选Vue:中后台管理系统的最佳适配方案

社区医疗服务系统本质上是中后台管理系统,特点是页面多、表格多、弹窗表单多、状态管理需求明确。Vue生态在这一点上成熟得吓人:Vue Router管路由,Pinia/Vuex管全局状态,Element Plus直接提供表格、分页、日期选择器、树形控件、弹窗组件,组合起来能快速完成80%后台界面工作。

Vue3 + Vite的方案在2025年已经是绝对主流。Vite按需编译,本地启动速度比Webpack快一个数量级,Element Plus对Vue3也做了全面适配,组合下来体验很顺滑。当然,如果拿到的是基于Vue2的老项目,也不需要推翻重写,Vue2 + Element UI依然可以用,只是迁移或者二次维护时就要考虑逐步升级的代价了。

2.4 数据库选MySQL:够用、稳定、熟悉

MySQL对于这种项目是“零沟通成本”的选择。它支持标准SQL、事务(InnoDB)、主外键/索引,部署也简单,开发环境和生产环境能用同一套行为逻辑。社区医疗系统一天几千条挂号、病历数据量,对MySQL来说压力很小,根本不需要上分布式数据库。如果以后数据量增长,单库单表加索引也够顶相当长时间。

把整套技术栈用一张表格对比一下,选型逻辑会更直观:

对比维度本方案(SpringBoot+Vue+MyBatis+MySQL)全JSP单体方案SpringCloud微服务方案
上手难度中等,前后端分离但边界清晰低,但代码前后端混杂高,大量分布式概念
团队协作前后端可并行开发页面和逻辑绑定,分工混乱运维、基础设施要求高
维护扩展模块替换方便,适合二次开发改动成本高,页面难复用功能强但复杂度超卖
交付效率高,组件化开发中,改样式容易影响逻辑低,不适合小团队

3. 数据库设计与核心表结构

3.1 表设计思路:先梳理实体关系

数据库设计是该项目的“灵魂”,表结构设计得好,后面写代码就是流水线工作;设计得不好,业务写到一半必然返工。我习惯的流程是先画实体和关系,再转成表结构。

系统核心实体包括:用户(User)、角色(Role)、患者档案(HealthRecord)、医生信息(Doctor)、预约挂号(Appointment)、病历(MedicalRecord)、处方(Prescription)、药品(Drug)、药品库存流水(DrugStockLog)、收费单(Bill)、公告(Notice)。

实体间的关系上有几个关键设计点值得展开:

  • 患者和档案:一个用户登录账号对应一份患者档案,用唯一外键或unique约束绑定,保证档案不重复。
  • 医生和管理员:都属于用户,通过角色区分,医生的排班信息可以用单独字段或者排班表维护,简单场景下直接在预约模块按日期判断。
  • 病历和处方:一份病历可以包含多个处方条目(一条病历对应多条药品明细),处方明细里冗余药品名称和单价,避免每次展示都去关联药品表。
  • 预约、病历、收费:挂号单生成后可被病历引用,病历生成后可被收费单引用,状态流转要一致。

3.2 核心表结构落地

下面给出几张关键表的简化结构,实际建表时需要加必要索引和注释。

患者档案表(health_record):

CREATE TABLE `health_record` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` bigint NOT NULL COMMENT '关联用户ID', `real_name` varchar(50) NOT NULL COMMENT '真实姓名', `gender` tinyint DEFAULT NULL COMMENT '性别 1男 2女', `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号', `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `birthday` date DEFAULT NULL COMMENT '出生日期', `blood_type` varchar(10) DEFAULT NULL COMMENT '血型', `allergy_history` varchar(255) DEFAULT NULL COMMENT '过敏史', `chronic_disease` varchar(255) DEFAULT NULL COMMENT '慢性病史', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_id` (`user_id`) ) ENGINE=InnoDB COMMENT='患者健康档案表';

预约挂号表(appointment):

CREATE TABLE `appointment` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `patient_id` bigint NOT NULL COMMENT '患者档案ID', `doctor_id` bigint NOT NULL COMMENT '医生ID', `appointment_date` date NOT NULL COMMENT '预约日期', `time_slot` varchar(20) NOT NULL COMMENT '时段 如08:30-09:00', `status` tinyint NOT NULL DEFAULT '0' COMMENT '预约状态 0待就诊 1已完成 2已取消 3超时', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_doctor_date` (`doctor_id`,`appointment_date`), KEY `idx_patient` (`patient_id`) ) ENGINE=InnoDB COMMENT='预约挂号表';

病历表(medical_record):

CREATE TABLE `medical_record` ( `id` bigint NOT NULL AUTO_INCREMENT, `appointment_id` bigint NOT NULL COMMENT '关联挂号单', `patient_id` bigint NOT NULL, `doctor_id` bigint NOT NULL, `chief_complaint` varchar(500) DEFAULT NULL COMMENT '主诉', `present_illness` varchar(1000) DEFAULT NULL COMMENT '现病史', `diagnosis` varchar(500) DEFAULT NULL COMMENT '诊断结果', `treatment_advice` varchar(500) DEFAULT NULL COMMENT '治疗建议', `fee` decimal(10,2) DEFAULT '0.00' COMMENT '就诊费用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_appointment` (`appointment_id`), KEY `idx_patient` (`patient_id`) ) ENGINE=InnoDB COMMENT='门诊病历表';

药品表(drug)和处方明细表(prescription)就不全列了,但有一个字段一定要预留:stock库存字段,以及version版本号或者类似字段,后续实现防超卖需要用到。

3.3 账号权限体系:用简单的RBAC搞定三类角色

权限设计没有用复杂的Shiro或Spring Security,而是采用了简单的RBAC模型:用户表、角色表、用户角色关联表。登录后JWT里直接携带userId和role编码,后端拦截器校验接口权限,前端根据角色渲染菜单。

三张角色对应菜单权限可以做成这样:

  • admin:用户管理、角色管理、药品目录、公告管理、全部数据统计。
  • doctor:今日预约、病历管理(接诊)、开处方、个人排班查看。
  • patient:健康档案维护、预约挂号、我的病历、我的处方、缴费记录。

这套设计把“用户身份”和“可操作性权限”绑定在一起,在中小型系统里完全够用。如果后续要更细粒度(比如“某医生只能看自己的患者”),在业务代码里加用户ID的拦截判断即可,不必为了权限升级而引入重框架。

4. 后端实现核心细节

4.1 后端包结构:让代码在包里一目了然

后端工程包结构我采用最经典的分层方式,直接把项目根包设计为com.community.medical:

com.community.medical ├── config // 全局配置:拦截器、跨域、Swagger ├── controller // 控制层 │ ├── AuthController │ ├── HealthRecordController │ ├── AppointmentController │ ├── MedicalRecordController │ └── DrugController ├── service // 业务接口 │ └── impl // 业务实现 ├── mapper // MyBatis Mapper接口 ├── entity // 数据库实体类 ├── dto // 传输对象:接收前端参数,返回VO ├── common // 通用返回结果、枚举、异常处理 └── utils // JWT、密码加密工具

很多新手容易犯的错误是把业务逻辑全塞进Controller。我看到的干净代码,Controller只负责接收参数和返回结果,Service层承载业务规则,Mapper只做数据访问。事务注解加在Service实现类的方法上,多表操作才能保证原子性。

4.2 登录认证与JWT拦截器:不引入Security也能做得规范

社区医疗服务系统对安全的要求不能马虎,但也没复杂到需要上Spring Security全家桶。我用的方案是:密码BCrypt加密存储,登录成功后用JWT生成token,后端写一个HandlerInterceptor统一校验。

登录核心逻辑可以概括为几个步骤:先根据用户名查出用户,再比对密码;密码通过后查出用户角色;用JWT生成带userId和role的token;返回前端。简单示例:

public LoginResponse login(LoginRequest req) { User user = userMapper.findByUsername(req.getUsername()); if (user == null || !BCrypt.checkpw(req.getPassword(), user.getPassword())) { throw new BusinessException("用户名或密码错误"); } String token = JwtUtil.createToken(user.getId(), user.getRole()); return new LoginResponse(token, user.getRealName(), user.getRole()); }

拦截器的核心逻辑非常直观:从请求头取Authorization前缀为Bearer的token,解析后放入ThreadLocal或Request上下文;解析失败返回401。在WebMvcConfigurer里注册拦截器,并放行/api/auth/login、静态资源等公开地址即可。

提示:JWT的密钥务必从配置文件中读取,不要硬编码。正式部署时建议至少用256位随机密钥,并设置合理的过期时间。

4.3 挂号与开药:两个最体现事务控制的地方

整个系统里最容易出问题、最考察基本功的两个操作,一个是挂号,一个是开药扣库存。这两个操作都不能只做单表插入。

挂号业务的要点是防止一个医生在同一时段被重复预约。我会在appointment表上建立(doctor_id, appointment_date, time_slot)的唯一索引,并在Service中使用事务。业务代码里先做一次查询校验,如果能查到未取消的挂号记录,直接提示“该时段已被预约”。数据库唯一索引是第二道防线,就算并发请求同时进来,也可以兜底拦住。

开处方更加典型:生成病历、插入处方明细、扣减药品库存,三个操作必须在一个事务里完成。扣库存不能直接执行update drug set stock = stock - 1 where id = ?,因为这种写法在并发超卖场景下会出大问题。更稳的写法是带条件扣减:

UPDATE drug SET stock = stock - #{num} WHERE id = #{drugId} AND stock >= #{num}

如果更新返回受影响行数为0,说明库存不足,直接抛出业务异常使事务回滚,一口药都不会多发。这段代码虽然只有几行,但“先查再减”和“条件扣减”的本质区别,就是面试官最喜欢问的并发问题,也是项目里真正能体现工程思维的地方。

4.4 接口设计:REST风格接口一览

接口设计上,统一使用/api前缀,按资源命名,状态码和返回结构与前端约定好。核心接口如下:

功能接口地址方法说明
登录/api/auth/loginPOST用户名密码获取token
健康档案查询/api/healthRecord/{id}GET查看指定档案
健康档案保存/api/healthRecordPOST新增档案
医生列表/api/doctor/listGET分页查询医生
预约挂号/api/appointmentPOST新增预约
取消预约/api/appointment/{id}PUT修改预约状态
病历列表/api/medicalRecord/pageGET分页查病历
新增病历处方/api/medicalRecordPOST含处方明细
药品分页/api/drug/pageGET分页查药品
扣减药品库存/api/drug/stockPUT条件扣减

统一返回结构可以采用{code, message, data}的格式,前端通过code判断业务是否成功,而不是只依赖HTTP状态码。这一点在前后端对接时能省去很多沟通成本。

5. 前端实现要点

5.1 前端技术栈与工程结构

前端这部分采用Vue3 + Vite + Vue Router + Pinia + Axios + Element Plus。工程目录结构如下:

src ├── api // 按模块拆分的接口请求 │ ├── auth.js │ ├── appointment.js │ ├── medicalRecord.js │ └── drug.js ├── assets // 静态资源 ├── components // 通用组件:分页表格、弹窗表单 ├── router // 路由配置 ├── store // Pinia状态 │ └── user.js ├── views // 页面视图 │ ├── login.vue │ ├── layout.vue │ ├── admin │ ├── doctor │ └── patient ├── utils // axios实例、token工具 └── App.vue

目录按“api-页面-状态”切分,核心思想是让新增一个模块时只动三个地方:后端Mapper/Service/Controller、前端api文件、前端views页面。模块之间尽量不互相引用,保证扩展性。

5.2 登录态与路由守卫:把token流转讲透

登录成功后的token必须被妥善保存,并注入后续每个请求。最朴素的做法是localStorage存储token,Pinia中维护用户信息。请求发出前,axios实例的请求拦截器统一加Authorization头:

axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = 'Bearer ' + token } return config })

响应拦截器做统一异常处理。比如后端返回业务码401表示token过期,就清除本地登录态并跳转到登录页;其他业务码统一弹出消息提示。

路由守卫的作用是控制页面访问权限。我的做法是在beforeEach里判断:如果路由meta中配置了角色,就检查当前用户角色是否在允许列表内,不在则重定向到无权限页。这样即使患者手动输入医生页面的URL,也无法绕过权限。

5.3 页面组件化开发,大幅缩减工作量

页面开发是工作量最大的部分,但Element Plus能把这部分劳动密集程度降下来很多。预约页面用日期选择器加医生下拉列表,列表页面用el-table+el-pagination,新增和编辑统一用el-dialog弹窗完成,校验规则用el-form的rules。一套写完之后,药品管理、用户管理等页面基本就是复制改配置。

这里有一个很实际的坑:Element Plus的分页组件触发current-change事件时传的是当前页数,而后端PageHelper返回的字段通常有pageNum和pageSize,如果接口要求参数名不一致,就会导致切页无效。建议前端api层统一做一次参数映射,后端返回的分页结构也统一用{total, list, pageNum, pageSize}格式,前端不直接依赖具体字段,页面会稳定很多。

5.4 本地联调环境:Vite代理解决跨域

前后端分离开发时跨域问题几乎必然遇到。本地开发阶段的最优方案不是开启后端CORS,而是用Vite的代理把请求转发到后端端口,这样前端请求的URL和后端实际接口地址在浏览器看来是同源的,彻底规避跨域:

// vite.config.js server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

注意生产环境部署时,同样可以利用Nginx的反向代理做类似事情,前端和后端共用域名或域名下的不同路径,前后端分离但对外是统一的访问入口。

6. 从零跑通项目的实操记录

6.1 环境清单与版本选择

给出我实测比较稳的环境组合:

工具推荐版本说明
JDK8 或 11SpringBoot 2.7建议JDK8/11,3.x需要17+
Maven3.6.3+最好用国内镜像加速
Node16/18 LTSVite5对Node版本有要求
MySQL5.7 或 8.0注意8.0驱动包有差异
IDEIntelliJ IDEA后端主力
前端工具VSCode轻量,配Vue插件

版本不必追求最新。JDK17配合SpringBoot 3虽然新,但一些老MyBatis配置或自定义starter可能出现兼容问题,新手容易踩坑。如果目标是“快速跑通项目”,SpringBoot 2.7.18 + JDK8 是最稳的选择。

6.2 数据库初始化

拿到源码后第一步永远是初始化数据库。用Navicat或命令行创建数据库,例如community_medical,字符集选utf8mb4,排序规则选utf8mb4_general_ci,然后导入项目提供的community_medical.sql文件。导入成功后确认几件事:核心表数量是否正确,admin账号是否存在于用户表,药品表中是否有初始数据。

随后修改后端application.yml:

spring: datasource: url: jdbc:mysql://localhost:3306/community_medical?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver

这段配置里最容易出问题的两个点:一是MySQL8必须用com.mysql.cj.jdbc.Driver,二是必须加serverTimezone=Asia/Shanghai,否则默认时区会给日期类型字段报错。

6.3 后端启动与接口验证

后端启动可以通过IDE直接运行启动类,也可以使用命令行:

mvn clean package -DskipTests java -jar target/community-medical-system.jar

启动成功后建议先验证登录接口,避免连页面都打不开就开始后端异常排查。可以用Postman或者curl:

curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'

如果返回的JSON中包含token字段,说明数据库连接、MyBatis配置、登录逻辑都已通关。这一步不要跳过,它能快速定位80%的启动类问题。

6.4 前端启动与联调

前端进入工程目录后依次执行:

npm install npm run dev

如果npm install速度太慢或者报错,可以先设置淘宝镜像源npm config set registry https://registry.npmmirror.com再重新安装。启动成功后在浏览器访问http://localhost:5173,此时页面上所有/api请求都会通过Vite代理发送到8080端口,实现前后端联调。

建议联调顺序也是按业务链路走:登录 -> 查看医生列表 -> 添加健康档案 -> 预约挂号 -> 以医生身份登录接诊 -> 写病历开处方 -> 查看药品库存是否减少。这条链路只要跑通,项目核心功能基本全部验证完。

6.5 服务器部署要点

本地跑通以后,部署上线也就几步:

后端打成jar包(上文已经完成),放到一台有JDK和MySQL的服务器,最简单的方式是用nohup守护进程启动:

nohup java -Xms512m -Xmx512m -jar community-medical-system.jar > app.log 2>&1 &

前端打包命令是npm run build,产物在dist目录,把它整体上传到服务器,再用Nginx托管,并配置反向代理转发API:

server { listen 80; server_name your-domain.com; location / { root /var/www/community-medical; 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这行非常关键,它解决的是前端路由使用history模式时刷新页面404的问题。少了这行,用户停留在某子页面刷新直接白屏,属于部署最高频踩坑点。

7. 常见问题与排查实录

7.1 数据库连接失败或日期时间报错

现象:启动报Access denied for user或The server time zone value错误。

排查方向:前者检查用户名、密码和授权,确认MySQL里账号允许远端或localhost访问;后者几乎都是时区配置缺失,按上文在JDBC URL中加上serverTimezone=Asia/Shanghai即可。MySQL8的驱动类名要和pom中的依赖版本匹配,不能用MySQL5的旧驱动。

7.2 MyBatis映射不到字段或XML找不到

现象:查询结果全是null,或启动报Invalid bound statement (not found)。

第一类问题基本是驼峰命名没有开启转换。数据库字段常是user_id,实体属性是userId,在application.yml中配置mybatis.configuration.map-underscore-to-camel-case=true即可自动映射。

第二类问题是因为Maven打包src/main/java路径下的XML资源默认被排除,需要在pom.xml中做资源包含配置,或者在打包后确认target目录下确实存在Mapper XML文件。这个坑几乎每个人都会碰到一次。

7.3 前端请求跨域失败

现象:浏览器控制台提示CORS错误,或者请求发出去了但Response里没有业务数据。

如果已经配置了Vite/Nginx代理,仍然出现跨域,最常见的原因是前后端调用地址不一致,比如前端用了http://localhost:8080/api直连后端,绕过了代理。正确的做法是前端请求统一使用/api相对路径,由代理转发。生产环境同样检查Nginx的proxy_pass目标地址是否写对。

7.4 刷新页面404或白屏

现象:访问首页正常,但在某个路由刷新就404;或者部署后打开是空白页。

历史路由模式导致,解决方案就是Nginx配置try_files。如果不想依赖服务器配置,可以把Vue Router切换为hash模式,URL会带上#号,刷新不会404,但不够美观。二选一即可。

7.5 预约冲突或库存扣成负数

现象:并发操作下同一个时段被预约两单,或药品库存变成负数。

这是典型的并发问题,不是简单加个sql能解决。除了在代码里做条件扣减(stock >= num),预约表还需要建唯一索引做兜底。更重要的是,这两个操作都必须在事务方法中执行,任何一个异常都要让整体回滚。测试时可以用JMeter或手动开两个浏览器标签同时提交,验证幂等性。

我个人在这个项目上最大的体会是:这类管理系统真正的难点从来不是某个框架用法,而是数据表设计和对业务链路的理解。如果接到这类项目,先不要急着写代码,花一天时间画实体关系、理清状态流转,后面能省三天的返工时间。另外强烈建议为系统加上操作日志和简单的图表统计模块,一是让整个系统真实感更强,二是在答辩或演示时,有数据支撑的系统远比空页面更有说服力。后面如果大家需要,我可以再单独写一篇关于“预约防重复”和“药品库存流水”的并发方案拆解,把这两个点讲得更深一些。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询