当前位置: 首页 > article >正文

Java全栈开发:宠物医院管理系统项目实战

Java全栈开发:宠物医院管理系统项目实战

项目介绍

本文将介绍一个基于Spring Boot + Vue.js的宠物医院管理系统的开发过程。该系统主要用于帮助宠物医院管理日常运营,包括患者管理、预约挂号、处方开具等功能。

技术栈

后端技术

  • Spring Boot 2.7.x
  • Spring Security
  • MyBatis Plus
  • MySQL 8.0
  • Redis
  • JWT

前端技术

  • Vue 3
  • Element Plus
  • Axios
  • Vuex
  • Vue Router

核心功能模块

1. 用户管理模块

  • 用户注册与登录
  • 角色权限管理
  • 个人信息维护

2. 宠物档案管理

@Data
@TableName("pet_info")
public class Pet {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String species;
    private Integer age;
    private String gender;
    private Long ownerId;
    private String medicalHistory;
    // ...其他字段
}

3. 预约挂号模块

@Service
public class AppointmentService {
    @Autowired
    private AppointmentMapper appointmentMapper;
    
    public boolean createAppointment(AppointmentDTO dto) {
        // 检查医生排班情况
        if(!checkDoctorAvailable(dto.getDoctorId(), dto.getAppointmentTime())) {
            throw new BusinessException("医生当前时段已约满");
        }
        // 创建预约记录
        return appointmentMapper.insert(dto) > 0;
    }
}

4. 诊疗管理模块

@RestController
@RequestMapping("/api/treatment")
public class TreatmentController {
    @PostMapping("/prescription")
    public Result createPrescription(@RequestBody PrescriptionDTO dto) {
        // 处理处方信息
        prescriptionService.create(dto);
        return Result.success();
    }
}

5. 库存管理模块

@Service
public class InventoryService {
    public void updateStock(Long medicineId, Integer quantity) {
        // 检查库存
        Medicine medicine = medicineMapper.selectById(medicineId);
        if(medicine.getStock() < quantity) {
            throw new BusinessException("库存不足");
        }
        // 更新库存
        medicine.setStock(medicine.getStock() - quantity);
        medicineMapper.updateById(medicine);
    }
}

数据库设计

主要数据表

  1. 用户表(user)
  2. 宠物信息表(pet_info)
  3. 预约记录表(appointment)
  4. 诊疗记录表(treatment)
  5. 处方表(prescription)
  6. 药品库存表(medicine_inventory)

系统架构

整体架构

pet-hospital/
├── pet-hospital-backend/    # 后端项目
│   ├── common/             # 公共模块
│   ├── system/            # 系统模块
│   └── business/          # 业务模块
└── pet-hospital-frontend/   # 前端项目
    ├── src/
    ├── public/
    └── package.json

关键技术要点

1. 权限控制

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager()));
    }
}

2. 缓存处理

@Service
public class PetService {
    @Cacheable(value = "pet", key = "#id")
    public PetVO getPetInfo(Long id) {
        return petMapper.selectVOById(id);
    }
}

3. 异步处理

@Service
public class NotificationService {
    @Async
    public void sendAppointmentReminder(AppointmentDTO dto) {
        // 发送预约提醒
        // 可以是短信、邮件等
    }
}

部署方案

  1. 后端服务部署

    • 使用Docker容器化部署
    • Nginx反向代理
    • Redis集群
  2. 前端部署

    • Nginx托管静态资源
    • CDN加速

项目亮点

  1. 采用前后端分离架构,提高开发效率
  2. 使用Redis缓存提升系统性能
  3. 实现了完整的权限控制体系
  4. 支持异步处理大量并发请求
  5. 良好的异常处理机制

总结

本项目采用主流的Java全栈技术栈,实现了一个功能完整的宠物医院管理系统。通过这个项目,不仅可以学习到完整的全栈开发流程,还能掌握项目开发中的各种最佳实践。

