本文目录导读:

我来为您设计一个完整的Java医疗系统案例,这是一个基于Spring Boot + MyBatis + Vue的医院门诊管理系统。
1 系统架构
前端:Vue3 + Element Plus + Axios
后端:Spring Boot 2.7 + MyBatis Plus + JWT
数据库:MySQL 8.0
工具:Maven, Redis, Swagger
2 核心功能模块
- 患者管理
- 医生管理
- 挂号管理
- 门诊收费
- 药房管理
- 住院管理
- 系统管理
数据库设计
1 核心表结构
-- 患者表
CREATE TABLE patient (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
patient_no VARCHAR(20) UNIQUE COMMENT '病历号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
gender TINYINT COMMENT '性别 0女1男',
age INT,
phone VARCHAR(20),
id_card VARCHAR(18) COMMENT '身份证号',
address VARCHAR(200),
allergy_history TEXT COMMENT '过敏史',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 医生表
CREATE TABLE doctor (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
doctor_no VARCHAR(20) UNIQUE,
name VARCHAR(50) NOT NULL,
department_id BIGINT COMMENT '科室ID',VARCHAR(50) COMMENT '职称',
phone VARCHAR(20),
schedule TEXT COMMENT '排班信息',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 挂号表
CREATE TABLE registration (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
reg_no VARCHAR(30) UNIQUE COMMENT '挂号单号',
patient_id BIGINT,
doctor_id BIGINT,
dept_id BIGINT,
reg_type TINYINT COMMENT '挂号类型 1专家 2普通',
status TINYINT COMMENT '状态 0待就诊 1就诊中 2已完成 3已取消',
reg_time DATETIME,
visit_date DATE,
visit_time VARCHAR(20) COMMENT '就诊时间段',
fee DECIMAL(10,2) COMMENT '挂号费',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 处方表
CREATE TABLE prescription (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
presc_no VARCHAR(30) UNIQUE,
registration_id BIGINT,
patient_id BIGINT,
doctor_id BIGINT,
status TINYINT COMMENT '状态 0待收费 1已收费 2已发药',
total_amount DECIMAL(10,2),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 药品库存表
CREATE TABLE drug_inventory (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
drug_code VARCHAR(20) UNIQUE,
drug_name VARCHAR(100) NOT NULL,
spec VARCHAR(100) COMMENT '规格',
unit VARCHAR(20) COMMENT '单位',
price DECIMAL(10,2),
stock INT COMMENT '库存数量',
warning_line INT COMMENT '预警线',
status TINYINT COMMENT '状态 0停用 1启用'
);
后端核心代码
1 项目结构
com.hospital
├── config
├── controller
├── service
├── mapper
├── entity
├── dto
├── common
└── util
2 Maven依赖 (pom.xml)
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
3 核心实体类
// Patient.java
@Data
@TableName("patient")
public class Patient {
@TableId(type = IdType.AUTO)
private Long id;
private String patientNo;
private String name;
private Integer gender;
private Integer age;
private String phone;
private String idCard;
private String address;
private String allergyHistory;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}
4 挂号Service实现
@Service
@Slf4j
public class RegistrationServiceImpl implements RegistrationService {
@Autowired
private RegistrationMapper registrationMapper;
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 创建挂号单
*/
@Override
@Transactional(rollbackFor = Exception.class)
public RegisterResult createRegistration(RegisterDTO dto) {
// 1. 检查号源是否充足
String key = "doctor:schedule:" + dto.getDoctorId() + ":" + dto.getVisitDate();
String countKey = key + ":count";
Long currentCount = redisTemplate.opsForValue().increment(countKey);
if (currentCount != null && currentCount > 30) { // 假设每天限号30个
throw new BusinessException("该医生号源已满");
}
// 2. 生成挂号单号
String regNo = generateRegNo();
// 3. 创建挂号单
Registration registration = new Registration();
registration.setRegNo(regNo);
registration.setPatientId(dto.getPatientId());
registration.setDoctorId(dto.getDoctorId());
registration.setDeptId(dto.getDeptId());
registration.setRegType(dto.getRegType());
registration.setStatus(0);
registration.setVisitDate(dto.getVisitDate());
registration.setRegTime(new Date());
// 计算挂号费
BigDecimal fee = calcFee(dto.getRegType(), dto.getDoctorId());
registration.setFee(fee);
// 4. 保存挂号信息
registrationMapper.insert(registration);
// 5. 发送通知(异步)
asyncNotify(registration);
return new RegisterResult(regNo, fee);
}
/**
* 取消挂号
*/
@Override
public boolean cancelRegistration(Long regId) {
Registration reg = registrationMapper.selectById(regId);
if (reg == null) {
throw new BusinessException("挂号单不存在");
}
if (reg.getStatus() != 0) {
throw new BusinessException("当前挂号状态不允许取消");
}
// 直接修改状态为已取消
reg.setStatus(3);
registrationMapper.updateById(reg);
// 释放号源
String key = "doctor:schedule:" + reg.getDoctorId() + ":" + reg.getVisitDate();
String countKey = key + ":count";
redisTemplate.opsForValue().decrement(countKey);
return true;
}
private String generateRegNo() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String timeStr = sdf.format(new Date());
int randomNum = (int)(Math.random() * 900) + 100;
return "REG" + timeStr + randomNum;
}
private BigDecimal calcFee(Integer regType, Long doctorId) {
// 专家号100,普通号20
return regType == 1 ? new BigDecimal("100") : new BigDecimal("20");
}
}
5 门诊收费Service
@Service
@Slf4j
public class OutpatientChargeService {
@Autowired
private PrescriptionMapper prescriptionMapper;
@Autowired
private DrugInventoryMapper drugInventoryMapper;
/**
* 收费结算
*/
@Transactional(rollbackFor = Exception.class)
public ChargeResult charge(ChargeDTO dto) {
// 1. 查到处方信息
Prescription presc = prescriptionMapper.selectById(dto.getPrescriptionId());
if (presc == null) {
throw new BusinessException("处方不存在");
}
if (presc.getStatus() != 0) {
throw new BusinessException("处方不是待收费状态");
}
// 2. 计算总金额
List<PrescriptionItem> items = prescriptionItemMapper
.selectList(new LambdaQueryWrapper<PrescriptionItem>()
.eq(PrescriptionItem::getPrescriptionId, presc.getId()));
BigDecimal totalAmount = BigDecimal.ZERO;
for (PrescriptionItem item : items) {
DrugInventory drug = drugInventoryMapper.selectByCode(item.getDrugCode());
BigDecimal amount = drug.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()));
totalAmount = totalAmount.add(amount);
}
// 3. 创建收费单
ChargeRecord record = new ChargeRecord();
record.setChargeNo(generateChargeNo());
record.setPrescriptionId(presc.getId());
record.setAmount(totalAmount);
record.setChargeType(dto.getChargeType());
record.setCreateTime(new Date());
chargeRecordMapper.insert(record);
// 4. 更新处方状态为已收费
presc.setStatus(1);
presc.setTotalAmount(totalAmount);
prescriptionMapper.updateById(presc);
// 5. 扣减药品库存
for (PrescriptionItem item : items) {
drugInventoryMapper.decreaseStock(item.getDrugCode(), item.getQuantity());
// 检查库存预警
checkDrugWarning(item.getDrugCode());
}
return new ChargeResult(record.getChargeNo(), totalAmount);
}
/**
* 药品库存预警
*/
private void checkDrugWarning(String drugCode) {
DrugInventory drug = drugInventoryMapper.selectByCode(drugCode);
if (drug.getStock() <= drug.getWarningLine()) {
// 发送预警通知
log.warn("药品{}库存不足,当前库存: {}", drug.getDrugName(), drug.getStock());
// 可加入消息队列通知采购部门
}
}
}
前端核心代码
1 Vue组件 - 门诊挂号
<template>
<div class="registration-page">
<el-card>
<template #header>
<span>门诊挂号</span>
</template>
<el-form :model="regForm" :rules="rules" ref="regFormRef" label-width="100px">
<el-form-item label="患者信息" prop="patientId">
<el-row :gutter="10">
<el-col :span="8">
<el-input
v-model="searchKey"
placeholder="输入手机号/病历号搜索"
@keyup.enter="searchPatient"
clearable>
</el-input>
</el-col>
<el-col :span="4">
<el-button @click="searchPatient" type="primary">搜索</el-button>
</el-col>
<el-col :span="12">
<el-select
v-model="regForm.patientId"
placeholder="选择患者"
filterable
style="width: 100%">
<el-option
v-for="patient in patientList"
:key="patient.id"
:label="patient.name + ' (' + patient.patientNo + ')'"
:value="patient.id">
</el-option>
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="就诊科室" prop="deptId">
<el-select v-model="regForm.deptId" @change="deptChange" placeholder="选择科室" style="width: 100%">
<el-option
v-for="dept in deptList"
:key="dept.id"
:label="dept.name"
:value="dept.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="选择医生" prop="doctorId">
<el-select v-model="regForm.doctorId" placeholder="选择医生" style="width: 100%">
<el-option
v-for="doctor in doctorList"
:key="doctor.id"
:label="doctor.name + ' (' + doctor.title + ')'"
:value="doctor.id">
<el-badge v-if="getDoctorRemain(doctor.id) <= 5"
:value="'仅剩' + getDoctorRemain(doctor.id) + '号'"
type="warning">
</el-badge>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="就诊日期" prop="visitDate">
<el-date-picker
v-model="regForm.visitDate"
type="date"
placeholder="选择日期"
:disabled-date="disabledDate"
value-format="YYYY-MM-DD"
style="width: 100%">
</el-date-picker>
</el-form-item>
<el-form-item label="挂号类型" prop="regType">
<el-radio-group v-model="regForm.regType">
<el-radio :value="2">普通号 (¥20)</el-radio>
<el-radio :value="1">专家号 (¥100)</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitReg" :loading="submitting">
确认挂号
</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 挂号成功提示 -->
<el-dialog v-model="successDialog" title="挂号成功" width="400px">
<el-result icon="success" title="挂号成功">
<template #sub-title>
<p>挂号单号: <el-tag>{{ regNo }}</el-tag></p>
<p>挂号费: ¥{{ fee }}</p>
</template>
</el-result>
<template #footer>
<el-button @click="successDialog = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { reactive, ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import axios from 'axios'
export default {
name: 'OutpatientRegistration',
setup() {
// 表单数据
const regFormRef = ref(null)
const regForm = reactive({
patientId: null,
doctorId: null,
deptId: null,
visitDate: '',
regType: 2
})
// 数据列表
const patientList = ref([])
const deptList = ref([])
const doctorList = ref([])
const searchKey = ref('')
const submitting = ref(false)
const successDialog = ref(false)
const regNo = ref('')
const fee = ref(0)
// 当前医生的号源剩余情况
const doctorRemain = ref({})
// 表单验证规则
const rules = {
patientId: [{ required: true, message: '请选择患者', trigger: 'change' }],
deptId: [{ required: true, message: '请选择科室', trigger: 'change' }],
doctorId: [{ required: true, message: '请选择医生', trigger: 'change' }],
visitDate: [{ required: true, message: '请选择就诊日期', trigger: 'change' }]
}
// 获取科室列表
const getDeptList = async () => {
const res = await axios.get('/api/dept/list')
deptList.value = res.data
}
// 科室改变时获取医生
const deptChange = async (deptId) => {
const res = await axios.get(`/api/doctor/list?deptId=${deptId}`)
doctorList.value = res.data
// 获取每个医生的剩余号源
for (const doctor of doctorList.value) {
if (regForm.visitDate) {
const remain = await getDoctorRemainFromApi(doctor.id, regForm.visitDate)
doctorRemain.value[doctor.id] = remain
}
}
}
// 搜索患者
const searchPatient = async () => {
if (!searchKey.value) {
ElMessage.warning('请输入搜索关键词')
return
}
const res = await axios.get(`/api/patient/search?key=${searchKey.value}`)
patientList.value = res.data
}
// 获取医生剩余号源
const getDoctorRemainFromApi = async (doctorId, date) => {
const res = await axios.get(`/api/registration/remain`, {
params: { doctorId, date }
})
return res.data
}
const getDoctorRemain = (doctorId) => {
return doctorRemain.value[doctorId] || 30
}
// 禁用过去的日期
const disabledDate = (date) => {
return date.getTime() < Date.now() - 86400000
}
// 提交挂号
const submitReg = async () => {
await regFormRef.value.validate()
submitting.value = true
try {
const res = await axios.post('/api/registration/create', regForm)
regNo.value = res.data.regNo
fee.value = res.data.fee
successDialog.value = true
resetForm()
} catch (error) {
ElMessage.error(error.response?.data?.message || '挂号失败')
} finally {
submitting.value = false
}
}
// 重置表单
const resetForm = () => {
regFormRef.value.resetFields()
regForm.regType = 2
}
onMounted(() => {
getDeptList()
})
return {
regFormRef,
regForm,
rules,
deptList,
doctorList,
patientList,
searchKey,
submitting,
successDialog,
regNo,
fee,
doctorRemain,
searchPatient,
deptChange,
getDoctorRemain,
disabledDate,
submitReg,
resetForm
}
}
}
</script>
<style scoped>
.registration-page {
padding: 20px;
min-height: calc(100vh - 120px);
}
.el-card {
max-width: 800px;
margin: 0 auto;
}
.el-badge {
margin-left: 8px;
}
</style>
2 药房管理页面
<template>
<div class="pharmacy-page">
<el-card>
<!-- 药房概览 -->
<el-row :gutter="20" class="stat-cards">
<el-col :span="6">
<el-card shadow="hover">
<div class="stat-item">
<div class="stat-label">处方总数</div>
<div class="stat-value">{{ stats.totalPrescriptions }}</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="stat-item">
<div class="stat-label">待发药</div>
<div class="stat-value">{{ stats.waitingDispense }}</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="stat-item">
<div class="stat-label">库存预警</div>
<div class="stat-value" style="color: #f56c6c">{{ stats.lowStock }}</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="hover">
<div class="stat-item">
<div class="stat-label">日均销售额</div>
<div class="stat-value">{{ stats.dailySales }}元</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 药品库存管理 -->
<el-tabs v-model="activeTab">
<el-tab-pane label="待发药" name="dispensing">
<el-table :data="dispensingList" stripe>
<el-table-column prop="prescNo" label="处方号" width="160"></el-table-column>
<el-table-column prop="patientName" label="患者姓名" width="100"></el-table-column>
<el-table-column prop="drugName" label="药品名称" width="120"></el-table-column>
<el-table-column prop="quantity" label="数量" width="80"></el-table-column>
<el-table-column prop="totalAmount" label="金额" width="100">
<template #default="{ row }">¥{{ row.totalAmount }}</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="120">
<template #default="{ row }">
<el-button
size="small"
type="success"
@click="dispenseDrug(row)">
发药
</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="药品库存" name="inventory">
<div class="toolbar">
<el-input
v-model="searchDrug"
placeholder="搜索药品"
prefix-icon="Search"
clearable
style="width: 300px"/>
<el-button type="primary" @click="showAddDrug">
新增药品
</el-button>
</div>
<el-table :data="drugList" stripe>
<el-table-column prop="drugName" label="药品名称" width="120"></el-table-column>
<el-table-column prop="spec" label="规格" width="120"></el-table-column>
<el-table-column prop="price" label="单价" width="80">
<template #default="{ row }">¥{{ row.price }}</template>
</el-table-column>
<el-table-column prop="stock" label="库存" width="80" sortable>
<template #default="{ row }">
<el-tag :type="row.stock <= row.warningLine ? 'danger' : 'success'">
{{ row.stock }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button size="small" @click="updateStock(row)">补货</el-button>
<el-button size="small" type="danger" @click="deleteDrug(row)">下架</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 药品补货对话框 -->
<el-dialog v-model="stockDialog" title="药品补货" width="400px">
<el-form :model="stockForm" label-width="80px">
<el-form-item label="药品名称">
<el-input v-model="stockForm.drugName" disabled></el-input>
</el-form-item>
<el-form-item label="补货数量">
<el-input-number
v-model="stockForm.quantity"
:min="1"
:max="1000"/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="stockDialog = false">取消</el-button>
<el-button type="primary" @click="submitStock">确认补货</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import axios from 'axios'
export default {
name: 'PharmacyManagement',
setup() {
const activeTab = ref('dispensing')
const dispensingList = ref([])
const drugList = ref([])
const searchDrug = ref('')
const stats = reactive({
totalPrescriptions: 0,
waitingDispense: 0,
lowStock: 0,
dailySales: 0
})
// 入库管理
const stockDialog = ref(false)
const stockForm = reactive({
drugId: null,
drugName: '',
quantity: 1
})
// 获取药房统计数据
const getStats = async () => {
const res = await axios.get('/api/pharmacy/stats')
Object.assign(stats, res.data)
}
// 获取待发药处方
const getDispensingList = async () => {
const res = await axios.get('/api/pharmacy/dispensing-list')
dispensingList.value = res.data
}
// 获取药品库存列表
const getDrugList = async () => {
const res = await axios.get('/api/drug/list', {
params: { search: searchDrug.value }
})
drugList.value = res.data
}
// 发药操作
const dispenseDrug = async (row) => {
try {
await ElMessageBox.confirm(
`确认发放处方 ${row.prescNo} 的药品吗?`,
'发药确认',
{ type: 'warning' }
)
await axios.post(`/api/prescription/${row.id}/dispense`)
ElMessage.success('发药成功')
getDispensingList()
getStats()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error(error.response?.data?.message || '发药失败')
}
}
}
// 打开补货对话框
const updateStock = (row) => {
stockForm.drugId = row.id
stockForm.drugName = row.drugName
stockForm.quantity = 1
stockDialog.value = true
}
// 提交补货
const submitStock = async () => {
try {
await axios.post('/api/drug/stock', { ...stockForm })
ElMessage.success('补货成功')
stockDialog.value = false
getDrugList()
} catch (error) {
ElMessage.error(error.response?.data?.message || '补货失败')
}
}
// 药品下架
const deleteDrug = async (row) => {
try {
await ElMessageBox.confirm(`确认下架药品 ${row.drugName} 吗?`, '下架确认')
await axios.delete(`/api/drug/${row.id}`)
ElMessage.success('下架成功')
getDrugList()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('下架失败')
}
}
}
const showAddDrug = () => {
// 跳转到新增药品页面
// router.push('/drug/add')
ElMessage.info('可跳转到新增药品页面')
}
onMounted(() => {
getStats()
getDispensingList()
getDrugList()
})
return {
activeTab,
dispensingList,
drugList,
searchDrug,
stats,
stockDialog,
stockForm,
dispenseDrug,
updateStock,
submitStock,
deleteDrug,
showAddDrug
}
}
}
</script>
<style scoped>
.pharmacy-page {
padding: 20px;
}
.stat-cards {
margin-bottom: 20px;
}
.stat-item {
text-align: center;
padding: 20px 0;
}
.stat-label {
color: #999;
font-size: 14px;
margin-bottom: 10px;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #333;
}
.toolbar {
margin-bottom: 15px;
display: flex;
justify-content: space-between;
}
</style>
系统配置与部署
1 application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/hospital_system
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
global-config:
db-config:
id-type: auto
jwt:
secret: hospital-secret-key
expire: 7200000 # 2小时
logging:
level:
com.hospital: debug
2 安全配置 - JWT拦截器
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 判断是否为预检请求
if (request.getMethod().equals("OPTIONS")) {
return true;
}
// 获取token
String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token)) {
throw new BusinessException("未登录或登录已过期");
}
// 验证token(从Redis中验证)
String userId = redisTemplate.opsForValue().get("token:" + token);
if (userId == null) {
throw new BusinessException("登录已过期,请重新登录");
}
// 设置用户信息到请求上下文
UserContext.setUserId(Long.parseLong(userId));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
UserContext.clear();
}
}
3 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
log.warn("业务异常: {}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系统异常", e);
return Result.error(500, "系统繁忙,请稍后重试");
}
}
系统特色功能
1 智能排班系统
- 支持医生排班管理
- 自动生成排班表
- 智能提醒功能
2 电子病历管理
- 结构化病历模板
- 历史病历查询
- 病历打印导出
3 数据统计分析
设计仪表板展示关键指标:
- 患者就诊趋势
- 科室收益分析
- 药品消耗统计
- 医生绩效评估
部署方案
Docker部署
# 后端Dockerfile FROM openjdk:11-jre-slim WORKDIR /app COPY target/hospital-system.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]
Docker Compose
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: hospital_system
ports:
- "3306:3306"
redis:
image: redis:6.0
app:
build: .
depends_on:
- mysql
- redis
ports:
- "8080:8080"
系统特色亮点
- 高并发处理:使用Redis缓存号源,防止超卖
- 分布式事务:支持多渠道整合使用
- 接口安全:JWT认证 + RBAC权限控制
- 智能预警:药物库存自动预警提醒
- 操作审计:关键操作留痕,责任到人
后续扩展方向
- 引入消息队列(RabbitMQ)处理异步通知
- 使用Elasticsearch实现病历全文搜索
- 引入工作流引擎(Flowable)处理复杂审批流程
- 对接第三方支付接口
- 生成微信小程序端挂号功能
- 实现电子发票功能
这个医疗系统案例涵盖了医院门诊的核心业务流程,包括挂号管理、医生排班、药品管理等模块,具有完整的技术栈和实际应用价值,您可以根据具体需求进行调整和扩展。