@RestController
@Tag(name = "InventoryController", description = "库存API接口")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class InventoryController {
...
@Operation(summary = "库存预扣",description = "sq-,wq+,创建订单时调用")
@PostMapping("/api/inventory/withholdInventory")
public void withholdInventory(@Valid @RequestBody InventoryLockRequest lockRequest) {
inventoryService.withholdInventory(lockRequest);
}
@Operation(summary = "库存扣减",description = "wq-,oq+,付款时调用")
@PutMapping("/api/inventory/deductionInventory")
public void deductionInventory(@RequestParam("transactionId") Long transactionId) {
inventoryService.deductionInventory(transactionId);
}
@Operation(summary = "库存发货",description = "oq-,发货时调用")
@PutMapping("/api/inventory/shipInventory")
public void shipInventory(@RequestParam("transactionId") Long transactionId) {
inventoryService.shipInventory(transactionId);
}
@Operation(summary = "释放库存")
@PutMapping("/api/inventory/releaseInventory")
public void releaseInventory(@RequestParam("transactionId") Long transactionId) {
inventoryService.releaseInventory(transactionId);
}
...
}
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@Slf4j
public class InventoryServiceImpl implements InventoryService {
...
@Override
@Transactional
public void withholdInventory(InventoryLockRequest inventoryLockRequest) {
Long inventoryId = inventoryLockRequest.getInventoryId();
//1. 获取库存
Inventory inventory = Optional.ofNullable(inventoryRepository.find(new InventoryId(inventoryId)))
.orElseThrow(()->new BusinessException("No inventory found with id:" + inventoryId));
// 2. 幂等校验
boolean exists = inventoryRepository.existsWithTransactionId(inventoryLockRequest.getTransactionId());
if(exists ){
log.error("Inventory record with transaction ID {} already exists, no deduction will be made.", inventoryLockRequest.getTransactionId());
return;
}
//3. 库存预扣
inventory.withholdInventory(inventoryLockRequest.getQuantity());
//4. 生成扣减记录
InventoryRecord inventoryRecord = InventoryRecord.builder()
.inventoryId(inventoryId)
.userId(inventoryLockRequest.getUserId())
.deductionQuantity(inventoryLockRequest.getQuantity())
.transactionId(inventoryLockRequest.getTransactionId())
.state(InventoryRecordStateEnum.PRE_DEDUCTION.code())
.build();
inventory.addInventoryRecord(inventoryRecord);
inventoryRepository.save(inventory);
}
...
}
@Data
public class Inventory implements Aggregate<InventoryId> {
@Serial
private static final long serialVersionUID = 2139884371907883203L;
private InventoryId id;
...
/**
* 库存预扣 sq-,wq+
* @param quantity 数量
*/
public void withholdInventory(int quantity){
if (quantity <= 0) {
throw new BusinessException("扣减库存数量必须大于零");
}
if (getInventoryQuantity() - quantity < 0) {
throw new BusinessException("库存不足,无法扣减库存");
}
sellableQuantity -= quantity;
withholdingQuantity += quantity;
}
/**
* 释放库存
* @param currentState 当前状态
* @param quantity 数量
*/
public void releaseInventory(int currentState, Integer quantity) {
InventoryRecordStateEnum stateEnum = InventoryRecordStateEnum.of(currentState);
switch (stateEnum){
//sq+,wq-
case PRE_DEDUCTION -> {
sellableQuantity += quantity;
withholdingQuantity -= quantity;
}
//sq+,oq-
case DEDUCTION -> {
sellableQuantity += quantity;
occupyQuantity -= quantity;
}
//sq+
case SHIPPED -> {
sellableQuantity += quantity;
}
}
}
...
}
/**
* 仓储接口定义
*/
public interface InventoryRepository extends Repository<Inventory, InventoryId> {
boolean existsWithTransactionId(Long transactionId);
Inventory findByTransactionId(Long transactionId);
}
@Repository
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class InventoryRepositoryImpl implements InventoryRepository {
...
@Override
public Inventory find(InventoryId inventoryId) {
InventoryItemDO inventoryItemDO = inventoryItemMapper.selectById(inventoryId.getValue());
return itemInventoryConverter.fromData(inventoryItemDO);
}
@Override
public Inventory save(Inventory aggregate) {
InventoryItemDO inventoryItemDO = itemInventoryConverter.toData(aggregate);
if(inventoryItemDO.getId() == null){
inventoryItemMapper.insert(inventoryItemDO);
}else{
inventoryItemMapper.updateById(inventoryItemDO);
}
InventoryRecord inventoryRecord = aggregate.getInventoryRecordList().get(0);
InventoryRecordDO inventoryRecordDO = inventoryRecordConverter.toData(inventoryRecord);
if(inventoryRecordDO.getId() == null){
inventoryRecordMapper.insert(inventoryRecordDO);
}else{
inventoryRecordMapper.updateById(inventoryRecordDO);
}
return aggregate;
}
...
}