后续优化方向

  1. 引入微服务架构
  2. 添加更多的数据分析功能
  3. 优化系统性能
  4. 增加移动端适配
  5. 引入人工智能辅助诊断

宠物医院预约挂号模块详细设计

一、数据库设计

1. 预约表(appointment)

CREATE TABLE `appointment` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `pet_id` bigint NOT NULL COMMENT '宠物ID',
  `doctor_id` bigint NOT NULL COMMENT '医生ID',
  `appointment_time` datetime NOT NULL COMMENT '预约时间',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待确认 1-已确认 2-已完成 3-已取消',
  `symptom_desc` varchar(500) COMMENT '症状描述',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_pet_id` (`pet_id`),
  KEY `idx_doctor_id` (`doctor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 医生排班表(doctor_schedule)

CREATE TABLE `doctor_schedule` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `doctor_id` bigint NOT NULL COMMENT '医生ID',
  `work_date` date NOT NULL COMMENT '工作日期',
  `period` tinyint NOT NULL COMMENT '时段:1-上午 2-下午',
  `max_appointments` int NOT NULL COMMENT '最大预约数',
  `current_appointments` int NOT NULL DEFAULT '0' COMMENT '当前预约数',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_doctor_date_period` (`doctor_id`,`work_date`,`period`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、后端实现

1. 实体类

@Data
@TableName("appointment")
public class Appointment {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long petId;
    private Long doctorId;
    private LocalDateTime appointmentTime;
    private Integer status;
    private String symptomDesc;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

@Data
@TableName("doctor_schedule")
public class DoctorSchedule {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long doctorId;
    private LocalDate workDate;
    private Integer period;
    private Integer maxAppointments;
    private Integer currentAppointments;
}

2. Service层实现

@Service
@Slf4j
public class AppointmentService {
    @Autowired
    private AppointmentMapper appointmentMapper;
    @Autowired
    private DoctorScheduleMapper scheduleMapper;
    
    @Transactional
    public Result createAppointment(AppointmentDTO dto) {
        // 1. 检查医生排班
        DoctorSchedule schedule = checkDoctorSchedule(dto);
        if (schedule == null) {
            return Result.error("医生当天未排班");
        }
        
        // 2. 检查预约数量
        if (schedule.getCurrentAppointments() >= schedule.getMaxAppointments()) {
            return Result.error("该时段预约已满");
        }
        
        // 3. 创建预约记录
        Appointment appointment = new Appointment();
        BeanUtils.copyProperties(dto, appointment);
        appointment.setStatus(AppointmentStatus.PENDING.getCode());
        
        // 4. 更新排班预约数
        schedule.setCurrentAppointments(schedule.getCurrentAppointments() + 1);
        
        // 5. 保存数据
        try {
            appointmentMapper.insert(appointment);
            scheduleMapper.updateById(schedule);
            
            // 6. 异步发送通知
            sendNotification(appointment);
            
            return Result.success(appointment);
        } catch (Exception e) {
            log.error("创建预约失败", e);
            throw new BusinessException("预约失败,请稍后重试");
        }
    }
    
    @Async
    private void sendNotification(Appointment appointment) {
        // 发送短信通知
        // 发送微信通知
        // 发送邮件通知
    }
}

3. Controller层实现

@RestController
@RequestMapping("/api/appointment")
public class AppointmentController {
    @Autowired
    private AppointmentService appointmentService;
    
    @PostMapping("/create")
    public Result create(@RequestBody @Validated AppointmentDTO dto) {
        return appointmentService.createAppointment(dto);
    }
    
    @GetMapping("/list")
    public Result list(@RequestParam Long petId) {
        return appointmentService.getAppointmentsByPetId(petId);
    }
    
    @PostMapping("/cancel/{id}")
    public Result cancel(@PathVariable Long id) {
        return appointmentService.cancelAppointment(id);
    }
}

三、前端实现

1. 预约表单组件

<template>
  <el-form :model="form" :rules="rules" ref="appointmentForm">
    <el-form-item label="宠物信息" prop="petId">
      <el-select v-model="form.petId" placeholder="请选择宠物">
        <el-option
          v-for="pet in petList"
          :key="pet.id"
          :label="pet.name"
          :value="pet.id"
        />
      </el-select>
    </el-form-item>
    
    <el-form-item label="预约日期" prop="appointmentDate">
      <el-date-picker
        v-model="form.appointmentDate"
        type="date"
        placeholder="选择日期"
        :disabled-date="disabledDate"
        @change="handleDateChange"
      />
    </el-form-item>
    
    <el-form-item label="预约时段" prop="period">
      <el-radio-group v-model="form.period">
        <el-radio :label="1">上午</el-radio>
        <el-radio :label="2">下午</el-radio>
      </el-radio-group>
    </el-form-item>
    
    <el-form-item label="选择医生" prop="doctorId">
      <el-select 
        v-model="form.doctorId" 
        placeholder="请选择医生"
        :loading="doctorLoading"
      >
        <el-option
          v-for="doctor in availableDoctors"
          :key="doctor.id"
          :label="doctor.name"
          :value="doctor.id"
        >
          <span>{{ doctor.name }}</span>
          <span style="float: right; color: #8492a6; font-size: 13px">
            剩余号源: {{ doctor.remainingQuota }}
          </span>
        </el-option>
      </el-select>
    </el-form-item>
    
    <el-form-item label="症状描述" prop="symptomDesc">
      <el-input
        type="textarea"
        v-model="form.symptomDesc"
        :rows="3"
        placeholder="请描述宠物症状"
      />
    </el-form-item>
    
    <el-form-item>
      <el-button type="primary" @click="submitForm">提交预约</el-button>
      <el-button @click="resetForm">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      form: {
        petId: '',
        appointmentDate: '',
        period: 1,
        doctorId: '',
        symptomDesc: ''
      },
      rules: {
        petId: [{ required: true, message: '请选择宠物', trigger: 'change' }],
        appointmentDate: [{ required: true, message: '请选择预约日期', trigger: 'change' }],
        doctorId: [{ required: true, message: '请选择医生', trigger: 'change' }],
        symptomDesc: [{ required: true, message: '请描述症状', trigger: 'blur' }]
      },
      petList: [],
      availableDoctors: [],
      doctorLoading: false
    }
  },
  methods: {
    async submitForm() {
      try {
        await this.$refs.appointmentForm.validate()
        const res = await this.createAppointment(this.form)
        if (res.success) {
          this.$message.success('预约成功')
          this.resetForm()
        }
      } catch (error) {
        this.$message.error(error.message || '预约失败')
      }
    }
  }
}
</script>

2. 预约列表组件

<template>
  <div class="appointment-list">
    <el-table :data="appointments" style="width: 100%">
      <el-table-column prop="appointmentTime" label="预约时间" />
      <el-table-column prop="doctorName" label="医生" />
      <el-table-column prop="status" label="状态">
        <template #default="scope">
          <el-tag :type="getStatusType(scope.row.status)">
            {{ getStatusText(scope.row.status) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作">
        <template #default="scope">
          <el-button 
            v-if="scope.row.status === 0"
            type="danger" 
            size="small" 
            @click="cancelAppointment(scope.row.id)"
          >
            取消预约
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

四、业务流程

  1. 用户选择预约日期和时段
  2. 系统查询可预约医生列表
  3. 用户选择医生并填写症状描述
  4. 提交预约信息
  5. 系统校验并创建预约记录
  6. 发送预约确认通知

五、其他功能

1. 预约提醒

@Component
@Slf4j
public class AppointmentReminder {
    @Scheduled(cron = "0 0 20 * * ?")  // 每天晚上8点执行
    public void sendReminders() {
        // 查询明天的预约
        List<Appointment> tomorrowAppointments = appointmentMapper.findTomorrowAppointments();
        
        // 发送提醒
        for (Appointment appointment : tomorrowAppointments) {
            try {
                notificationService.sendReminder(appointment);
            } catch (Exception e) {
                log.error("发送预约提醒失败", e);
            }
        }
    }
}

2. 预约统计

@Service
public class AppointmentStatisticsService {
    public Map<String, Object> getStatistics(LocalDate startDate, LocalDate endDate) {
        Map<String, Object> statistics = new HashMap<>();
        
        // 总预约数
        statistics.put("totalAppointments", appointmentMapper.countByDateRange(startDate, endDate));
        
        // 各状态预约数量
        statistics.put("statusDistribution", appointmentMapper.groupByStatus(startDate, endDate));
        
        // 医生预约排名
        statistics.put("doctorRanking", appointmentMapper.getDoctorRanking(startDate, endDate));
        
        return statistics;
    }
}

六、注意事项

  1. 并发控制:使用数据库乐观锁或Redis分布式锁
  2. 事务管理:确保预约创建和排班更新的原子性
  3. 异常处理:完善的异常处理机制
  4. 参数校验:前后端都需要进行参数验证
  5. 性能优化:合理使用缓存,避免频繁查询

七、扩展功能

  1. 在线支付
  2. 智能推荐医生
  3. 预约变更
  4. 就诊提醒
  5. 预约评价

这样的预约挂号模块设计,既考虑了基本功能的完整性,又包含了性能优化和业务扩展的考虑,可以满足大多数宠物医院的需求。

宠物医院库存管理模块详细设计

一、数据库设计

1. 药品信息表(medicine)

CREATE TABLE `medicine` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `code` varchar(32) NOT NULL COMMENT '药品编码',
  `name` varchar(100) NOT NULL COMMENT '药品名称',
  `specification` varchar(50) NOT NULL COMMENT '规格',
  `unit` varchar(20) NOT NULL COMMENT '单位',
  `category_id` bigint NOT NULL COMMENT '分类ID',
  `manufacturer` varchar(100) COMMENT '生产厂家',
  `shelf_life` int COMMENT '保质期(月)',
  `storage_condition` varchar(50) COMMENT '存储条件',
  `retail_price` decimal(10,2) NOT NULL COMMENT '零售价',
  `purchase_price` decimal(10,2) NOT NULL COMMENT '采购价',
  `min_stock` int NOT NULL COMMENT '最低库存',
  `max_stock` int NOT NULL COMMENT '最高库存',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-停用 1-启用',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. 库存记录表(inventory)

CREATE TABLE `inventory` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `medicine_id` bigint NOT NULL COMMENT '药品ID',
  `batch_no` varchar(32) NOT NULL COMMENT '批次号',
  `quantity` int NOT NULL COMMENT '数量',
  `production_date` date COMMENT '生产日期',
  `expiry_date` date COMMENT '有效期',
  `warehouse_id` bigint NOT NULL COMMENT '仓库ID',
  `position` varchar(50) COMMENT '货架位置',
  PRIMARY KEY (`id`),
  KEY `idx_medicine_id` (`medicine_id`),
  KEY `idx_batch_no` (`batch_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3. 出入库记录表(stock_record)

