本文目录导读:

我来为您设计一个完整的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)处理复杂审批流程
- 对接第三方支付接口
- 生成微信小程序端挂号功能
- 实现电子发票功能
这个医疗系统案例涵盖了医院门诊的核心业务流程,包括挂号管理、医生排班、药品管理等模块,具有完整的技术栈和实际应用价值,您可以根据具体需求进行调整和扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播