1. 项目概述
医院后台管理系统是现代医疗信息化建设的重要组成部分。作为一名长期从事医疗信息化开发的工程师,我经常遇到医院管理者抱怨传统手工操作效率低下、数据易丢失的问题。这套基于SpringBoot+Vue的医院后台管理系统,正是为了解决这些痛点而设计的。
系统采用前后端分离架构,后端使用SpringBoot框架提供RESTful API,前端采用Vue.js构建用户界面,数据库选用MySQL存储业务数据。这种技术组合在当前企业级应用开发中非常流行,具有开发效率高、性能稳定、易于维护等优势。
2. 系统架构设计
2.1 技术选型分析
后端技术栈:
- Spring Boot 2.7.x:简化了Spring应用的初始搭建和开发过程
- MyBatis-Plus:强大的ORM框架,提供丰富的CRUD接口
- Spring Security:处理认证和授权
- Redis:缓存高频访问数据,如用户会话信息
选择SpringBoot是因为它:
- 内置Tomcat服务器,简化部署
- 自动配置机制减少XML配置
- 丰富的Starter依赖,快速集成常用组件
- 完善的监控端点(Actuator),便于运维
前端技术栈:
- Vue 3.x:响应式前端框架
- Element Plus:UI组件库
- Axios:HTTP客户端
- Vue Router:前端路由管理
- Pinia:状态管理库
Vue.js的优势在于:
- 渐进式框架,学习曲线平缓
- 组件化开发,代码复用率高
- 虚拟DOM技术,性能优异
- 丰富的生态系统和工具链
2.2 数据库设计
系统核心表结构设计遵循第三范式,同时考虑了查询性能:
用户表(users)
sql复制CREATE TABLE `users` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password_hash` varchar(100) NOT NULL COMMENT '加密密码',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`role_type` enum('ADMIN','DOCTOR','NURSE') NOT NULL COMMENT '角色类型',
`department_id` int DEFAULT NULL COMMENT '所属科室',
`is_active` tinyint(1) DEFAULT '1' COMMENT '是否激活',
`last_login` datetime DEFAULT NULL COMMENT '最后登录时间',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
患者表(patients)
sql复制CREATE TABLE `patients` (
`patient_id` bigint NOT NULL AUTO_INCREMENT,
`patient_name` varchar(50) NOT NULL,
`gender` char(1) NOT NULL COMMENT 'M/F',
`birth_date` date DEFAULT NULL,
`id_card` varchar(18) DEFAULT NULL COMMENT '身份证号',
`contact_phone` varchar(20) NOT NULL,
`emergency_contact` varchar(20) DEFAULT NULL,
`blood_type` varchar(10) DEFAULT NULL,
`allergy_history` text COMMENT '过敏史',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`patient_id`),
KEY `idx_id_card` (`id_card`),
KEY `idx_phone` (`contact_phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
药品表(medicines)
sql复制CREATE TABLE `medicines` (
`medicine_id` bigint NOT NULL AUTO_INCREMENT,
`medicine_code` varchar(20) NOT NULL COMMENT '药品编码',
`medicine_name` varchar(100) NOT NULL,
`specification` varchar(50) NOT NULL COMMENT '规格',
`unit` varchar(10) NOT NULL COMMENT '单位',
`manufacturer` varchar(100) DEFAULT NULL,
`batch_number` varchar(50) NOT NULL,
`stock_quantity` int NOT NULL DEFAULT '0',
`price` decimal(10,2) NOT NULL,
`production_date` date NOT NULL,
`expiry_date` date NOT NULL,
`category_id` int DEFAULT NULL COMMENT '药品分类',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`medicine_id`),
UNIQUE KEY `idx_code` (`medicine_code`),
KEY `idx_name` (`medicine_name`),
KEY `idx_expiry` (`expiry_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
数据库设计经验:
- 所有表都添加created_at字段记录创建时间
- 为高频查询字段建立索引
- 使用ENUM类型限定固定取值范围
- 金额字段使用DECIMAL而非FLOAT避免精度问题
3. 核心功能实现
3.1 权限管理系统
系统采用RBAC(基于角色的访问控制)模型,实现精细化的权限管理:
java复制// 权限配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/doctor/**").hasAnyRole("ADMIN", "DOCTOR")
.antMatchers("/api/nurse/**").hasAnyRole("ADMIN", "NURSE")
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}
前端路由也根据角色动态加载:
javascript复制// 动态路由配置
const routes = [
{
path: '/',
component: Layout,
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard'),
meta: { roles: ['ADMIN', 'DOCTOR', 'NURSE'] }
},
{
path: 'patient-mgmt',
component: () => import('@/views/patient/List'),
meta: { roles: ['ADMIN', 'DOCTOR'] }
},
// 更多路由...
]
}
]
router.beforeEach((to, from, next) => {
const userRoles = store.getters.roles
if (to.meta.roles && !to.meta.roles.some(role => userRoles.includes(role))) {
next('/403') // 无权限访问
} else {
next()
}
})
3.2 患者管理模块
患者管理包含CRUD操作和高级查询功能。后端接口示例:
java复制@RestController
@RequestMapping("/api/patients")
public class PatientController {
@Autowired
private PatientService patientService;
@GetMapping
public ResponseEntity<PageResult<PatientVO>> listPatients(
@RequestParam(required = false) String name,
@RequestParam(required = false) String phone,
@RequestParam(required = false) String idCard,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
PatientQuery query = new PatientQuery(name, phone, idCard);
PageResult<PatientVO> result = patientService.queryPatients(query, page, size);
return ResponseEntity.ok(result);
}
@PostMapping
public ResponseEntity<PatientVO> createPatient(@Valid @RequestBody PatientCreateDTO dto) {
PatientVO patient = patientService.createPatient(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(patient);
}
// 其他接口...
}
前端使用Element Plus表格展示数据,并实现分页和筛选:
vue复制<template>
<el-table :data="tableData" v-loading="loading">
<el-table-column prop="patientId" label="ID" width="80" />
<el-table-column prop="patientName" label="姓名" />
<el-table-column prop="gender" label="性别" width="80">
<template #default="{row}">
{{ row.gender === 'M' ? '男' : '女' }}
</template>
</el-table-column>
<el-table-column prop="contactPhone" label="联系电话" />
<el-table-column label="操作" width="180">
<template #default="{row}">
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="query.page"
v-model:page-size="query.size"
:total="total"
@current-change="fetchData"
layout="total, prev, pager, next"
/>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getPatients } from '@/api/patient'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const query = ref({
name: '',
phone: '',
idCard: '',
page: 1,
size: 10
})
const fetchData = async () => {
loading.value = true
try {
const res = await getPatients(query.value)
tableData.value = res.data.list
total.value = res.data.total
} finally {
loading.value = false
}
}
onMounted(() => {
fetchData()
})
</script>
4. 系统部署与运维
4.1 开发环境搭建
-
后端环境:
- JDK 11+
- Maven 3.6+
- MySQL 8.0+
- Redis 6.0+
-
前端环境:
- Node.js 16+
- npm 8+ 或 yarn
配置文件示例(application-dev.yml):
yaml复制server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/hospital_db?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
password:
database: 0
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.2 生产环境部署
推荐使用Docker容器化部署:
docker-compose.yml:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
container_name: hospital-mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: hospital_db
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
restart: always
redis:
image: redis:6.2
container_name: hospital-redis
ports:
- "6379:6379"
restart: always
backend:
build: ./backend
container_name: hospital-backend
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_URL: jdbc:mysql://mysql:3306/hospital_db
DB_USER: root
DB_PASSWORD: ${DB_ROOT_PASSWORD}
REDIS_HOST: redis
depends_on:
- mysql
- redis
restart: always
frontend:
build: ./frontend
container_name: hospital-frontend
ports:
- "80:80"
restart: always
volumes:
mysql_data:
部署注意事项:
- 生产环境务必配置HTTPS
- 数据库定期备份
- 使用Nginx反向代理前端和后端
- 配置日志轮转和监控告警
5. 常见问题与解决方案
5.1 性能优化技巧
-
数据库优化:
- 为常用查询字段添加索引
- 避免SELECT *,只查询需要的字段
- 大数据量表考虑分库分表
-
缓存策略:
java复制@Cacheable(value = "patients", key = "#patientId") public PatientVO getPatientById(Long patientId) { return patientMapper.selectById(patientId); } @CacheEvict(value = "patients", key = "#patientId") public void updatePatient(PatientUpdateDTO dto) { // 更新逻辑 } -
前端性能:
- 使用路由懒加载
- 组件按需引入
- 图片等静态资源使用CDN
5.2 安全防护措施
-
SQL注入防护:
- 使用MyBatis预编译语句
- 对用户输入进行严格校验
-
XSS防护:
javascript复制// 前端过滤危险HTML function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } -
CSRF防护:
- 使用SameSite Cookie属性
- 重要操作需要二次验证
6. 项目扩展方向
-
移动端适配:
- 开发微信小程序版本
- 使用Uniapp跨平台方案
-
数据可视化:
- 集成ECharts展示医疗数据
- 开发院长驾驶舱功能
-
智能预警:
- 药品库存不足预警
- 患者复诊提醒
-
对接医保系统:
- 实现医保结算功能
- 开发电子病历接口
在实际开发过程中,我发现前后端分离架构虽然提高了开发效率,但也带来了接口联调复杂度的增加。建议使用Swagger或YAPI等工具维护API文档,确保前后端开发人员对接口定义的理解一致。