CREATE TABLE `stock_record` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `record_no` varchar(32) NOT NULL COMMENT '单据编号',
  `type` tinyint NOT NULL COMMENT '类型:1-入库 2-出库 3-盘点 4-报损',
  `medicine_id` bigint NOT NULL COMMENT '药品ID',
  `batch_no` varchar(32) NOT NULL COMMENT '批次号',
  `quantity` int NOT NULL COMMENT '数量',
  `operator_id` bigint NOT NULL COMMENT '操作人ID',
  `operation_time` datetime NOT NULL COMMENT '操作时间',
  `remark` varchar(200) COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_record_no` (`record_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、后端实现

1. 实体类

@Data
@TableName("medicine")
public class Medicine {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String code;
    private String name;
    private String specification;
    private String unit;
    private Long categoryId;
    private String manufacturer;
    private Integer shelfLife;
    private String storageCondition;
    private BigDecimal retailPrice;
    private BigDecimal purchasePrice;
    private Integer minStock;
    private Integer maxStock;
    private Integer status;
}

@Data
@TableName("inventory")
public class Inventory {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long medicineId;
    private String batchNo;
    private Integer quantity;
    private LocalDate productionDate;
    private LocalDate expiryDate;
    private Long warehouseId;
    private String position;
}

2. Service层实现

