超市货品信息管理系统是零售行业数字化转型的基础设施。我在为本地连锁超市实施这类系统时发现,传统Excel手工记账方式存在三大痛点:一是库存数据滞后导致频繁出现"有单无货";二是促销活动效果难以实时追踪;三是月末对账需要3-5个工作日手工汇总。这套基于SpringBoot的系统正是为解决这些实际问题而生。
系统最核心的价值在于实现了"四个实时":库存变动实时更新、销售数据实时统计、利润分析实时计算、异常情况实时预警。比如当某商品库存低于安全阈值时,系统会自动在管理界面弹出提示,并生成补货建议单。这比传统方式提前2-3天发现缺货风险,仅这一项就帮客户减少了15%的销售损失。
采用SpringBoot 2.7 + MyBatis-Plus + Vue.js的前后端分离架构,经过多个项目验证这是最适合中小型零售系统的方案。相比纯后端渲染的Thymeleaf,分离架构让手机端和POS终端可以复用同一套API。MyBatis-Plus的ActiveRecord模式大幅简化了商品CRUD操作,其内置的代码生成器可自动产出80%的基础DAO层代码。
数据库选用MySQL 8.0而非Oracle,主要基于三点考虑:一是超市业务不需要复杂的事务处理;二是JSON字段支持可以灵活存储商品扩展属性;三是GIS功能可以支持未来门店热力图分析。实测在200万条销售记录规模下,配合适当的索引优化,聚合查询响应时间仍能控制在300ms内。
系统采用经典的领域驱动设计,划分出六个核心模块:
特别说明促销引擎的设计:采用策略模式+规则引擎的组合,将促销条件抽象为"when...then..."的DSL语句。例如"当商品类别=饮料且购买数量>3时,总价打8折",这样的规则超市运营人员可以直接在后台配置,无需开发介入。
商品采用三级分类体系(大类-中类-小类),每个SKU包含核心字段:
java复制public class Goods {
private String skuCode; // 国际标准13位条码
private String goodsName;
private Integer categoryId;
private String spec; // 规格如"500ml/瓶"
private BigDecimal costPrice;
private BigDecimal sellingPrice;
private String supplierInfo;
private LocalDateTime createTime;
private String qrCode; // 包含店铺ID的加密二维码
}
特别注意价格字段使用BigDecimal而非Double,避免浮点数计算误差。在实际编码中,我们封装了Money工具类处理所有货币计算,确保四舍五入符合财务规范。
销售单采用主子表结构,主表记录交易概要,子表记录商品明细:
sql复制CREATE TABLE `sales_order` (
`order_no` varchar(20) NOT NULL COMMENT '订单号规则:门店ID+年月日+6位序列',
`total_amount` decimal(12,2) NOT NULL,
`actual_payment` decimal(12,2) NOT NULL COMMENT '实收金额',
`payment_method` tinyint NOT NULL COMMENT '1现金 2微信 3支付宝',
`cashier_id` int NOT NULL COMMENT '收银员ID',
`create_time` datetime NOT NULL,
PRIMARY KEY (`order_no`)
);
CREATE TABLE `sales_order_item` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(20) NOT NULL,
`sku_code` varchar(20) NOT NULL,
`quantity` int NOT NULL,
`unit_price` decimal(10,2) NOT NULL,
`discount_amount` decimal(10,2) DEFAULT '0.00',
PRIMARY KEY (`id`)
);
订单号设计蕴含关键业务逻辑:前4位门店编号方便分库分表,中间8位日期支持按日分区,最后6位序列保证唯一性。这种设计使系统在3年运行后,单表数据量超过500万时仍能保持高效查询。
销售看板需要实时展示今日/本月关键指标,传统方案是定时跑批生成汇总表。我们采用更高效的"增量计算+全量兜底"策略:
核心聚合SQL示例:
sql复制SELECT
DATE_FORMAT(create_time,'%Y-%m-%d') AS day,
COUNT(DISTINCT order_no) AS order_count,
SUM(total_amount) AS gross_sales,
SUM(actual_payment) AS net_sales
FROM sales_order
WHERE create_time BETWEEN ? AND ?
GROUP BY day
ORDER BY day DESC
LIMIT 30;
当管理端查询历史销售记录时,采用"游标分页"替代传统LIMIT方案:
java复制public PageResult<SalesOrder> queryOrders(OrderQuery query) {
// 第一页使用常规分页
if (query.getCursor() == null) {
return baseMapper.selectPage(query, wrapper);
}
// 后续页使用ID游标
wrapper.lt("id", query.getCursor())
.orderByDesc("id")
.last("LIMIT " + query.getSize());
List<SalesOrder> records = baseMapper.selectList(wrapper);
Long nextCursor = records.isEmpty() ? null : records.get(records.size()-1).getId();
return new PageResult<>(records, nextCursor);
}
这种方案在10万级数据量时,查询速度比传统分页快3-5倍,且不会出现"越往后越慢"的问题。
初期采用"先查询再更新"的库存扣减逻辑,在高并发场景下出现超卖。最终通过三种方案组合解决:
数据库层面:使用乐观锁
sql复制UPDATE inventory
SET stock = stock - ?
WHERE sku_code = ? AND stock >= ? AND version = ?
应用层面:对同一SKU加分布式锁
java复制String lockKey = "stock:" + skuCode;
try {
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) throw new BusinessException("操作太频繁");
// 执行库存操作
} finally {
redisTemplate.delete(lockKey);
}
最终兜底:每日库存校对任务
某次店庆活动期间,复杂促销规则导致结算接口RT飙升至2秒。通过以下优化降至200ms内:
优化前后的性能对比:
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 平均响应时间 | 1850ms | 172ms |
| 99线 | 3.2s | 320ms |
| TPS | 45 | 210 |
推荐使用Docker Compose部署,关键配置示例:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
redis:
image: redis:6
command: redis-server --requirepass ${REDIS_PASS}
ports:
- "6379:6379"
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
特别注意:MySQL需调整以下参数以适应零售业务特点:
ini复制[mysqld]
innodb_buffer_pool_size = 2G # 建议分配物理内存的50%-70%
innodb_io_capacity = 2000 # SSD硬盘建议值
transaction_isolation = READ-COMMITTED
binlog_format = ROW
使用Spring Boot Actuator + Prometheus + Grafana搭建监控体系,关键指标包括:
告警规则示例(PromQL):
promql复制# 库存服务调用失败率告警
sum(rate(inventory_service_errors_total[1m])) by (instance)
/
sum(rate(inventory_service_calls_total[1m])) by (instance)
> 0.05
在实际运营中,我们持续收集到两类高频需求:一是供应商协同门户,让供货商能自助查看商品动销情况;二是智能补货预测,基于历史销售数据给出采购建议。这引出了三个技术升级方向:
当前系统已预留了这些扩展点,比如在商品模型中增加了predicted_sales字段,在架构设计上遵循了"核心稳定、周边灵活"的原则。