简介:本资源是一套完整的基于Spring Boot的医院急诊系统毕业设计级源码,面向计算机专业本科生、Java全栈初学者及医疗信息化课程实践者,解决急诊业务流程数字化、前后端分离开发与MySQL数据管理等典型工程问题。压缩包共852个文件,17.7MB,涵盖138个Java后端逻辑文件、50个Vue组件页面、153个JS交互脚本、44个CSS样式文件及63个JPG/GIF图片资源,辅以SQL建表语句、YML配置、BAT部署脚本和详细说明文档,结构清晰、模块完整。已有67人学习下载,资源经实测可正常运行,提供从环境搭建(JDK 1.8+MySQL 5.7/8+Eclipse/IDEA)到前后端联调的完整路径,包含登录、分诊、病历录入、医生排班等核心功能模块,且保留了.bak备份文件与多版本静态资源(SVG/woff/ttf等),便于理解开发迭代过程与UI适配逻辑。
1. 项目概述:一个面向实战的医院急诊系统
最近在整理过往项目资料时,翻出了一个几年前主导开发的医院急诊系统。这个项目在当时算是一个比较典型的“互联网+医疗”的院内信息化升级案例,核心目标是将急诊科从传统的手工登记、纸质流转的混乱状态,升级为一个流程化、数字化、可视化的高效工作平台。项目打包文件里包含了完整的后端(Spring Boot + Java)、前端(Vue.js)、数据库(MySQL)源码以及详细的说明文档,算是一个麻雀虽小但五脏俱全的实战项目。
这个系统主要解决了几个急诊科的痛点:一是患者信息录入慢、易出错,二是抢救、检查、治疗等环节的状态无法实时同步,导致医护人员“跑断腿”沟通,三是各类文书(抢救记录、医嘱单)生成效率低下,四是管理者无法直观掌握急诊室的实时负荷与资源使用情况。我们通过一个B/S架构的Web系统,将分诊台、抢救室、留观区、药房、检验科等角色串联起来,实现了从患者入院到离院(或转入住院)的全流程闭环管理。
对于开发者而言,这个项目源码的价值在于它并非一个简单的“增删改查”Demo,而是涉及了复杂的业务流程状态机、高实时性的WebSocket通信、多角色权限控制、以及前端复杂表格和图表展示等实战场景。无论你是想学习Spring Boot如何构建中型企业应用,Vue.js如何管理复杂的组件状态,还是想了解一个真实医疗系统的数据库设计与业务逻辑,它都能提供一个不错的参考范本。
2. 系统核心架构与设计思路拆解
2.1 技术栈选型背后的考量
当时选择Spring Boot + Vue.js + MySQL这个组合,是经过一番权衡的,现在看来依然是中小型后台管理系统的高效组合拳。
后端选择Spring Boot,核心诉求是“快速搭建、易于维护”。急诊系统业务模块多(分诊、抢救、医嘱、药品、统计等),Spring Boot的自动配置和起步依赖能让我们迅速集成MyBatis(数据层)、Spring Security(安全)、WebSocket(实时通信)、Redis(缓存与Session共享)等关键组件。它的“约定大于配置”理念,让团队能更专注于业务逻辑开发,而非繁琐的XML配置。例如,通过spring-boot-starter-websocket一个依赖,就轻松实现了抢救室大屏与护士站PC端的实时信息同步。
前端选择Vue.js而非当时的 Angular 或 React,主要基于两点:一是上手曲线平缓,团队前端资源有限,Vue的模板语法对后端转前端的同学更友好;二是其响应式数据绑定和组件化开发模式,非常适合构建像急诊系统这样拥有大量表单、表格和动态交互的SPA(单页应用)。我们利用Vue Router管理不同角色(医生、护士、管理员)的视图路由,用Vuex集中管理患者列表、医嘱项等全局状态,使得前端逻辑清晰可控。
数据库选用MySQL,这是最务实的选择。急诊系统的数据关系虽然复杂(患者、病历、医嘱、药品、库存等),但事务性要求强,且存在大量的关联查询(如查询某个患者的所有检查记录)。MySQL在5.7版本后对JSON字段的支持,也让我们在存储一些动态扩展的体征信息时更加灵活。同时,其成熟的生态和运维经验,对于医院信息科后续的维护至关重要。
2.2 业务架构与模块划分
系统在业务上采用了经典的分层架构和模块化设计,确保高内聚、低耦合。
后端模块划分:
- 急诊核心模块:包含患者登记、分诊(根据病情严重程度分级,如1级濒危、2级危重等)、急诊病历书写。这是数据入口。
- 抢救与留观模块:这是系统的“心脏”。管理抢救床位、实时记录生命体征(通过接口对接监护仪数据,或手动录入)、生成抢救记录。留观区则管理需要短期观察的患者。
- 医嘱与执行模块:医生开具药品、检查、治疗医嘱,护士核对并执行。这里实现了严格的闭环管理,每条医嘱都有“已开立、已审核、已执行、已停止”等状态。
- 药品与物资管理模块:与医院HIS(医院信息系统)或独立的库存系统对接,实现药品的申领、发放、盘点,确保急救药品随时可用。
- 统计与报表模块:为科室主任和管理者提供实时数据看板,如实时在院人数、患者停留时间、病种分布、医护工作量等。
前后端交互设计:采用完全分离的模式。前端Vue项目独立部署,通过Axios库调用后端Spring Boot提供的RESTful API。所有API请求都经由Spring Security和JWT(JSON Web Token)进行认证和授权。这种分离为未来可能的移动端(如护士Pad端)扩展提供了便利。
数据库设计核心思想:围绕“患者一次就诊”为核心。主表是visit_record(就诊记录),关联patient_info(患者基本信息)、triage_record(分诊记录)、medical_orders(医嘱主表)、order_execution(医嘱执行记录)等。大量使用外键约束保证数据一致性,并对visit_record表的status字段(如“分诊中”、“抢救中”、“留观中”、“已离院”)建立了索引,以优化高频的状态查询。
注意:在实际医疗系统中,患者基本信息通常来自医院的EMPI(患者主索引)系统,本项目为了演示完整性,独立设计了
patient_info表。真实上线时,这部分应通过接口同步。
3. 关键功能实现细节与核心技术点
3.1 高实时性:WebSocket在急诊大屏的应用
急诊抢救室通常配备一块大屏幕,动态显示所有抢救床位患者的关键信息(姓名、年龄、生命体征、预警状态)。这是系统的“门面”,要求信息延迟极低。
我们使用Spring Boot集成的STOMP over WebSocket协议来实现。后端通过@Controller注解处理STOMP消息,前端使用SockJS-client和stompjs库连接。
后端关键配置与代码:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 指定连接端点,允许SockJS降级 registry.addEndpoint("/ws-emergency").setAllowedOriginPatterns("*").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 启用简单的内存代理,用于订阅/广播 registry.enableSimpleBroker("/topic", "/queue"); registry.setApplicationDestinationPrefixes("/app"); } }当护士在PC端更新某个抢救患者的体征(如血压)时,后端服务在更新数据库后,会立即向特定主题发布消息:
@Service public class PatientMonitorService { @Autowired private SimpMessagingTemplate messagingTemplate; public void updateVitalSigns(PatientVitalSigns signs) { // 1. 更新数据库... vitalSignsMapper.update(signs); // 2. 计算预警状态(如血压过高/过低) String alertLevel = calculateAlertLevel(signs); signs.setAlertLevel(alertLevel); // 3. 广播给所有订阅了该床位信息的客户端 messagingTemplate.convertAndSend("/topic/rescue-bed/" + signs.getBedId(), signs); } }前端关键实现:在Vue组件中,我们会在mounted生命周期钩子中建立WebSocket连接,并订阅对应的床位主题。
mounted() { this.connectWebSocket(); }, methods: { connectWebSocket() { const socket = new SockJS('/ws-emergency'); const stompClient = Stomp.over(socket); stompClient.connect({}, (frame) => { // 订阅所有抢救床位主题,这里以1号床为例 stompClient.subscribe('/topic/rescue-bed/1', (message) => { const updatedSigns = JSON.parse(message.body); // 更新本地数据,视图会自动响应 this.patientData = updatedSigns; // 如果预警状态变化,触发前端弹窗或声音提示 if (updatedSigns.alertLevel !== 'normal') { this.showAlert(updatedSigns); } }); }); } }实操心得:WebSocket连接在用户刷新或网络波动时会中断。我们前端增加了心跳检测和自动重连机制。另外,对于非实时性要求极高的数据(如患者列表更新),我们仍采用HTTP轮询或长轮询,以减轻WebSocket服务器的压力。
3.2 复杂状态流转:基于状态模式的医嘱生命周期管理
一条医嘱从开立到完成,状态流转复杂且规则严格。例如,医生开立(CREATED) -> 护士审核(VERIFIED) -> 护士执行(EXECUTING) -> 执行完成(COMPLETED)。也可能被医生停止(STOPPED)。如果使用大量的if-else来判断状态能否变更以及变更后需要触发的动作(如执行医嘱时要扣减库存),代码会非常臃肿且难以维护。
我们引入了状态模式来优雅地管理医嘱状态。首先定义一个状态接口和一系列具体状态类:
public interface OrderState { // 能否切换到目标状态 boolean canTransitionTo(OrderState nextState); // 状态变更时执行的动作 void handleAction(MedicalOrder order, OrderContext context); } @Component public class CreatedState implements OrderState { @Override public boolean canTransitionTo(OrderState nextState) { // 创建状态只能转向“已审核”或“已取消” return nextState instanceof VerifiedState || nextState instanceof CancelledState; } @Override public void handleAction(MedicalOrder order, OrderContext context) { // 创建时,可能初始化一些默认属性,或记录日志 log.info("医嘱[{}]已开立,开立医生:{}", order.getOrderNo(), order.getCreateDoctor()); } } @Component public class ExecutingState implements OrderState { @Autowired private InventoryService inventoryService; @Override public boolean canTransitionTo(OrderState nextState) { return nextState instanceof CompletedState || nextState instanceof StoppedState; } @Override @Transactional // 重要:状态变更涉及业务操作,需要事务管理 public void handleAction(MedicalOrder order, OrderContext context) { // 执行医嘱时,核心业务是扣减药品或材料库存 if ("MEDICATION".equals(order.getOrderType())) { inventoryService.deductStock(order.getItemId(), order.getQuantity(), order.getPatientId()); } // 记录执行时间和执行人 order.setExecuteTime(new Date()); order.setExecuteNurse(context.getCurrentUserId()); log.info("医嘱[{}]开始执行,执行护士:{}", order.getOrderNo(), context.getCurrentUserId()); } }然后,有一个上下文类OrderContext来持有当前状态并处理状态转换请求:
@Service public class OrderContext { private MedicalOrder order; private OrderState currentState; @Autowired private ApplicationContext appContext; // 用于获取状态Bean public void transitionTo(String stateName) { OrderState nextState = (OrderState) appContext.getBean(stateName); if (currentState.canTransitionTo(nextState)) { currentState = nextState; currentState.handleAction(this.order, this); // 更新数据库中订单的状态字段 order.setStatus(stateName); updateOrderInDB(order); } else { throw new IllegalStateException("无法从状态[" + currentState + "]转换到[" + stateName + "]"); } } }这样,当护士点击“执行医嘱”按钮时,后端服务只需调用orderContext.transitionTo("executingState")。所有的状态校验和伴随业务逻辑都封装在对应的状态类中,新增状态或修改规则变得非常清晰。
3.3 前端复杂表格与图表渲染优化
急诊系统的管理后台有大量数据表格(如患者列表、医嘱列表)和统计图表(如24小时就诊人次趋势图)。如果处理不当,页面性能会急剧下降。
对于大型表格(Vue + Element UI):
- 虚拟滚动:对于可能超过1000条的数据,我们使用了Element UI的
el-table结合vue-virtual-scroller实现虚拟滚动。只渲染可视区域内的DOM元素,极大提升了渲染性能。 - 分页与懒加载:后端API支持分页查询,前端表格每次只加载当前页数据。对于表格内的展开行详情(如查看某患者的所有医嘱),采用懒加载方式,点击展开时才去请求数据。
- 列显示优化:允许用户自定义显示哪些列,减少不必要的数据绑定和DOM节点。
对于数据图表(ECharts):
- 按需引入:不使用完整的ECharts包,而是通过
echarts/core和echarts/charts等按需引入的方式,减小最终打包体积。 - 数据抽样:当需要展示长时间段(如一年)的精细趋势图时,请求全部数据在前端渲染可能导致卡顿。我们与后端约定,当数据点超过一定数量(如1000个)时,后端进行降采样(如取日均值)后再返回。
- 防抖请求:在时间范围选择器组件上,应用防抖函数,避免用户快速切换范围时频繁发起请求。
// 使用lodash的debounce优化图表数据请求 import { debounce } from 'lodash'; export default { data() { return { fetchChartData: debounce(this.realFetchData, 500) // 500ms防抖 }; }, methods: { handleDateChange() { // 用户改变时间范围时,触发防抖函数 this.fetchChartData(); }, realFetchData() { // 实际发起API请求获取图表数据 axios.get('/api/statistics/visit-trend', {params: this.queryParams}).then(...); } } }4. 数据库设计与核心业务逻辑实现
4.1 核心表结构设计解析
数据库设计是整个系统的基石。以下是几个核心表的设计思路:
1. 就诊记录表 (visit_record)这是整个急诊流程的“总纲”,一次就诊一条记录。
CREATE TABLE `visit_record` ( `visit_id` varchar(32) NOT NULL COMMENT '就诊流水号,主键', `patient_id` varchar(32) NOT NULL COMMENT '患者ID', `triage_level` tinyint(4) DEFAULT NULL COMMENT '分诊等级:1-濒危,2-危重,3-急症,4-非急症', `chief_complaint` varchar(500) DEFAULT NULL COMMENT '主诉', `status` varchar(50) NOT NULL DEFAULT 'REGISTERED' COMMENT '状态:REGISTERED, TRIAGED, RESCUING, OBSERVING, DISCHARGED, ADMITTED...', `register_time` datetime NOT NULL COMMENT '挂号时间', `discharge_time` datetime DEFAULT NULL COMMENT '离院时间', `attending_doctor_id` varchar(32) DEFAULT NULL COMMENT '接诊医生ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`visit_id`), KEY `idx_patient_id` (`patient_id`), KEY `idx_status_time` (`status`,`register_time`) -- 复合索引,用于高频查询 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='急诊就诊记录表';设计要点:
status字段使用明确的字符串,而非魔法数字,提高可读性。idx_status_time索引对于按状态和时段筛选患者的查询至关重要。
2. 医嘱表 (medical_orders) 与执行记录表 (order_execution)我们将其拆分为主表和执行表,以支持一条长期医嘱(如“测血压q6h”)对应多次执行记录。
CREATE TABLE `medical_orders` ( `order_id` bigint(20) NOT NULL AUTO_INCREMENT, `visit_id` varchar(32) NOT NULL COMMENT '关联就诊ID', `order_type` varchar(50) NOT NULL COMMENT '医嘱类型:MEDICATION(药品), EXAMINATION(检查), TREATMENT(治疗)', `order_content` varchar(1000) NOT NULL COMMENT '医嘱内容', `order_status` varchar(50) NOT NULL DEFAULT 'CREATED' COMMENT '状态:CREATED, VERIFIED, EXECUTING, COMPLETED, STOPPED', `frequency` varchar(100) DEFAULT NULL COMMENT '频次,如“每日一次”、“每6小时一次”', `doctor_id` varchar(32) NOT NULL COMMENT '开嘱医生', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`order_id`), KEY `idx_visit_status` (`visit_id`,`order_status`) ) ENGINE=InnoDB COMMENT='医嘱主表'; CREATE TABLE `order_execution` ( `execution_id` bigint(20) NOT NULL AUTO_INCREMENT, `order_id` bigint(20) NOT NULL COMMENT '关联医嘱ID', `scheduled_time` datetime NOT NULL COMMENT '计划执行时间', `actual_time` datetime DEFAULT NULL COMMENT '实际执行时间', `executor_id` varchar(32) DEFAULT NULL COMMENT '执行护士ID', `result_note` varchar(500) DEFAULT NULL COMMENT '执行结果备注', `status` varchar(50) DEFAULT 'PENDING' COMMENT '执行状态:PENDING, EXECUTED, CANCELLED', PRIMARY KEY (`execution_id`), KEY `idx_order_schedule` (`order_id`,`scheduled_time`) ) ENGINE=InnoDB COMMENT='医嘱执行记录表';设计要点:拆分后,
medical_orders表更稳定,而高频变动的执行记录放在order_execution表,符合数据库设计范式,也便于查询某条医嘱的所有执行历史。
4.2 关键业务逻辑:分诊与患者流转
分诊是急诊的“第一关”。其核心逻辑是根据患者的生命体征、主诉等,自动或手动计算出一个分诊等级(1-4级),并据此决定患者流向(立即抢救、优先就诊、普通排队等)。
后端分诊逻辑实现示例:我们设计了一个分诊规则引擎,规则可以配置(例如,存储在数据库表中)。当分诊护士提交信息时,后端会遍历规则进行计算。
@Service public class TriageService { @Autowired private TriageRuleRepository ruleRepository; public TriageResult evaluate(PatientTriageData data) { List<TriageRule> rules = ruleRepository.findActiveRules(); int totalScore = 0; String suggestedLevel = "4"; // 默认非急症 List<String> triggers = new ArrayList<>(); for (TriageRule rule : rules) { // 例如,规则:如果呼吸频率 > 30次/分,加5分 if ("respiratory_rate".equals(rule.getIndicator()) && data.getRespiratoryRate() > rule.getThreshold()) { totalScore += rule.getScore(); triggers.add(rule.getRuleName()); } // 规则:如果收缩压 < 90mmHg,加10分 if ("systolic_bp".equals(rule.getIndicator()) && data.getSystolicBP() < rule.getThreshold()) { totalScore += rule.getScore(); triggers.add(rule.getRuleName()); } // ... 其他规则,如血氧饱和度、意识状态等 } // 根据总分映射分诊等级 if (totalScore >= 15) { suggestedLevel = "1"; // 濒危 } else if (totalScore >= 10) { suggestedLevel = "2"; // 危重 } else if (totalScore >= 5) { suggestedLevel = "3"; // 急症 } TriageResult result = new TriageResult(); result.setSuggestedLevel(suggestedLevel); result.setScore(totalScore); result.setTriggeredRules(triggers); // 同时,根据分诊等级,自动更新就诊记录状态,并推送消息 if ("1".equals(suggestedLevel) || "2".equals(suggestedLevel)) { // 推送到抢救室大屏和护士站 messagingTemplate.convertAndSend("/topic/new-critical-patient", data.getVisitId()); } return result; } }患者状态流转驱动业务:系统内很多操作都依赖于visit_record.status。我们使用Spring的事件发布机制来解耦状态变更后的后续动作。
// 1. 定义事件 public class PatientStatusChangedEvent extends ApplicationEvent { private String visitId; private String oldStatus; private String newStatus; // ... 构造方法和getter } // 2. 在更新状态的地方发布事件 @Service public class VisitRecordService { @Autowired private ApplicationEventPublisher eventPublisher; @Transactional public void updateStatus(String visitId, String newStatus) { VisitRecord record = visitRecordMapper.selectById(visitId); String oldStatus = record.getStatus(); record.setStatus(newStatus); visitRecordMapper.updateById(record); // 发布状态变更事件 eventPublisher.publishEvent(new PatientStatusChangedEvent(this, visitId, oldStatus, newStatus)); } } // 3. 监听事件,执行后续业务 @Component public class StatusChangeListener { @EventListener @Async // 异步处理,避免阻塞主流程 public void handlePatientStatusChanged(PatientStatusChangedEvent event) { if ("RESCUING".equals(event.getNewStatus())) { // 状态变为“抢救中”,自动创建抢救记录模板 rescueRecordService.createTemplate(event.getVisitId()); // 通知相关医护人员 notificationService.notifyRescueTeam(event.getVisitId()); } if ("DISCHARGED".equals(event.getNewStatus())) { // 状态变为“已离院”,自动结算未结费用(调用HIS接口) billingService.settleUnpaidOrders(event.getVisitId()); // 释放床位资源 bedService.releaseBedByVisitId(event.getVisitId()); } } }这种事件驱动的方式,使得核心的状态变更服务保持简洁,而各种复杂的后续业务(通知、创建记录、结算)由专门的监听器处理,系统扩展性大大增强。
5. 项目部署、配置与常见问题排查
5.1 从零开始的环境搭建与部署
拿到源代码后,要成功运行起来,需要按顺序完成以下步骤:
1. 后端环境准备:
- JDK:确保安装 JDK 8 或 11(Spring Boot 2.x 兼容版本)。在终端输入
java -version验证。 - Maven:安装 Maven 用于管理依赖和构建项目。解压源码后,在根目录(含
pom.xml的文件夹)运行mvn clean install下载依赖并打包。 - MySQL:安装 MySQL 5.7 或以上版本。使用
sql/init_database.sql文件(如果项目提供)创建数据库和表结构。如果没有,需要根据实体类手动建表。 - Redis (可选但推荐):如果项目使用了Redis做缓存或Session存储,需要安装并启动Redis服务。
2. 关键配置文件修改:核心配置文件是src/main/resources/application.yml或application.properties。
spring: datasource: url: jdbc:mysql://localhost:3306/emergency_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: your_username password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 # password: 如果有密码则配置 # 文件上传路径配置(用于存储上传的检查报告等) servlet: multipart: max-file-size: 10MB max-request-size: 100MB # 自定义配置,如JWT密钥、文件存储路径等 hospital: file: upload-path: /data/upload/emergency jwt: secret: your-256-bit-secret-key-here # 务必修改! expiration: 86400000 # token有效期,单位毫秒注意:
jwt.secret是安全关键项,必须使用一个足够复杂且保密的字符串替换,切勿使用默认值。
3. 前端环境准备与运行:
- Node.js:安装 Node.js (建议 LTS 版本),自带 npm。
- 进入前端项目目录(通常包含
package.json和vue.config.js)。 - 运行
npm install或yarn install安装所有依赖。国内用户建议配置淘宝镜像:npm config set registry https://registry.npmmirror.com。 - 修改前端配置文件,通常是
src/config/index.js或.env.development,将API请求的基地址指向你的后端服务地址(例如VUE_APP_API_BASE_URL=http://localhost:8080)。 - 运行
npm run serve启动开发服务器,或npm run build构建生产环境静态文件。
4. 部署上线:
- 后端:使用
mvn clean package打包生成target/emergency-system-0.0.1-SNAPSHOT.jar。然后通过java -jar emergency-system-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod命令启动。生产环境建议使用 nohup 或 systemd 托管。 - 前端:运行
npm run build生成dist文件夹。将其内容部署到Nginx或Apache等Web服务器上。并配置反向代理,将/api等请求转发到后端Spring Boot应用。 - Nginx 配置示例:
server { listen 80; server_name your-domain.com; location / { root /path/to/your/dist; index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } location /api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # WebSocket代理配置 location /ws-emergency/ { proxy_pass http://localhost:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }
5.2 开发与运行中的典型问题及解决
在开发和部署这个系统的过程中,我们踩过不少坑,这里总结几个最常见的问题:
1. 前端跨域问题 (CORS)在开发阶段,前端运行在localhost:8081,后端在localhost:8080,浏览器会因同源策略阻止请求。
- 解决方案:在后端Spring Boot中配置全局CORS。
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") // 生产环境应指定具体域名 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意:生产环境
allowedOriginPatterns务必替换为实际的前端域名,如"https://hospital.example.com",使用"*"存在安全风险。
2. 前端路由在刷新后404使用Vue Router的history模式,在非根路径刷新页面时,Nginx会尝试去找对应的文件,导致404。
- 解决方案:如上文Nginx配置所示,关键是在location
/块中添加try_files $uri $uri/ /index.html;这行配置,将所有非静态文件的请求重定向到index.html,由前端路由接管。
3. 数据库连接池耗尽或慢查询在高并发下,可能出现“Cannot get connection from pool”错误或接口响应极慢。
- 排查与解决:
- 检查连接池配置:在
application.yml中优化HikariCP(Spring Boot默认连接池)参数,如maximum-pool-size、connection-timeout。 - 分析慢查询日志:在MySQL中开启慢查询日志
slow_query_log=ON,找到执行时间过长的SQL。 - 优化索引:使用
EXPLAIN分析慢查询SQL的执行计划,检查是否缺少索引或索引失效。例如,为visit_record表的status和register_time字段添加复合索引,对提升按状态和时间筛选的查询速度效果显著。 - 代码层面:检查是否有在循环中执行SQL查询的“N+1查询问题”,应使用MyBatis的
<collection>或@Many注解进行关联查询,或手动编写联表SQL。
- 检查连接池配置:在
4. 文件上传失败或路径错误系统需要上传检查报告、知情同意书等文件。
- 常见问题:配置的
spring.servlet.multipart.max-file-size太小;文件存储路径不存在或应用没有写入权限。 - 解决:
- 确保配置的文件大小限制足够。
- 在代码中或启动脚本里,检查并创建上传目录。
@PostConstruct public void init() { File uploadDir = new File(uploadPath); if (!uploadDir.exists()) { uploadDir.mkdirs(); } } - 在Linux服务器上,确保运行Java进程的用户(如
www-data或javaapp)对该目录有读写权限:chown -R javaapp:javaapp /data/upload/emergency。
5. WebSocket连接不稳定大屏或客户端偶尔收不到实时消息。
- 排查:
- 检查Nginx代理配置是否正确支持WebSocket升级(见上文配置)。
- 前端增加连接状态监听和自动重连逻辑。
stompClient.connect({}, onConnected, (error) => { console.error('连接失败,5秒后重连...', error); setTimeout(this.connectWebSocket, 5000); }); - 检查服务器防火墙是否放行了WebSocket使用的端口(通常是80/443或自定义端口)。
这个急诊系统项目,从技术选型到业务落地,涵盖了现代Web应用开发的多个核心环节。它不仅仅是一套代码,更是一套解决特定领域复杂问题的设计思想和工程实践的集合。对于学习者,我建议不要只停留在运行起来的层面,可以尝试去修改分诊规则、增加一个新的医嘱类型、或者优化一下那个复杂表格的渲染性能,在这个过程中遇到问题并解决,才是提升的真正途径。最后,在处理任何医疗相关数据时,请务必牢记数据安全和隐私保护,这是开发的底线。
本文还有配套的精品资源,点击获取