@Service
@Slf4j
public class InventoryService {
    @Autowired
    private InventoryMapper inventoryMapper;
    @Autowired
    private StockRecordMapper stockRecordMapper;
    
    @Transactional
    public Result inStock(InStockDTO dto) {
        // 1. 验证药品信息
        Medicine medicine = validateMedicine(dto.getMedicineId());
        
        // 2. 检查批次是否存在
        Inventory inventory = inventoryMapper.selectByMedicineAndBatch(
            dto.getMedicineId(), dto.getBatchNo());
            
        if (inventory != null) {
            // 更新现有库存
            inventory.setQuantity(inventory.getQuantity() + dto.getQuantity());
            inventoryMapper.updateById(inventory);
        } else {
            // 创建新库存记录
            inventory = new Inventory();
            BeanUtils.copyProperties(dto, inventory);
            inventoryMapper.insert(inventory);
        }
        
        // 3. 创建入库记录
        createStockRecord(StockRecordType.IN, dto);
        
        // 4. 检查是否超过最大库存
        checkMaxStock(medicine, inventory);
        
        return Result.success();
    }
    
    @Transactional
    public Result outStock(OutStockDTO dto) {
        // 1. 检查库存是否充足
        Inventory inventory = checkStock(dto.getMedicineId(), dto.getBatchNo(), dto.getQuantity());
        
        // 2. 扣减库存
        inventory.setQuantity(inventory.getQuantity() - dto.getQuantity());
        inventoryMapper.updateById(inventory);
        
        // 3. 创建出库记录
        createStockRecord(StockRecordType.OUT, dto);
        
        // 4. 检查是否达到最低库存警戒线
        checkMinStock(inventory);
        
        return Result.success();
    }
    
