Java医疗系统案例

wen java案例 3

本文目录导读:

Java医疗系统案例

  1. 数据库设计
  2. 后端核心代码
  3. 前端核心代码
  4. 系统配置与部署
  5. 系统特色功能
  6. 部署方案
  7. 系统特色亮点
  8. 后续扩展方向

我来为您设计一个完整的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"

系统特色亮点

  1. 高并发处理:使用Redis缓存号源,防止超卖
  2. 分布式事务:支持多渠道整合使用
  3. 接口安全:JWT认证 + RBAC权限控制
  4. 智能预警:药物库存自动预警提醒
  5. 操作审计:关键操作留痕,责任到人

后续扩展方向

  • 引入消息队列(RabbitMQ)处理异步通知
  • 使用Elasticsearch实现病历全文搜索
  • 引入工作流引擎(Flowable)处理复杂审批流程
  • 对接第三方支付接口
  • 生成微信小程序端挂号功能
  • 实现电子发票功能

这个医疗系统案例涵盖了医院门诊的核心业务流程,包括挂号管理、医生排班、药品管理等模块,具有完整的技术栈和实际应用价值,您可以根据具体需求进行调整和扩展。

抱歉,评论功能暂时关闭!