1. 项目概述
这个医院资源管理系统是我去年指导的一个计算机专业本科毕业设计项目,采用前后端分离架构,后端使用SpringBoot框架,前端基于Vue.js,数据库选用MySQL。系统主要实现了医院内部各类资源(包括医护人员、医疗设备、药品库存、病房床位等)的数字化管理,为医院管理者提供直观的数据看板和决策支持。
从技术选型来看,SpringBoot+Vue+MySQL的组合是目前企业级应用开发的主流方案。SpringBoot简化了后端服务的配置和部署,Vue提供了现代化的前端交互体验,MySQL则以其稳定性和成熟度成为关系型数据库的首选。这个技术栈既保证了系统的可靠性,又便于学生理解和实现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 技术栈选型考量
选择SpringBoot作为后端框架主要基于以下几点考虑:
- 自动配置特性大幅减少了XML配置,学生可以更专注于业务逻辑开发
- 内嵌Tomcat服务器简化了部署流程
- 丰富的starter依赖可以快速集成常用组件如MyBatis、Redis等
- 完善的文档和社区支持,遇到问题容易找到解决方案
前端选用Vue.js而非传统jQuery主要因为:
- 组件化开发模式更符合现代前端工程实践
- 响应式数据绑定简化了DOM操作
- 丰富的生态系统(Vuex、Vue Router等)
- 渐进式框架特性,学习曲线相对平缓
数据库选择MySQL 8.0版本,主要特性包括:
- 完善的ACID事务支持
- 良好的性能表现
- JSON数据类型支持
- 成熟的备份恢复机制
2.2 系统模块划分
系统主要分为以下几个功能模块:
-
用户权限管理模块
- 基于RBAC模型的权限控制
- JWT实现无状态认证
- 细粒度的功能权限控制
-
医护人员管理模块
- 医生/护士信息CRUD
- 排班管理
- 绩效统计
-
医疗设备管理模块
- 设备台账管理
- 维护保养记录
- 使用状态监控
-
药品库存管理模块
- 药品基础信息管理
- 库存预警
- 出入库记录
-
病房床位管理模块
- 床位状态可视化
- 患者分配
- 周转率统计
-
数据统计分析模块
- 资源使用率分析
- 运营指标看板
- 自定义报表导出
3. 核心功能实现
3.1 后端关键实现
SpringBoot应用配置:
java复制@SpringBootApplication
@MapperScan("com.hospital.mapper")
public class HospitalApplication {
public static void main(String[] args) {
SpringApplication.run(HospitalApplication.class, args);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
JWT认证实现示例:
java复制public class JwtTokenUtil {
private static final String SECRET_KEY = "hospital-secret";
private static final long EXPIRATION = 86400000; // 24小时
public static String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
MyBatis动态SQL示例(药品库存查询):
xml复制<select id="selectDrugStock" resultType="DrugStockVO">
SELECT d.*, s.current_quantity, s.min_quantity
FROM drug d
LEFT JOIN stock s ON d.id = s.drug_id
<where>
<if test="name != null and name != ''">
AND d.name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="category != null">
AND d.category = #{category}
</if>
<if test="lowStockOnly">
AND s.current_quantity < s.min_quantity
</if>
</where>
ORDER BY s.current_quantity ASC
</select>
3.2 前端关键实现
Vue路由配置示例:
javascript复制const routes = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard'),
name: 'Dashboard',
meta: { title: '控制台', icon: 'dashboard', affix: true }
}
]
},
{
path: '/equipment',
component: Layout,
redirect: '/equipment/list',
name: 'Equipment',
meta: { title: '设备管理', icon: 'equipment' },
children: [
{
path: 'list',
component: () => import('@/views/equipment/list'),
name: 'EquipmentList',
meta: { title: '设备列表' }
},
{
path: 'detail/:id',
component: () => import('@/views/equipment/detail'),
name: 'EquipmentDetail',
meta: { title: '设备详情', noCache: true },
hidden: true
}
]
}
]
病房状态可视化组件:
vue复制<template>
<div class="ward-container">
<div v-for="room in rooms" :key="room.id" class="room-card">
<h3>{{ room.name }}</h3>
<div class="bed-grid">
<div
v-for="bed in room.beds"
:key="bed.id"
:class="['bed', bed.status]"
@click="handleBedClick(bed)"
>
<span>{{ bed.number }}</span>
<span v-if="bed.patient" class="patient-name">{{ bed.patient.name }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
rooms: {
type: Array,
required: true
}
},
methods: {
handleBedClick(bed) {
if (bed.status === 'available') {
this.$emit('assign-bed', bed)
} else {
this.$emit('show-detail', bed)
}
}
}
}
</script>
<style scoped>
.ward-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.room-card {
border: 1px solid #ddd;
padding: 15px;
border-radius: 8px;
width: 300px;
}
.bed-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-top: 10px;
}
.bed {
height: 60px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 4px;
cursor: pointer;
}
.bed.available {
background-color: #e6f7ff;
border: 1px dashed #1890ff;
}
.bed.occupied {
background-color: #fff2f0;
border: 1px solid #ff4d4f;
}
.bed.maintenance {
background-color: #fffbe6;
border: 1px solid #faad14;
}
.patient-name {
font-size: 12px;
margin-top: 4px;
}
</style>
4. 数据库设计
4.1 核心表结构
用户权限相关表:
sql复制CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录名',
`password` varchar(100) NOT NULL COMMENT '密码',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(20) DEFAULT NULL COMMENT '手机号',
`status` tinyint DEFAULT '1' COMMENT '状态 0:禁用 1:正常',
`dept_id` bigint DEFAULT NULL COMMENT '部门ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户';
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL COMMENT '角色名称',
`remark` varchar(100) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色';
CREATE TABLE `sys_menu` (
`id` bigint NOT NULL AUTO_INCREMENT,
`parent_id` bigint DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(50) DEFAULT NULL COMMENT '菜单名称',
`url` varchar(200) DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(500) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`type` int DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(50) DEFAULT NULL COMMENT '菜单图标',
`order_num` int DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单管理';
医疗业务相关表:
sql复制CREATE TABLE `medical_staff` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL COMMENT '关联系统用户ID',
`staff_no` varchar(20) NOT NULL COMMENT '工号',
`name` varchar(50) NOT NULL COMMENT '姓名',
`gender` tinyint DEFAULT NULL COMMENT '性别 0:未知 1:男 2:女',
`birth_date` date DEFAULT NULL COMMENT '出生日期',
`id_card` varchar(18) DEFAULT NULL COMMENT '身份证号',
`position` varchar(50) DEFAULT NULL COMMENT '职位',
`title` varchar(50) DEFAULT NULL COMMENT '职称',
`department_id` bigint DEFAULT NULL COMMENT '所属科室',
`employment_date` date DEFAULT NULL COMMENT '入职日期',
`status` tinyint DEFAULT '1' COMMENT '状态 0:离职 1:在职',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `staff_no` (`staff_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='医护人员信息';
CREATE TABLE `medical_equipment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`equipment_no` varchar(50) NOT NULL COMMENT '设备编号',
`name` varchar(100) NOT NULL COMMENT '设备名称',
`model` varchar(100) DEFAULT NULL COMMENT '型号',
`spec` varchar(200) DEFAULT NULL COMMENT '规格',
`category_id` bigint DEFAULT NULL COMMENT '设备分类',
`department_id` bigint DEFAULT NULL COMMENT '所属科室',
`purchase_date` date DEFAULT NULL COMMENT '购置日期',
`price` decimal(10,2) DEFAULT NULL COMMENT '价格',
`manufacturer` varchar(100) DEFAULT NULL COMMENT '生产厂家',
`warranty_period` int DEFAULT NULL COMMENT '保修期(月)',
`status` tinyint DEFAULT '1' COMMENT '状态 0:报废 1:在用 2:维修中',
`last_maintenance` date DEFAULT NULL COMMENT '上次维护日期',
`next_maintenance` date DEFAULT NULL COMMENT '下次维护日期',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
UNIQUE KEY `equipment_no` (`equipment_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='医疗设备';
CREATE TABLE `drug` (
`id` bigint NOT NULL AUTO_INCREMENT,
`code` varchar(50) NOT NULL COMMENT '药品编码',
`name` varchar(100) NOT NULL COMMENT '药品名称',
`generic_name` varchar(100) DEFAULT NULL COMMENT '通用名',
`spec` varchar(100) NOT NULL COMMENT '规格',
`unit` varchar(20) NOT NULL COMMENT '单位',
`category_id` bigint DEFAULT NULL COMMENT '分类ID',
`manufacturer` varchar(100) DEFAULT NULL COMMENT '生产厂家',
`approval_no` varchar(100) DEFAULT NULL COMMENT '批准文号',
`price` decimal(10,2) DEFAULT NULL COMMENT '单价',
`description` text DEFAULT NULL COMMENT '药品说明',
PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='药品信息';
CREATE TABLE `ward` (
`id` bigint NOT NULL AUTO_INCREMENT,
`ward_no` varchar(20) NOT NULL COMMENT '病区编号',
`name` varchar(50) NOT NULL COMMENT '病区名称',
`type` tinyint DEFAULT NULL COMMENT '病区类型 1:内科 2:外科 3:儿科...',
`location` varchar(100) DEFAULT NULL COMMENT '位置',
`total_beds` int DEFAULT '0' COMMENT '总床位数',
`available_beds` int DEFAULT '0' COMMENT '可用床位数',
`nurse_station` varchar(50) DEFAULT NULL COMMENT '护士站',
`head_nurse_id` bigint DEFAULT NULL COMMENT '护士长ID',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
UNIQUE KEY `ward_no` (`ward_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='病区信息';
4.2 索引优化策略
为提高查询性能,我们在以下字段上建立了索引:
- 医护人员表的department_id和status字段
- 医疗设备表的department_id和status字段
- 药品表的category_id字段
- 所有业务表的时间字段(create_time, update_time等)
对于频繁查询的组合条件,如"按科室+状态查询设备",我们建立了复合索引:
sql复制ALTER TABLE medical_equipment ADD INDEX idx_dept_status (department_id, status);
5. 系统部署方案
5.1 开发环境搭建
后端开发环境:
- JDK 1.8+
- Maven 3.6+
- IntelliJ IDEA(推荐)或Eclipse
- MySQL 8.0+
- Redis(可选,用于会话管理)
前端开发环境:
- Node.js 14+
- npm或yarn
- Vue CLI 4+
- VS Code(推荐)或WebStorm
5.2 生产环境部署
后端部署步骤:
- 打包应用:
bash复制mvn clean package -DskipTests
- 上传生成的hospital-api.jar到服务器
- 创建启动脚本start.sh:
bash复制#!/bin/bash
nohup java -jar hospital-api.jar --spring.profiles.active=prod > hospital.log 2>&1 &
- 设置开机自启(Systemd方式):
ini复制[Unit]
Description=Hospital Management System
After=syslog.target
[Service]
User=hospital
ExecStart=/usr/bin/java -jar /opt/hospital/hospital-api.jar --spring.profiles.active=prod
SuccessExitStatus=143
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
前端部署步骤:
- 构建生产环境代码:
bash复制npm run build
- 将dist目录内容上传到Nginx服务器
- Nginx配置示例:
nginx复制server {
listen 80;
server_name hospital.example.com;
location / {
root /opt/hospital-frontend/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/ {
alias /opt/hospital-frontend/dist/static/;
expires 30d;
}
}
5.3 数据库部署建议
- 生产环境建议使用MySQL主从复制架构
- 配置合理的innodb_buffer_pool_size(通常为物理内存的70-80%)
- 开启慢查询日志:
sql复制SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
- 定期备份策略:
bash复制# 每日全量备份
mysqldump -uroot -p hospital > /backup/hospital_$(date +%Y%m%d).sql
6. 项目开发经验总结
6.1 技术难点与解决方案
跨域问题处理:
在后端增加CORS配置类:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.maxAge(3600);
}
}
大数据量导出性能优化:
- 使用POI的SXSSFWorkbook实现流式导出
- 分页查询数据,避免内存溢出
- 后端示例代码:
java复制@GetMapping("/export")
public void exportDrugStock(HttpServletResponse response,
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer category) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=drug_stock.xlsx");
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {
Sheet sheet = workbook.createSheet("药品库存");
// 创建表头...
int pageSize = 1000;
int pageNum = 1;
int rowNum = 1;
while (true) {
PageHelper.startPage(pageNum, pageSize);
List<DrugStockVO> list = drugService.selectDrugStock(name, category, false);
if (list.isEmpty()) {
break;
}
for (DrugStockVO item : list) {
Row row = sheet.createRow(rowNum++);
// 填充数据...
}
pageNum++;
PageHelper.clearPage();
}
workbook.write(response.getOutputStream());
} catch (IOException e) {
log.error("导出失败", e);
throw new RuntimeException("导出失败");
}
}
6.2 项目开发建议
-
代码规范:
- 遵循阿里巴巴Java开发手册
- 前端使用ESLint进行代码检查
- 统一的API响应格式:
java复制public class R<T> implements Serializable { private int code; private String msg; private T data; public static <T> R<T> ok() { return restResult(null, 200, "操作成功"); } public static <T> R<T> ok(T data) { return restResult(data, 200, "操作成功"); } public static <T> R<T> fail(String msg) { return restResult(null, 500, msg); } } -
接口文档:
使用Swagger或YApi维护API文档,示例配置:java复制@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.hospital.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("医院资源管理系统API文档") .description("前后端接口定义") .version("1.0") .build(); } } -
性能优化建议:
- 频繁访问的基础数据使用Redis缓存
- 复杂统计报表使用定时任务预计算
- 前端使用路由懒加载
- 图片等静态资源使用CDN加速
6.3 常见问题排查
问题1:前端打包后访问空白页
解决方案:
- 检查路由模式是否为history,需要服务器配合支持
- 确认publicPath配置正确
- 检查资源路径是否正确引用
问题2:MySQL连接超时
解决方案:
- 增加连接池配置:
yaml复制spring:
datasource:
hikari:
connection-timeout: 30000
maximum-pool-size: 20
idle-timeout: 600000
max-lifetime: 1800000
- 检查MySQL的wait_timeout参数
问题3:Vue页面数据不更新
解决方案:
- 检查是否正确地使用了响应式数据
- 对于数组操作,使用Vue.set或数组的变异方法
- 确保没有直接修改props
7. 论文写作要点
对于毕业设计论文,建议包含以下几个核心部分:
-
系统需求分析
- 功能性需求(用例图、功能模块说明)
- 非功能性需求(性能、安全性等)
-
系统设计
- 架构设计(技术选型、系统架构图)
- 数据库设计(ER图、表结构说明)
- 接口设计(关键API说明)
-
系统实现
- 核心功能实现(配关键代码片段)
- 难点解决方案
-
系统测试
- 测试用例设计
- 性能测试结果
-
总结与展望
- 项目成果总结
- 可改进方向
论文写作时要注意:
- 技术描述准确,避免模糊表述
- 图表规范,有编号和标题
- 参考文献格式统一
- 代码片段不宜过多,关键部分即可
这个医院资源管理系统作为毕业设计项目,涵盖了从需求分析到部署上线的完整开发流程,技术栈选择合理且主流,既保证了项目的实用性,又体现了学生的综合技术能力。在实际开发过程中,特别要注意权限控制的设计和性能优化,这些往往是毕业答辩时的重点考察内容。