    @Transactional
    public Result stockTaking(StockTakingDTO dto) {
        // 盘点处理逻辑
        Inventory inventory = inventoryMapper.selectById(dto.getInventoryId());
        int difference = dto.getActualQuantity() - inventory.getQuantity();
        
        // 更新库存
        inventory.setQuantity(dto.getActualQuantity());
        inventoryMapper.updateById(inventory);
        
        // 创建盘点记录
        createStockTakingRecord(inventory, difference, dto.getRemark());
        
        return Result.success();
    }
}

3. Controller层实现

@RestController
@RequestMapping("/api/inventory")
public class InventoryController {
    @Autowired
    private InventoryService inventoryService;
    
    @PostMapping("/in")
    public Result inStock(@RequestBody @Validated InStockDTO dto) {
        return inventoryService.inStock(dto);
    }
    
    @PostMapping("/out")
    public Result outStock(@RequestBody @Validated OutStockDTO dto) {
        return inventoryService.outStock(dto);
    }
    
    @GetMapping("/list")
    public Result list(InventoryQueryDTO query) {
        return inventoryService.queryInventory(query);
    }
    
    @PostMapping("/stockTaking")
    public Result stockTaking(@RequestBody @Validated StockTakingDTO dto) {
        return inventoryService.stockTaking(dto);
    }
}

三、前端实现

1. 库存管理主页面

