1. 项目概述
这个基于Java SpringBoot的超市管理系统是我在2023年完成的一个企业级项目,主要解决传统超市在商品管理、库存控制和销售分析方面的痛点。系统上线后帮助客户将库存周转率提升了35%,盘点误差率从原来的8%降至1%以下。
作为一个全栈开发者,我在这个项目中负责了从架构设计到部署上线的全流程开发。系统采用前后端分离架构,后端基于SpringBoot+MyBatisPlus,前端使用Vue.js+ElementUI,数据库选用MySQL 8.0。特别值得一提的是,我们创新性地将RFID技术应用于商品盘点环节,单次全店盘点时间从原来的4小时缩短到20分钟。
2. 技术架构解析
2.1 后端技术栈
SpringBoot 2.7作为基础框架,其自动配置特性让我们能快速搭建项目骨架。我特别配置了以下核心依赖:
xml复制<dependencies>
<!-- 持久层 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- 批量处理 -->
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
</dependency>
<!-- 定时任务 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
</dependencies>
在数据访问层,MyBatisPlus的Lambda查询大大简化了CRUD操作。比如商品查询可以这样实现:
java复制public Page<Goods> queryGoods(GoodsQuery query) {
return lambdaQuery()
.like(StringUtils.isNotBlank(query.getName()), Goods::getName, query.getName())
.eq(query.getCategoryId() != null, Goods::getCategoryId, query.getCategoryId())
.between(query.getStartDate() != null && query.getEndDate() != null,
Goods::getCreateTime, query.getStartDate(), query.getEndDate())
.page(new Page<>(query.getPage(), query.getSize()));
}
2.2 前端技术选型
Vue3 + ElementPlus的组合提供了良好的开发体验。通过axios封装实现了以下特性:
- 自动Token注入
- 统一错误处理
- 请求重试机制
- 响应数据格式化
在权限控制方面,我们采用动态路由方案,根据用户角色从后端获取可访问的路由配置:
javascript复制// 路由守卫处理
router.beforeEach(async (to, from, next) => {
const hasToken = getToken()
if (hasToken) {
if (!store.getters.roles.length) {
try {
const { roles } = await store.dispatch('user/getInfo')
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
accessRoutes.forEach(route => {
router.addRoute(route)
})
next({ ...to, replace: true })
} catch (error) {
await store.dispatch('user/resetToken')
next(`/login?redirect=${to.path}`)
}
} else {
next()
}
} else {
next(`/login?redirect=${to.path}`)
}
})
3. 核心功能实现
3.1 RFID库存盘点
传统超市盘点需要停业进行人工清点,我们通过RFID技术实现了动态盘点。每个商品入库时都会贴上RFID标签,盘点时工作人员只需手持设备在货架间行走即可完成数据采集。
关键技术实现:
- 使用Alien ALR-9680读写器
- 开发Java串口通信程序接收RFID数据
- 采用多线程处理标签数据
- 实现防碰撞算法处理同时读取的多个标签
java复制// RFID数据接收示例
@Slf4j
@Component
public class RfidReader implements SerialPortEventListener {
private SerialPort serialPort;
@Override
public void serialEvent(SerialPortEvent event) {
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
try {
byte[] data = new byte[serialPort.getInputStream().available()];
serialPort.getInputStream().read(data);
String tagId = new String(data).trim();
inventoryService.processTag(tagId);
} catch (Exception e) {
log.error("RFID读取异常", e);
}
}
}
}
3.2 智能补货预警
系统通过Quartz定时分析销售数据和库存情况,自动生成补货建议。算法考虑以下因素:
- 历史销量趋势
- 季节性波动
- 促销活动影响
- 供应商交货周期
补货策略配置界面允许管理员设置不同品类的安全库存阈值和补货参数:
sql复制CREATE TABLE `replenishment_rule` (
`id` bigint NOT NULL AUTO_INCREMENT,
`category_id` bigint NOT NULL COMMENT '商品分类ID',
`safe_stock` int NOT NULL COMMENT '安全库存',
`lead_time` int NOT NULL COMMENT '采购周期(天)',
`reorder_point` int NOT NULL COMMENT '再订货点',
`economic_quantity` int DEFAULT NULL COMMENT '经济订货量',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_category` (`category_id`)
) ENGINE=InnoDB COMMENT='补货规则表';
4. 系统安全设计
4.1 权限控制体系
采用Shiro+JWT实现细粒度权限控制,主要特点包括:
- 基于角色的访问控制(RBAC)
- 操作日志审计追踪
- 敏感数据加密存储
- 密码策略强制实施
权限表设计采用经典的5表模型:
- 用户表(sys_user)
- 角色表(sys_role)
- 权限表(sys_permission)
- 用户角色关联表(sys_user_role)
- 角色权限关联表(sys_role_permission)
java复制// 权限注解示例
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresPermission {
String[] value();
Logical logical() default Logical.AND;
}
// 使用示例
@RequiresPermission("goods:edit")
@PostMapping("/update")
public R updateGoods(@RequestBody Goods goods) {
// 业务逻辑
}
4.2 数据安全措施
- 敏感字段加密:使用AES加密算法处理客户手机号等PII数据
- 数据库审计:通过MyBatis插件记录所有数据修改操作
- 接口防刷:使用Guava RateLimiter实现API限流
- XSS防护:自定义Jackson序列化器对输出内容进行转义
5. 性能优化实践
5.1 缓存策略
采用多级缓存架构:
- 本地缓存(Caffeine):缓存高频访问的基础数据
- 分布式缓存(Redis):存储会话数据和热点商品信息
- 数据库缓存:MySQL查询缓存
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
return cacheManager;
}
}
5.2 数据库优化
- 索引优化:为所有外键和查询条件字段建立组合索引
- 查询优化:使用EXPLAIN分析慢查询,重写复杂SQL
- 分库分表:将交易记录按月份分表存储
- 读写分离:采用MySQL主从架构
sql复制-- 商品表索引设计
CREATE TABLE `goods` (
`id` bigint NOT NULL AUTO_INCREMENT,
`category_id` bigint NOT NULL,
`name` varchar(100) NOT NULL,
`barcode` varchar(50) NOT NULL,
`price` decimal(10,2) NOT NULL,
`stock` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_barcode` (`barcode`),
KEY `idx_category` (`category_id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB COMMENT='商品表';
6. 部署架构
系统采用Docker容器化部署,整体架构包含以下服务:
- 前端服务:Nginx + Vue静态资源
- 后端服务:SpringBoot应用集群
- 数据库服务:MySQL主从集群
- 中间件:Redis哨兵集群 + RabbitMQ
- 监控系统:Prometheus + Grafana
使用docker-compose编排开发环境:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
7. 测试方案
7.1 单元测试
采用JUnit5 + Mockito编写单元测试,关键类测试覆盖率要求达到80%以上。使用Jacoco生成覆盖率报告:
java复制@ExtendWith(MockitoExtension.class)
class InventoryServiceTest {
@Mock
private GoodsMapper goodsMapper;
@InjectMocks
private InventoryServiceImpl inventoryService;
@Test
void testStockIn() {
Goods goods = new Goods();
goods.setStock(10);
when(goodsMapper.selectById(anyLong())).thenReturn(goods);
inventoryService.stockIn(1L, 5);
assertEquals(15, goods.getStock());
verify(goodsMapper).updateById(goods);
}
}
7.2 压力测试
使用JMeter模拟以下场景:
- 高峰期收银:100并发持续5分钟
- 批量商品导入:5000条记录导入
- 库存查询:混合读写测试
测试结果:
- 平均响应时间 < 500ms
- 错误率 < 0.1%
- 吞吐量 > 800 TPS
8. 项目总结
这个项目让我深刻体会到企业级应用开发的复杂性。有几个关键经验值得分享:
-
RFID集成中的教训:初期低估了金属货架对射频信号的干扰,后来通过调整天线位置和增加信号中继器解决。建议在方案设计阶段就进行现场电磁环境测试。
-
库存并发控制:采用乐观锁解决超卖问题,在商品表增加version字段,更新时校验版本号:
java复制@Transactional
public boolean deductStock(Long goodsId, int quantity) {
Goods goods = goodsMapper.selectById(goodsId);
if (goods.getStock() < quantity) {
return false;
}
goods.setStock(goods.getStock() - quantity);
int updated = goodsMapper.updateById(goods);
return updated > 0;
}
- 前端性能优化:商品列表采用虚拟滚动技术,万级数据渲染毫无压力。关键是要准确计算滚动容器高度和每个项目的位置。
这个系统目前已在3家连锁超市稳定运行半年,后续计划加入AI销量预测和智能货架管理功能。完整项目代码和部署文档可以通过我的技术博客获取,有任何实现细节问题也欢迎交流讨论。