4、库存服务(Stock Service)设计与实现:库存核心接口定义(入库、出库、查询)、数据库表设计(stock、stock_detail)、MyBatis-Plus集成与代码生成
好,咱们进入库存服务的核心环节了。说实话,库存服务是整个库存系统的灵魂。你想想看,订单可以取消,商品可以下架,但库存数据一旦出错,那可真是一笔糊涂账。我在项目中见过太多因为库存接口设计不合理,导致超卖、数据不一致的惨案。今天咱们就把这块彻底讲透。
4.1 库存核心接口定义
接口设计,说白了就是定义清楚「别人怎么用你的库存」。我个人习惯先把接口契约定死,再动手写代码。这样后面联调的时候,能少踩很多坑。
4.1.1 入库接口
入库操作,不只是加库存那么简单。它背后往往关联着采购单、退货单、调拨单。我建议接口设计成幂等的,防止网络重试导致库存翻倍。
POST /api/stock/inbound
Request Body:
{
"skuId": "SKU001", // 商品SKU ID
"warehouseId": "WH001", // 仓库ID
"quantity": 100, // 入库数量
"batchNo": "B20240301", // 批次号(可选)
"sourceType": "PURCHASE", // 来源类型:PURCHASE/RETURN/TRANSFER
"sourceOrderNo": "PO20240301001", // 来源单号
"operator": "张三" // 操作人
}
Response:
{
"code": 200,
"message": "success",
"data": {
"stockId": 12345,
"beforeQuantity": 500,
"afterQuantity": 600,
"inboundTime": "2024-03-01 10:30:00"
}
}
4.1.2 出库接口
出库比入库复杂,因为它涉及「库存扣减」和「库存预占」两个概念。电商场景下,用户下单时先预占库存,支付成功后才真正扣减。这样能防止「下了单却发不出货」的尴尬。
POST /api/stock/outbound
Request Body:
{
"skuId": "SKU001",
"warehouseId": "WH001",
"quantity": 10,
"orderNo": "ORD20240301001",
"outboundType": "SALE", // 出库类型:SALE/RETURN/TRANSFER
"operator": "李四"
}
Response:
{
"code": 200,
"message": "success",
"data": {
"stockId": 12345,
"beforeQuantity": 600,
"afterQuantity": 590,
"outboundTime": "2024-03-01 11:00:00"
}
}
4.1.3 库存查询接口
查询接口看似简单,但实际场景中查询维度很多。我建议提供灵活的查询方式,支持按SKU、仓库、批次等条件组合查询。
GET /api/stock/query?skuId=SKU001&warehouseId=WH001&batchNo=B20240301
Response:
{
"code": 200,
"message": "success",
"data": {
"skuId": "SKU001",
"warehouseId": "WH001",
"availableQuantity": 590, // 可用库存
"lockedQuantity": 20, // 预占库存
"totalQuantity": 610, // 总库存
"batchInfo": [
{
"batchNo": "B20240301",
"quantity": 590,
"inboundTime": "2024-03-01 10:30:00"
}
]
}
}
4.2 数据库表设计
表设计这块,我踩过不少坑。最核心的原则是:库存主表和流水表分离。主表存当前库存快照,流水表存每一次变动记录。这样既能保证查询性能,又能追溯历史。
4.2.1 stock 表(库存主表)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| sku_id | varchar(64) | 商品SKU ID |
| warehouse_id | varchar(64) | 仓库ID |
| available_quantity | int | 可用库存 |
| locked_quantity | int | 预占库存 |
| total_quantity | int | 总库存 |
| version | int | 乐观锁版本号 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
4.2.2 stock_detail 表(库存流水表)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| stock_id | bigint | 库存主表ID |
| sku_id | varchar(64) | 商品SKU ID |
| warehouse_id | varchar(64) | 仓库ID |
| change_type | varchar(32) | 变动类型:INBOUND/OUTBOUND/LOCK/UNLOCK |
| change_quantity | int | 变动数量(正数增加,负数减少) |
| before_quantity | int | 变动前库存 |
| after_quantity | int | 变动后库存 |
| source_order_no | varchar(64) | 来源单号 |
| operator | varchar(32) | 操作人 |
| remark | varchar(255) | 备注 |
| create_time | datetime | 创建时间 |
4.3 MyBatis-Plus 集成与代码生成
MyBatis-Plus 这东西,说白了就是 MyBatis 的增强版。它帮你省掉了大部分 CRUD 的模板代码。我个人习惯用它来生成 Entity、Mapper、Service 这些基础代码,然后自己再写业务逻辑。
4.3.1 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!-- 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3</version>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
4.3.2 代码生成器配置
我一般会写一个独立的生成器类,放在 test 目录下。这样不会污染主代码。配置好数据库连接、表名、包名,一键生成。
public class CodeGenerator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/stock_db",
"root", "password")
.globalConfig(builder -> {
builder.author("张三") // 设置作者
.outputDir("D://code//stock-service//src//main//java"); // 输出目录
})
.packageConfig(builder -> {
builder.parent("com.example.stock")
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.controller("controller");
})
.strategyConfig(builder -> {
builder.addInclude("stock", "stock_detail") // 设置需要生成的表名
.entityBuilder()
.enableLombok() // 启用 Lombok
.enableTableFieldAnnotation() // 启用字段注解
.controllerBuilder()
.enableRestStyle(); // 启用 REST 风格
})
.execute();
}
}
- entity/Stock.java - 库存实体类
- entity/StockDetail.java - 库存流水实体类
- mapper/StockMapper.java - 库存 Mapper 接口
- mapper/StockDetailMapper.java - 库存流水 Mapper 接口
- service/IStockService.java - 库存服务接口
- service/impl/StockServiceImpl.java - 库存服务实现
- controller/StockController.java - 库存控制器
4.3.3 核心业务代码示例
生成完基础代码后,咱们得自己写核心业务逻辑。比如入库操作的 Service 实现:
@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock>
implements IStockService {
@Override
@Transactional(rollbackFor = Exception.class)
public StockInboundResponse inbound(StockInboundRequest request) {
// 1. 查询当前库存(带乐观锁)
Stock stock = this.lambdaQuery()
.eq(Stock::getSkuId, request.getSkuId())
.eq(Stock::getWarehouseId, request.getWarehouseId())
.one();
if (stock == null) {
// 首次入库,创建库存记录
stock = new Stock();
stock.setSkuId(request.getSkuId());
stock.setWarehouseId(request.getWarehouseId());
stock.setAvailableQuantity(0);
stock.setLockedQuantity(0);
stock.setTotalQuantity(0);
stock.setVersion(1);
}
// 2. 更新库存(乐观锁更新)
int beforeQuantity = stock.getAvailableQuantity();
stock.setAvailableQuantity(beforeQuantity + request.getQuantity());
stock.setTotalQuantity(stock.getTotalQuantity() + request.getQuantity());
stock.setVersion(stock.getVersion() + 1);
boolean updated = this.update(
new LambdaUpdateWrapper<Stock>()
.eq(Stock::getId, stock.getId())
.eq(Stock::getVersion, stock.getVersion() - 1) // 乐观锁条件
);
if (!updated) {
throw new RuntimeException("库存更新失败,请重试");
}
// 3. 记录流水
StockDetail detail = new StockDetail();
detail.setStockId(stock.getId());
detail.setSkuId(request.getSkuId());
detail.setWarehouseId(request.getWarehouseId());
detail.setChangeType("INBOUND");
detail.setChangeQuantity(request.getQuantity());
detail.setBeforeQuantity(beforeQuantity);
detail.setAfterQuantity(beforeQuantity + request.getQuantity());
detail.setSourceOrderNo(request.getSourceOrderNo());
detail.setOperator(request.getOperator());
detail.setRemark("入库操作");
stockDetailMapper.insert(detail);
// 4. 返回结果
StockInboundResponse response = new StockInboundResponse();
response.setStockId(stock.getId());
response.setBeforeQuantity(beforeQuantity);
response.setAfterQuantity(beforeQuantity + request.getQuantity());
return response;
}
}
4.4 核心流程示意图
下面这张图,是我梳理的库存服务核心流程。你一看就明白入库、出库、查询是怎么串联起来的。
嗯,到这里库存服务的核心设计就讲完了。接口定义、表设计、代码生成,这三块是库存服务的基石。你在实际项目中,可以根据业务场景再灵活调整。比如有些场景需要支持批次管理,那就在 stock 表里加个 batch_no 字段。核心思路不变,细节自己把控就好。