<template>
  <div class="inventory-management">
    <!-- 搜索栏 -->
    <el-form :inline="true" :model="queryForm" class="search-form">
      <el-form-item label="药品名称">
        <el-input v-model="queryForm.medicineName" placeholder="请输入药品名称" />
      </el-form-item>
      <el-form-item label="库存状态">
        <el-select v-model="queryForm.stockStatus" placeholder="请选择">
          <el-option label="正常" value="normal" />
          <el-option label="低库存" value="low" />
          <el-option label="超储" value="high" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleQuery">查询</el-button>
        <el-button @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <!-- 操作按钮 -->
    <div class="operation-buttons">
      <el-button type="primary" @click="handleInStock">入库</el-button>
      <el-button type="warning" @click="handleOutStock">出库</el-button>
      <el-button type="info" @click="handleStockTaking">盘点</el-button>
    </div>

    <!-- 库存列表 -->
    <el-table :data="inventoryList" border style="width: 100%">
      <el-table-column prop="medicineName" label="药品名称" />
      <el-table-column prop="specification" label="规格" />
      <el-table-column prop="batchNo" label="批次号" />
      <el-table-column prop="quantity" label="库存数量" />
      <el-table-column prop="expiryDate" label="有效期" />
      <el-table-column label="库存状态">
        <template #default="scope">
          <el-tag :type="getStockStatusType(scope.row)">
            {{ getStockStatusText(scope.row) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作" width="200">
        <template #default="scope">
          <el-button size="small" @click="handleDetail(scope.row)">详情</el-button>
          <el-button 
            size="small" 
            type="danger" 
            @click="handleWarn(scope.row)"
            v-if="isNearExpiry(scope.row)"
          >
            临期警告
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      queryForm: {
        medicineName: '',
        stockStatus: ''
      },
      inventoryList: []
    }
  },
  methods: {
    async handleQuery() {
      try {
        const res = await this.queryInventory(this.queryForm)
        this.inventoryList = res.data
      } catch (error) {
        this.$message.error('查询失败')
      }
    },
    
    getStockStatusType(row) {
      if (row.quantity <= row.minStock) return 'danger'
      if (row.quantity >= row.maxStock) return 'warning'
      return 'success'
    }
  }
}
</script>

2. 入库表单组件

<template>
  <el-dialog title="药品入库" v-model="visible">
    <el-form :model="form" :rules="rules" ref="inStockForm" label-width="100px">
      <el-form-item label="药品" prop="medicineId">
        <el-select 
          v-model="form.medicineId" 
          filterable 
          remote 
          :remote-method="searchMedicine"
          placeholder="请选择药品"
        >
          <el-option
            v-for="item in medicineOptions"
            :key="item.id"
            :label="item.name"
            :value="item.id"
          />
        </el-select>
      </el-form-item>
      
      <el-form-item label="批次号" prop="batchNo">
        <el-input v-model="form.batchNo" />
      </el-form-item>
      
      <el-form-item label="数量" prop="quantity">
        <el-input-number v-model="form.quantity" :min="1" />
      </el-form-item>
      
      <el-form-item label="生产日期" prop="productionDate">
        <el-date-picker 
          v-model="form.productionDate"
          type="date"
          placeholder="选择日期"
        />
      </el-form-item>
      
      <el-form-item label="有效期" prop="expiryDate">
        <el-date-picker 
          v-model="form.expiryDate"
          type="date"
          placeholder="选择日期"
        />
      </el-form-item>
    </el-form>
    
    <template #footer>
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="submitForm">确认入库</el-button>
    </template>
  </el-dialog>
</template>

四、业务流程

1. 入库流程

  1. 验证药品信息
  2. 检查批次号
  3. 更新或创建库存记录
  4. 生成入库单据
  5. 记录操作日志

2. 出库流程

  1. 检查库存数量
  2. 按批次先进先出出库
  3. 更新库存记录
  4. 生成出库单据
  5. 记录操作日志

3. 库存预警

@Component
public class StockWarningTask {
    @Scheduled(cron = "0 0 9 * * ?")  // 每天早上9点执行
    public void checkStockWarning() {
        // 检查低库存
        List<Inventory> lowStockList = inventoryMapper.findLowStock();
        sendLowStockWarning(lowStockList);
        
        // 检查临期商品
        List<Inventory> expiringList = inventoryMapper.findExpiring();
        sendExpiryWarning(expiringList);
    }
}

