电商库存管理系统设计与实现
2024/1/1
# 电商库存管理系统设计与实现
# 系统概述
库存管理系统是电商平台的核心组件,负责商品库存的实时监控、预警、调拨和优化。一个高效的库存管理系统需要处理高并发的库存查询和更新操作,确保数据一致性,防止超卖现象,同时提供准确的库存预警和分析功能。
# 系统架构设计
# 整体架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 订单系统 │ │ 购物车系统 │ │ 商品系统 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
│
┌─────────────────────────────────────────────────┐
│ 库存管理系统 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 库存查询 │ │ 库存扣减 │ │ 库存预警 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 库存调拨 │ │ 库存盘点 │ │ 库存分析 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────┐
│ 数据存储层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Redis缓存 │ │ MySQL数据库 │ │ 消息队列 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────┘
# 核心模块
- 库存查询模块:提供实时库存查询服务
- 库存扣减模块:处理订单库存扣减操作
- 库存预警模块:监控库存水位,及时预警
- 库存调拨模块:处理仓库间库存调拨
- 库存盘点模块:定期盘点,确保数据准确性
- 库存分析模块:提供库存分析和报表功能
# 核心实体设计
# 1. 库存实体
@Entity
@Table(name = "inventory")
public class Inventory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long productId;
@Column(nullable = false)
private Long warehouseId;
@Column(nullable = false)
private Integer totalStock; // 总库存
@Column(nullable = false)
private Integer availableStock; // 可用库存
@Column(nullable = false)
private Integer reservedStock; // 预留库存
@Column(nullable = false)
private Integer frozenStock; // 冻结库存
@Column(nullable = false)
private Integer safetyStock; // 安全库存
@Column(nullable = false)
private Integer reorderPoint; // 补货点
@Column(nullable = false)
private Integer maxStock; // 最大库存
@Version
private Long version; // 乐观锁版本号
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
// 计算可销售库存
public Integer getSellableStock() {
return availableStock - reservedStock;
}
// 检查是否需要补货
public Boolean needReorder() {
return availableStock <= reorderPoint;
}
// 检查是否库存不足
public Boolean isLowStock() {
return availableStock <= safetyStock;
}
}
# 2. 库存变更记录
@Entity
@Table(name = "inventory_transactions")
public class InventoryTransaction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long productId;
@Column(nullable = false)
private Long warehouseId;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private TransactionType type;
@Column(nullable = false)
private Integer quantity;
@Column(nullable = false)
private Integer beforeStock;
@Column(nullable = false)
private Integer afterStock;
@Column(length = 100)
private String referenceId; // 关联业务ID(订单号、调拨单号等)
@Column(length = 50)
private String referenceType; // 关联业务类型
@Column(length = 500)
private String remark;
@Column(nullable = false)
private Long operatorId;
@CreationTimestamp
private LocalDateTime createdAt;
}
@Getter
public enum TransactionType {
IN_PURCHASE("采购入库"),
IN_RETURN("退货入库"),
IN_TRANSFER("调拨入库"),
IN_ADJUSTMENT("盘点调整入库"),
OUT_SALE("销售出库"),
OUT_TRANSFER("调拨出库"),
OUT_DAMAGE("损耗出库"),
OUT_ADJUSTMENT("盘点调整出库"),
RESERVE("预留"),
RELEASE("释放"),
FREEZE("冻结"),
UNFREEZE("解冻");
private final String description;
TransactionType(String description) {
this.description = description;
}
}
# 3. 仓库实体
@Entity
@Table(name = "warehouses")
public class Warehouse {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, length = 50)
private String code;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private WarehouseType type;
@Column(length = 500)
private String address;
@Column(length = 20)
private String contactPhone;
@Column(length = 100)
private String contactPerson;
@Column(nullable = false)
private Boolean active = true;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
@Getter
public enum WarehouseType {
MAIN("主仓"),
BRANCH("分仓"),
VIRTUAL("虚拟仓"),
SUPPLIER("供应商仓");
private final String description;
WarehouseType(String description) {
this.description = description;
}
}
# 库存服务实现
# 1. 核心库存服务
@Service
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private InventoryTransactionRepository transactionRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
private static final String INVENTORY_LOCK_PREFIX = "inventory:lock:";
private static final String INVENTORY_CACHE_PREFIX = "inventory:";
/**
* 查询商品库存
*/
public InventoryDTO getInventory(Long productId, Long warehouseId) {
// 先从缓存获取
String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + warehouseId;
InventoryDTO cached = (InventoryDTO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 从数据库获取
Inventory inventory = inventoryRepository.findByProductIdAndWarehouseId(productId, warehouseId);
if (inventory == null) {
return null;
}
InventoryDTO dto = convertToDTO(inventory);
// 缓存结果
redisTemplate.opsForValue().set(cacheKey, dto, 300, TimeUnit.SECONDS);
return dto;
}
/**
* 批量查询库存
*/
public Map<Long, InventoryDTO> batchGetInventory(List<Long> productIds, Long warehouseId) {
Map<Long, InventoryDTO> result = new HashMap<>();
List<Long> uncachedIds = new ArrayList<>();
// 先从缓存获取
for (Long productId : productIds) {
String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + warehouseId;
InventoryDTO cached = (InventoryDTO) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
result.put(productId, cached);
} else {
uncachedIds.add(productId);
}
}
// 从数据库批量获取未缓存的
if (!uncachedIds.isEmpty()) {
List<Inventory> inventories = inventoryRepository.findByProductIdInAndWarehouseId(
uncachedIds, warehouseId);
for (Inventory inventory : inventories) {
InventoryDTO dto = convertToDTO(inventory);
result.put(inventory.getProductId(), dto);
// 缓存结果
String cacheKey = INVENTORY_CACHE_PREFIX + inventory.getProductId() + ":" + warehouseId;
redisTemplate.opsForValue().set(cacheKey, dto, 300, TimeUnit.SECONDS);
}
}
return result;
}
/**
* 扣减库存
*/
@Transactional
public Boolean deductStock(Long productId, Long warehouseId, Integer quantity,
String referenceId, String referenceType) {
String lockKey = INVENTORY_LOCK_PREFIX + productId + ":" + warehouseId;
// 分布式锁
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
lockKey, "1", 10, TimeUnit.SECONDS);
if (!lockAcquired) {
throw new BusinessException("库存操作繁忙,请稍后重试");
}
try {
Inventory inventory = inventoryRepository.findByProductIdAndWarehouseIdForUpdate(
productId, warehouseId);
if (inventory == null) {
throw new BusinessException("库存记录不存在");
}
if (inventory.getAvailableStock() < quantity) {
throw new BusinessException("库存不足");
}
// 记录变更前库存
Integer beforeStock = inventory.getAvailableStock();
// 扣减库存
inventory.setAvailableStock(inventory.getAvailableStock() - quantity);
inventory.setTotalStock(inventory.getTotalStock() - quantity);
inventoryRepository.save(inventory);
// 记录库存变更
recordTransaction(inventory, TransactionType.OUT_SALE, quantity,
beforeStock, inventory.getAvailableStock(), referenceId, referenceType);
// 清除缓存
clearInventoryCache(productId, warehouseId);
// 检查库存预警
checkStockAlert(inventory);
return true;
} finally {
redisTemplate.delete(lockKey);
}
}
/**
* 预留库存
*/
@Transactional
public Boolean reserveStock(Long productId, Long warehouseId, Integer quantity,
String referenceId, String referenceType) {
String lockKey = INVENTORY_LOCK_PREFIX + productId + ":" + warehouseId;
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
lockKey, "1", 10, TimeUnit.SECONDS);
if (!lockAcquired) {
throw new BusinessException("库存操作繁忙,请稍后重试");
}
try {
Inventory inventory = inventoryRepository.findByProductIdAndWarehouseIdForUpdate(
productId, warehouseId);
if (inventory == null) {
throw new BusinessException("库存记录不存在");
}
if (inventory.getSellableStock() < quantity) {
return false; // 库存不足
}
// 记录变更前库存
Integer beforeReserved = inventory.getReservedStock();
// 预留库存
inventory.setReservedStock(inventory.getReservedStock() + quantity);
inventoryRepository.save(inventory);
// 记录库存变更
recordTransaction(inventory, TransactionType.RESERVE, quantity,
beforeReserved, inventory.getReservedStock(), referenceId, referenceType);
// 清除缓存
clearInventoryCache(productId, warehouseId);
return true;
} finally {
redisTemplate.delete(lockKey);
}
}
/**
* 释放预留库存
*/
@Transactional
public void releaseReservedStock(Long productId, Long warehouseId, Integer quantity,
String referenceId, String referenceType) {
String lockKey = INVENTORY_LOCK_PREFIX + productId + ":" + warehouseId;
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
lockKey, "1", 10, TimeUnit.SECONDS);
if (!lockAcquired) {
throw new BusinessException("库存操作繁忙,请稍后重试");
}
try {
Inventory inventory = inventoryRepository.findByProductIdAndWarehouseIdForUpdate(
productId, warehouseId);
if (inventory == null) {
return;
}
// 记录变更前库存
Integer beforeReserved = inventory.getReservedStock();
// 释放预留库存
inventory.setReservedStock(Math.max(0, inventory.getReservedStock() - quantity));
inventoryRepository.save(inventory);
// 记录库存变更
recordTransaction(inventory, TransactionType.RELEASE, quantity,
beforeReserved, inventory.getReservedStock(), referenceId, referenceType);
// 清除缓存
clearInventoryCache(productId, warehouseId);
} finally {
redisTemplate.delete(lockKey);
}
}
/**
* 入库操作
*/
@Transactional
public void inboundStock(Long productId, Long warehouseId, Integer quantity,
TransactionType type, String referenceId, String referenceType) {
String lockKey = INVENTORY_LOCK_PREFIX + productId + ":" + warehouseId;
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
lockKey, "1", 10, TimeUnit.SECONDS);
if (!lockAcquired) {
throw new BusinessException("库存操作繁忙,请稍后重试");
}
try {
Inventory inventory = inventoryRepository.findByProductIdAndWarehouseIdForUpdate(
productId, warehouseId);
if (inventory == null) {
// 创建新的库存记录
inventory = new Inventory();
inventory.setProductId(productId);
inventory.setWarehouseId(warehouseId);
inventory.setTotalStock(0);
inventory.setAvailableStock(0);
inventory.setReservedStock(0);
inventory.setFrozenStock(0);
inventory.setSafetyStock(10); // 默认安全库存
inventory.setReorderPoint(20); // 默认补货点
inventory.setMaxStock(1000); // 默认最大库存
}
// 记录变更前库存
Integer beforeStock = inventory.getAvailableStock();
// 入库
inventory.setAvailableStock(inventory.getAvailableStock() + quantity);
inventory.setTotalStock(inventory.getTotalStock() + quantity);
inventoryRepository.save(inventory);
// 记录库存变更
recordTransaction(inventory, type, quantity,
beforeStock, inventory.getAvailableStock(), referenceId, referenceType);
// 清除缓存
clearInventoryCache(productId, warehouseId);
} finally {
redisTemplate.delete(lockKey);
}
}
/**
* 记录库存变更
*/
private void recordTransaction(Inventory inventory, TransactionType type, Integer quantity,
Integer beforeStock, Integer afterStock,
String referenceId, String referenceType) {
InventoryTransaction transaction = new InventoryTransaction();
transaction.setProductId(inventory.getProductId());
transaction.setWarehouseId(inventory.getWarehouseId());
transaction.setType(type);
transaction.setQuantity(quantity);
transaction.setBeforeStock(beforeStock);
transaction.setAfterStock(afterStock);
transaction.setReferenceId(referenceId);
transaction.setReferenceType(referenceType);
transaction.setOperatorId(getCurrentUserId());
transactionRepository.save(transaction);
}
/**
* 清除库存缓存
*/
private void clearInventoryCache(Long productId, Long warehouseId) {
String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + warehouseId;
redisTemplate.delete(cacheKey);
}
/**
* 检查库存预警
*/
private void checkStockAlert(Inventory inventory) {
if (inventory.needReorder()) {
// 发送补货提醒
StockAlertEvent event = new StockAlertEvent();
event.setProductId(inventory.getProductId());
event.setWarehouseId(inventory.getWarehouseId());
event.setCurrentStock(inventory.getAvailableStock());
event.setReorderPoint(inventory.getReorderPoint());
event.setAlertType(StockAlertType.REORDER);
rabbitTemplate.convertAndSend("stock.alert.exchange", "stock.alert.reorder", event);
}
if (inventory.isLowStock()) {
// 发送低库存预警
StockAlertEvent event = new StockAlertEvent();
event.setProductId(inventory.getProductId());
event.setWarehouseId(inventory.getWarehouseId());
event.setCurrentStock(inventory.getAvailableStock());
event.setSafetyStock(inventory.getSafetyStock());
event.setAlertType(StockAlertType.LOW_STOCK);
rabbitTemplate.convertAndSend("stock.alert.exchange", "stock.alert.low", event);
}
}
}
# 2. 库存预警服务
@Service
public class StockAlertService {
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private NotificationService notificationService;
@Autowired
private PurchaseService purchaseService;
/**
* 扫描库存预警
*/
@Scheduled(cron = "0 */10 * * * ?") // 每10分钟执行一次
public void scanStockAlerts() {
// 查找需要补货的商品
List<Inventory> reorderItems = inventoryRepository.findReorderItems();
for (Inventory inventory : reorderItems) {
handleReorderAlert(inventory);
}
// 查找低库存商品
List<Inventory> lowStockItems = inventoryRepository.findLowStockItems();
for (Inventory inventory : lowStockItems) {
handleLowStockAlert(inventory);
}
// 查找零库存商品
List<Inventory> zeroStockItems = inventoryRepository.findZeroStockItems();
for (Inventory inventory : zeroStockItems) {
handleZeroStockAlert(inventory);
}
}
/**
* 处理补货预警
*/
private void handleReorderAlert(Inventory inventory) {
// 检查是否已经有未完成的采购订单
boolean hasPendingPurchase = purchaseService.hasPendingPurchaseOrder(
inventory.getProductId(), inventory.getWarehouseId());
if (!hasPendingPurchase) {
// 自动创建采购建议
PurchaseSuggestion suggestion = new PurchaseSuggestion();
suggestion.setProductId(inventory.getProductId());
suggestion.setWarehouseId(inventory.getWarehouseId());
suggestion.setCurrentStock(inventory.getAvailableStock());
suggestion.setSuggestedQuantity(inventory.getMaxStock() - inventory.getAvailableStock());
suggestion.setReason("库存低于补货点");
purchaseService.createPurchaseSuggestion(suggestion);
// 发送通知
notificationService.sendReorderNotification(inventory);
}
}
/**
* 处理低库存预警
*/
private void handleLowStockAlert(Inventory inventory) {
notificationService.sendLowStockNotification(inventory);
}
/**
* 处理零库存预警
*/
private void handleZeroStockAlert(Inventory inventory) {
notificationService.sendZeroStockNotification(inventory);
// 自动下架商品
productService.setProductOffline(inventory.getProductId(), "库存为零");
}
}
# 3. 库存调拨服务
@Service
public class InventoryTransferService {
@Autowired
private InventoryService inventoryService;
@Autowired
private TransferOrderRepository transferOrderRepository;
/**
* 创建调拨单
*/
@Transactional
public TransferOrder createTransferOrder(CreateTransferOrderRequest request) {
// 验证调出仓库库存
InventoryDTO fromInventory = inventoryService.getInventory(
request.getProductId(), request.getFromWarehouseId());
if (fromInventory == null || fromInventory.getSellableStock() < request.getQuantity()) {
throw new BusinessException("调出仓库库存不足");
}
// 创建调拨单
TransferOrder transferOrder = new TransferOrder();
transferOrder.setTransferNo(generateTransferNo());
transferOrder.setProductId(request.getProductId());
transferOrder.setFromWarehouseId(request.getFromWarehouseId());
transferOrder.setToWarehouseId(request.getToWarehouseId());
transferOrder.setQuantity(request.getQuantity());
transferOrder.setStatus(TransferStatus.PENDING);
transferOrder.setReason(request.getReason());
transferOrder.setCreatorId(getCurrentUserId());
transferOrderRepository.save(transferOrder);
return transferOrder;
}
/**
* 执行调拨
*/
@Transactional
public void executeTransfer(Long transferOrderId) {
TransferOrder transferOrder = transferOrderRepository.findById(transferOrderId)
.orElseThrow(() -> new BusinessException("调拨单不存在"));
if (transferOrder.getStatus() != TransferStatus.PENDING) {
throw new BusinessException("调拨单状态不正确");
}
try {
// 调出库存
inventoryService.deductStock(
transferOrder.getProductId(),
transferOrder.getFromWarehouseId(),
transferOrder.getQuantity(),
transferOrder.getTransferNo(),
"TRANSFER_OUT"
);
// 调入库存
inventoryService.inboundStock(
transferOrder.getProductId(),
transferOrder.getToWarehouseId(),
transferOrder.getQuantity(),
TransactionType.IN_TRANSFER,
transferOrder.getTransferNo(),
"TRANSFER_IN"
);
// 更新调拨单状态
transferOrder.setStatus(TransferStatus.COMPLETED);
transferOrder.setCompletedAt(LocalDateTime.now());
transferOrderRepository.save(transferOrder);
} catch (Exception e) {
// 调拨失败
transferOrder.setStatus(TransferStatus.FAILED);
transferOrder.setFailReason(e.getMessage());
transferOrderRepository.save(transferOrder);
throw e;
}
}
}
# 库存盘点系统
# 1. 盘点服务
@Service
public class InventoryCountService {
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private CountTaskRepository countTaskRepository;
@Autowired
private CountDetailRepository countDetailRepository;
/**
* 创建盘点任务
*/
@Transactional
public CountTask createCountTask(CreateCountTaskRequest request) {
CountTask countTask = new CountTask();
countTask.setTaskNo(generateTaskNo());
countTask.setWarehouseId(request.getWarehouseId());
countTask.setCountType(request.getCountType());
countTask.setStatus(CountStatus.PENDING);
countTask.setScheduledDate(request.getScheduledDate());
countTask.setRemark(request.getRemark());
countTask.setCreatorId(getCurrentUserId());
countTaskRepository.save(countTask);
// 生成盘点明细
generateCountDetails(countTask, request);
return countTask;
}
/**
* 生成盘点明细
*/
private void generateCountDetails(CountTask countTask, CreateCountTaskRequest request) {
List<Inventory> inventories;
if (request.getCountType() == CountType.FULL) {
// 全盘
inventories = inventoryRepository.findByWarehouseId(request.getWarehouseId());
} else if (request.getCountType() == CountType.PARTIAL) {
// 部分盘点
inventories = inventoryRepository.findByProductIdInAndWarehouseId(
request.getProductIds(), request.getWarehouseId());
} else {
// 循环盘点
inventories = inventoryRepository.findCyclicCountItems(
request.getWarehouseId(), request.getScheduledDate());
}
List<CountDetail> details = new ArrayList<>();
for (Inventory inventory : inventories) {
CountDetail detail = new CountDetail();
detail.setCountTaskId(countTask.getId());
detail.setProductId(inventory.getProductId());
detail.setSystemStock(inventory.getAvailableStock());
detail.setStatus(CountDetailStatus.PENDING);
details.add(detail);
}
countDetailRepository.saveAll(details);
}
/**
* 录入盘点结果
*/
@Transactional
public void inputCountResult(Long countDetailId, Integer actualStock, String remark) {
CountDetail detail = countDetailRepository.findById(countDetailId)
.orElseThrow(() -> new BusinessException("盘点明细不存在"));
detail.setActualStock(actualStock);
detail.setDifferenceStock(actualStock - detail.getSystemStock());
detail.setRemark(remark);
detail.setStatus(CountDetailStatus.COUNTED);
detail.setCountedAt(LocalDateTime.now());
detail.setCounterId(getCurrentUserId());
countDetailRepository.save(detail);
// 检查任务是否完成
checkTaskCompletion(detail.getCountTaskId());
}
/**
* 检查盘点任务完成情况
*/
private void checkTaskCompletion(Long countTaskId) {
long pendingCount = countDetailRepository.countByCountTaskIdAndStatus(
countTaskId, CountDetailStatus.PENDING);
if (pendingCount == 0) {
CountTask countTask = countTaskRepository.findById(countTaskId).orElse(null);
if (countTask != null) {
countTask.setStatus(CountStatus.COMPLETED);
countTask.setCompletedAt(LocalDateTime.now());
countTaskRepository.save(countTask);
}
}
}
/**
* 审核盘点结果
*/
@Transactional
public void auditCountResult(Long countTaskId, Boolean approved, String auditRemark) {
CountTask countTask = countTaskRepository.findById(countTaskId)
.orElseThrow(() -> new BusinessException("盘点任务不存在"));
if (countTask.getStatus() != CountStatus.COMPLETED) {
throw new BusinessException("盘点任务未完成");
}
if (approved) {
// 审核通过,调整库存
adjustInventoryByCountResult(countTaskId);
countTask.setStatus(CountStatus.APPROVED);
} else {
// 审核不通过
countTask.setStatus(CountStatus.REJECTED);
}
countTask.setAuditRemark(auditRemark);
countTask.setAuditedAt(LocalDateTime.now());
countTask.setAuditorId(getCurrentUserId());
countTaskRepository.save(countTask);
}
/**
* 根据盘点结果调整库存
*/
private void adjustInventoryByCountResult(Long countTaskId) {
List<CountDetail> details = countDetailRepository.findByCountTaskIdAndDifferenceStockNot(
countTaskId, 0);
for (CountDetail detail : details) {
if (detail.getDifferenceStock() > 0) {
// 盘盈,入库
inventoryService.inboundStock(
detail.getProductId(),
getWarehouseIdByCountTask(countTaskId),
detail.getDifferenceStock(),
TransactionType.IN_ADJUSTMENT,
"COUNT_" + countTaskId,
"INVENTORY_COUNT"
);
} else {
// 盘亏,出库
inventoryService.deductStock(
detail.getProductId(),
getWarehouseIdByCountTask(countTaskId),
Math.abs(detail.getDifferenceStock()),
"COUNT_" + countTaskId,
"INVENTORY_COUNT"
);
}
}
}
}
# 库存分析与报表
# 1. 库存分析服务
@Service
public class InventoryAnalysisService {
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
private InventoryTransactionRepository transactionRepository;
/**
* 库存周转率分析
*/
public InventoryTurnoverDTO analyzeInventoryTurnover(Long productId, Long warehouseId,
LocalDate startDate, LocalDate endDate) {
// 计算期间销售出库量
Integer salesQuantity = transactionRepository.sumQuantityByProductAndWarehouseAndTypeAndDateRange(
productId, warehouseId, TransactionType.OUT_SALE, startDate, endDate);
// 计算平均库存
BigDecimal avgInventory = calculateAverageInventory(productId, warehouseId, startDate, endDate);
// 计算周转率
BigDecimal turnoverRate = BigDecimal.ZERO;
if (avgInventory.compareTo(BigDecimal.ZERO) > 0) {
turnoverRate = BigDecimal.valueOf(salesQuantity).divide(avgInventory, 2, RoundingMode.HALF_UP);
}
// 计算周转天数
BigDecimal turnoverDays = BigDecimal.ZERO;
if (turnoverRate.compareTo(BigDecimal.ZERO) > 0) {
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
turnoverDays = BigDecimal.valueOf(daysBetween).divide(turnoverRate, 2, RoundingMode.HALF_UP);
}
InventoryTurnoverDTO dto = new InventoryTurnoverDTO();
dto.setProductId(productId);
dto.setWarehouseId(warehouseId);
dto.setSalesQuantity(salesQuantity);
dto.setAverageInventory(avgInventory);
dto.setTurnoverRate(turnoverRate);
dto.setTurnoverDays(turnoverDays);
dto.setStartDate(startDate);
dto.setEndDate(endDate);
return dto;
}
/**
* 计算平均库存
*/
private BigDecimal calculateAverageInventory(Long productId, Long warehouseId,
LocalDate startDate, LocalDate endDate) {
List<DailyInventory> dailyInventories = inventoryRepository.findDailyInventory(
productId, warehouseId, startDate, endDate);
if (dailyInventories.isEmpty()) {
return BigDecimal.ZERO;
}
BigDecimal totalInventory = dailyInventories.stream()
.map(DailyInventory::getInventoryQuantity)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return totalInventory.divide(BigDecimal.valueOf(dailyInventories.size()), 2, RoundingMode.HALF_UP);
}
/**
* 库存ABC分析
*/
public List<InventoryABCDTO> analyzeInventoryABC(Long warehouseId, LocalDate startDate, LocalDate endDate) {
// 获取商品销售数据
List<ProductSalesData> salesData = transactionRepository.getProductSalesData(
warehouseId, startDate, endDate);
// 按销售额排序
salesData.sort((a, b) -> b.getSalesAmount().compareTo(a.getSalesAmount()));
// 计算累计销售额
BigDecimal totalSales = salesData.stream()
.map(ProductSalesData::getSalesAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
List<InventoryABCDTO> result = new ArrayList<>();
BigDecimal cumulativeSales = BigDecimal.ZERO;
for (ProductSalesData data : salesData) {
cumulativeSales = cumulativeSales.add(data.getSalesAmount());
BigDecimal cumulativePercentage = cumulativeSales.divide(totalSales, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
InventoryABCDTO dto = new InventoryABCDTO();
dto.setProductId(data.getProductId());
dto.setSalesAmount(data.getSalesAmount());
dto.setSalesQuantity(data.getSalesQuantity());
dto.setCumulativePercentage(cumulativePercentage);
// 分类
if (cumulativePercentage.compareTo(BigDecimal.valueOf(80)) <= 0) {
dto.setCategory("A");
} else if (cumulativePercentage.compareTo(BigDecimal.valueOf(95)) <= 0) {
dto.setCategory("B");
} else {
dto.setCategory("C");
}
result.add(dto);
}
return result;
}
/**
* 滞销商品分析
*/
public List<SlowMovingInventoryDTO> analyzeSlowMovingInventory(Long warehouseId, Integer days) {
LocalDate cutoffDate = LocalDate.now().minusDays(days);
List<Inventory> slowMovingItems = inventoryRepository.findSlowMovingInventory(
warehouseId, cutoffDate);
return slowMovingItems.stream()
.map(this::convertToSlowMovingDTO)
.collect(Collectors.toList());
}
}
# 性能优化策略
# 1. 缓存优化
@Component
public class InventoryCacheManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String HOT_INVENTORY_KEY = "hot:inventory";
private static final String INVENTORY_CACHE_PREFIX = "inventory:";
/**
* 预热热门商品库存缓存
*/
@PostConstruct
public void preloadHotInventory() {
// 获取热门商品列表
List<Long> hotProductIds = getHotProductIds();
for (Long productId : hotProductIds) {
List<Inventory> inventories = inventoryRepository.findByProductId(productId);
for (Inventory inventory : inventories) {
String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + inventory.getWarehouseId();
InventoryDTO dto = convertToDTO(inventory);
redisTemplate.opsForValue().set(cacheKey, dto, 600, TimeUnit.SECONDS);
}
}
}
/**
* 批量刷新库存缓存
*/
@Async
public void batchRefreshInventoryCache(List<Long> productIds) {
for (Long productId : productIds) {
List<Inventory> inventories = inventoryRepository.findByProductId(productId);
for (Inventory inventory : inventories) {
String cacheKey = INVENTORY_CACHE_PREFIX + productId + ":" + inventory.getWarehouseId();
InventoryDTO dto = convertToDTO(inventory);
redisTemplate.opsForValue().set(cacheKey, dto, 300, TimeUnit.SECONDS);
}
}
}
}
# 2. 数据库优化
-- 库存表索引优化
CREATE INDEX idx_inventory_product_warehouse ON inventory(product_id, warehouse_id);
CREATE INDEX idx_inventory_warehouse_stock ON inventory(warehouse_id, available_stock);
CREATE INDEX idx_inventory_reorder ON inventory(warehouse_id, available_stock, reorder_point);
CREATE INDEX idx_inventory_updated_at ON inventory(updated_at);
-- 库存变更记录表索引
CREATE INDEX idx_transaction_product_warehouse_date ON inventory_transactions(product_id, warehouse_id, created_at);
CREATE INDEX idx_transaction_reference ON inventory_transactions(reference_id, reference_type);
CREATE INDEX idx_transaction_type_date ON inventory_transactions(type, created_at);
-- 分区表设计(按月分区)
CREATE TABLE inventory_transactions_202401 PARTITION OF inventory_transactions
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
# 总结
库存管理系统的关键设计要点:
- 数据一致性:使用分布式锁、乐观锁、事务保证库存数据准确性
- 高并发处理:Redis缓存、批量操作、异步处理提升性能
- 库存预警:实时监控、自动预警、智能补货建议
- 精细化管理:多仓库支持、库存调拨、盘点管理
- 数据分析:周转率分析、ABC分析、滞销分析
- 扩展性:事件驱动、微服务架构、水平扩展
通过以上设计,可以构建一个高效、准确、智能的库存管理系统,为电商业务提供强有力的库存保障。