电商库存管理系统是每个电商企业运营过程中不可或缺的核心系统。作为一名经历过多次库存管理混乱的开发者,我深知一个高效的库存管理系统对企业运营的重要性。这个基于Spring Boot框架开发的系统,正是为了解决传统库存管理中的各种痛点而生。
记得去年双十一期间,我参与的一个电商项目因为库存数据不同步,导致超卖和缺货同时发生,给企业造成了不小的损失。这也促使我下定决心要开发一套真正实用的库存管理系统。本系统不仅实现了采购、销售、库存等基础功能,还特别设计了多角色协同操作机制,让采购、销售和管理人员各司其职,数据实时同步。
系统采用三级权限设计,这是经过多次实践验证的最优方案:
这种设计避免了传统系统中常见的权限混乱问题。我在实现时使用了Spring Security框架,通过注解方式控制权限:
java复制@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/admin/goods")
public ResponseEntity addGoods(@RequestBody Goods goods) {
// 管理员添加商品逻辑
}
@PreAuthorize("hasRole('PURCHASE')")
@PostMapping("/purchase/order")
public ResponseEntity createPurchaseOrder(@RequestBody PurchaseOrder order) {
// 采购用户创建订单逻辑
}
库存管理是系统的核心,我设计了双重校验机制来保证数据准确性:
库存变更的SQL实现示例:
sql复制UPDATE commodity_information
SET commodity_inventory = commodity_inventory + :quantity
WHERE item_code = :itemCode
这个简单的SQL背后其实有很多考虑。比如,我们使用了乐观锁来防止并发更新问题:
java复制@Transactional
public void updateInventory(String itemCode, double quantity) {
Commodity commodity = commodityDao.findByCode(itemCode);
// 检查库存是否充足(出库时)
if(quantity < && commodity.getInventory() < Math.abs(quantity)) {
throw new InventoryException("库存不足");
}
commodityDao.updateInventory(itemCode, quantity, commodity.getVersion());
}
经过三个版本的迭代,最终确定的数据库设计既满足了功能需求,又保证了性能:
商品信息表(commodity_information)
sql复制CREATE TABLE `commodity_information` (
`commodity_information_id` int NOT NULL AUTO_INCREMENT,
`item_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '商品编号',
`product_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '商品名称',
`purchase_price` decimal(10,2) DEFAULT NULL COMMENT '采购价格',
`selling_price` decimal(10,2) DEFAULT NULL COMMENT '销售价格',
`commodity_inventory` decimal(10,2) DEFAULT NULL COMMENT '商品库存',
PRIMARY KEY (`commodity_information_id`),
UNIQUE KEY `idx_item_code` (`item_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
采购订单表(purchase_order)
sql复制CREATE TABLE `purchase_order` (
`purchase_order_id` int NOT NULL AUTO_INCREMENT,
`item_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '商品编号',
`purchase_quantity` decimal(10,2) DEFAULT NULL COMMENT '采购数量',
`examine_state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '审核状态',
PRIMARY KEY (`purchase_order_id`),
KEY `idx_item_code` (`item_code`),
CONSTRAINT `fk_purchase_item` FOREIGN KEY (`item_code`) REFERENCES `commodity_information` (`item_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
在高并发测试中,我们发现订单查询性能存在问题。通过EXPLAIN分析后,添加了以下索引:
sql复制ALTER TABLE purchase_order
ADD INDEX idx_user_item (purchasing_users, item_code);
sql复制ALTER TABLE sale_order
ADD INDEX idx_sales_time (sales_time);
这些优化使查询性能提升了5倍以上。特别是在处理月末报表生成时,从原来的30秒降低到了6秒。
订单处理是系统的核心业务流程。我采用了状态模式来实现订单状态流转:
java复制public interface OrderState {
void confirm(OrderContext context);
void cancel(OrderContext context);
void complete(OrderContext context);
}
@Component
@Scope("prototype")
public class PendingState implements OrderState {
@Override
public void confirm(OrderContext context) {
context.setState(new ConfirmedState());
// 更新数据库状态
orderDao.updateStatus(context.getOrderId(), "CONFIRMED");
}
// 其他方法实现...
}
状态机的使用使得订单流程更加清晰,也便于后期添加新的状态。
库存预警是实际运营中非常实用的功能。我实现了两种预警机制:
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行
public void checkInventory() {
List<Commodity> lowInventoryGoods = commodityDao.findLowInventory();
lowInventoryGoods.forEach(goods -> {
String message = String.format("商品%s库存不足,当前库存%.2f",
goods.getProductName(), goods.getCommodityInventory());
alertService.sendAlert(message);
});
}
java复制public List<InventoryWarning> generateDynamicWarnings() {
// 获取过去30天销售数据
List<SalesData> salesData = salesDao.getRecentSales(30);
// 使用简单移动平均算法预测
Map<String, Double> predictions = salesData.stream()
.collect(Collectors.groupingBy(SalesData::getItemCode,
Collectors.averagingDouble(SalesData::getQuantity)));
// 生成预警
return predictions.entrySet().stream()
.filter(entry -> {
Commodity commodity = commodityDao.findByCode(entry.getKey());
return commodity.getInventory() < entry.getValue() * 1.2; // 保留20%余量
})
.map(entry -> new InventoryWarning(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
}
我们使用JMeter进行了全面的性能测试,重点关注以下几个指标:
测试环境配置:
测试结果:
| 场景 | 线程数 | 平均响应时间(ms) | 吞吐量(请求/秒) |
|---|---|---|---|
| 商品查询 | 100 | 45 | 2200 |
| 创建订单 | 50 | 120 | 420 |
| 库存更新 | 30 | 80 | 350 |
为了进一步提高性能,我们实现了多级缓存:
java复制@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000));
return cacheManager;
}
java复制public Commodity getCommodityWithCache(String itemCode) {
String cacheKey = "commodity:" + itemCode;
Commodity commodity = redisTemplate.opsForValue().get(cacheKey);
if(commodity == null) {
commodity = commodityDao.findByCode(itemCode);
redisTemplate.opsForValue().set(cacheKey, commodity, 5, TimeUnit.MINUTES);
}
return commodity;
}
系统采用Docker容器化部署,大大简化了环境配置:
Docker-compose配置示例
yaml复制version: '3'
services:
app:
image: inventory-app:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=inventory
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6.0
ports:
- "6379:6379"
volumes:
mysql_data:
我们使用Prometheus + Grafana搭建了监控系统,主要监控:
Spring Boot Actuator配置
properties复制management.endpoints.web.exposure.include=*
management.metrics.export.prometheus.enabled=true
management.metrics.tags.application=inventory-system
在实际开发过程中,我总结了以下几点经验:
库存操作的原子性至关重要。我们最初没有处理好并发问题,导致库存数据不一致。最终通过以下方案解决:
审计日志必不可少。我们在后期才添加操作日志功能,导致追溯问题时很困难。建议从一开始就设计完善的日志系统:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(pointcut = "execution(* com..service..*(..))", returning = "result")
public void logServiceAccess(JoinPoint joinPoint, Object result) {
String method = joinPoint.getSignature().getName();
String params = Arrays.toString(joinPoint.getArgs());
auditLogDao.save(new AuditLog(method, params, result));
}
}
java复制@RestController
@RequestMapping("/api/goods")
public class GoodsController {
@GetMapping
public Page<Goods> listGoods(@RequestParam int page,
@RequestParam int size) {
return goodsService.getGoodsPage(page, size);
}
@GetMapping("/{code}")
public Goods getGoods(@PathVariable String code) {
return goodsService.getByCode(code);
}
@PostMapping
public ResponseEntity addGoods(@Valid @RequestBody Goods goods) {
Goods saved = goodsService.addGoods(goods);
return ResponseEntity.created(URI.create("/goods/"+saved.getItemCode()))
.body(saved);
}
}
xml复制<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
这个项目从设计到实现历时三个月,期间遇到了无数挑战,但也收获颇丰。最大的体会是:一个好的库存管理系统不仅要考虑功能实现,更要关注数据一致性和系统稳定性。希望我的这些经验能对正在开发类似系统的同学有所帮助。