五、其他功能

1. 库存分析报表

@Service
public class InventoryAnalysisService {
    public Map<String, Object> getAnalysisReport() {
        Map<String, Object> report = new HashMap<>();
        
        // 库存总值
        report.put("totalValue", calculateTotalValue());
        
        // 周转率分析
        report.put("turnoverRate", calculateTurnoverRate());
        
        // 库存结构分析
        report.put("categoryDistribution", analyzeCategoryDistribution());
        
        // 库存趋势
        report.put("stockTrend", analyzeStockTrend());
        
        return report;
    }
}

2. 批次追踪

@Service
public class BatchTrackingService {
    public List<BatchTrackingVO> trackBatch(String batchNo) {
        // 获取入库记录
        StockRecord inRecord = stockRecordMapper.findInRecordByBatch(batchNo);
        
        // 获取出库记录
        List<StockRecord> outRecords = stockRecordMapper.findOutRecordsByBatch(batchNo);
        
        // 获取使用记录
        List<UsageRecord> usageRecords = usageRecordMapper.findByBatch(batchNo);
        
        return buildTrackingChain(inRecord, outRecords, usageRecords);
    }
}

六、注意事项

  1. 数据一致性

    • 使用事务确保库存操作的原子性
    • 采用乐观锁防止并发更新问题
  2. 性能优化

    • 合理使用索引
    • 批量操作优化
    • 缓存热点数据
  3. 安全控制

    • 操作权限控制
    • 关键操作审计日志
    • 数据验证和消毒
  4. 业务规则

    • 先进先出原则
    • 库存预警机制
    • 有效期管理

七、扩展功能

  1. 条码管理
  2. 供应商管理
  3. 采购计划
  4. 库存成本核算
  5. 多仓库管理

八、代码示例:库存变动锁

@Aspect
@Component
public class InventoryLockAspect {
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Around("@annotation(InventoryLock)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        String key = generateLockKey(point);
        boolean locked = false;
        
        try {
            locked = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
            if (!locked) {
                throw new BusinessException("操作太频繁,请稍后再试");
            }
            return point.proceed();
        } finally {
            if (locked) {
                redisTemplate.delete(key);
            }
        }
    }
}

这样的库存管理模块设计,既满足了基本的库存管理需求,又具备了良好的扩展性和可维护性。通过合理的数据结构设计和业务流程控制,可以有效保证库存数据的准确性和一致性。


http://www.kler.cn/a/412550.html

相关文章:

  • 路由策略与路由控制实验
  • 模具监视器的特点有哪些
  • 服务限流、降级、熔断-SpringCloud
  • 鸿蒙中的Image组件如何引用网络图片
  • 吉利汽车x文心快码:AI最佳实践案例
  • Matlab 2016b安装教程附安装包下载
  • 电视网络机顶盒恢复出厂超级密码大全汇总
  • 深入理解 Java 基本语法之数组
  • 项目自动化部署,持续集成/持续交付(CI/CD)工具有那些?他们的优劣势分别是什么?共计15个工具进行对比分析。
  • C++虚函数面试题及参考答案
  • 如何为 ext2/ext3/ext4 文件系统的 /dev/centos/root 增加 800G 空间
  • ms-hot28 合并两个有序数组
  • 基于RAG的企业文档智能检索系统设计与实践
  • TCP/IP 协议:网络世界的基石(2/10)
  • C/C++语言基础--C++字符串实现的三种方法(eager copy、cow、sso)
  • JavaEE 【知识改变命运】03 多线程(2)
  • java——@Transactional 在哪些情况下会失效?
  • 基于Java的Nacos云原生动态服务发现、配置和服务管理平台设计源码
  • deepin社区与此芯科技完成产品兼容性认证
  • 南京移动携手南大打造江苏首个直通高校智算项目