去年接手一个社区电商平台的二次开发项目时,我深刻体会到一套健壮的网上超市系统对中小商家的价值。这个基于SpringBoot+Vue的网上超市管理系统,正是我在多个实际项目经验基础上提炼出的通用解决方案。相比传统实体超市,线上系统能突破时空限制,将营业时间延长至24小时,覆盖半径从500米扩展到全城,这正是某生鲜平台接入系统后订单量提升3倍的关键。
系统采用现在主流的前后端分离架构,后端使用SpringBoot 2.7 + MyBatis-Plus 3.5的组合,前端采用Vue 3 + Element Plus,数据库选用MySQL 8.0。这种技术栈选择既保证了开发效率,又能应对日均10万级订单的业务压力。特别值得一提的是,我们在用户认证方案上采用了JWT+RBAC的组合,既解决了无状态会话问题,又实现了细粒度的权限控制。
用户体系采用分级设计,区分普通用户、VIP用户和管理员三种角色。密码存储没有使用简单的MD5,而是采用BCryptPasswordEncoder进行加盐哈希,这是我们在某次安全审计后做的重点改进。核心用户表字段设计如下:
sql复制CREATE TABLE `user` (
`user_id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password` varchar(100) NOT NULL COMMENT 'BCrypt加密密码',
`email` varchar(50) NOT NULL COMMENT '绑定邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`register_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`account_status` tinyint NOT NULL DEFAULT '1' COMMENT '0-禁用 1-启用',
`user_type` tinyint NOT NULL DEFAULT '0' COMMENT '0-普通用户 1-VIP 2-管理员',
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键点:所有敏感操作如密码修改、绑定时都会进行二次验证,我们通过Spring Security的ACL机制实现了操作权限的实时校验。
商品系统支持多级分类(最多三级),采用左右值编码的树形结构存储,这是处理层级关系查询的最优方案。价格字段使用DECIMAL(10,2)类型,避免浮点数精度问题。库存管理实现了乐观锁机制,防止超卖:
java复制@Update("UPDATE product SET stock = stock - #{quantity}, version = version + 1
WHERE product_id = #{productId} AND version = #{version}")
int reduceStockWithVersion(@Param("productId") Long productId,
@Param("quantity") int quantity,
@Param("version") int version);
商品搜索功能整合了Elasticsearch,支持按分类、价格区间、销量等多维度筛选,响应时间控制在200ms以内。
订单生命周期管理是系统的核心难点,我们采用状态模式(State Pattern)实现了灵活的状态流转:
code复制待支付 --支付成功--> 待发货 --发货--> 待收货
\--支付超时--> 已取消 \--用户取消--> 已退款
状态变更通过Spring StateMachine实现,关键配置如下:
xml复制<transition source="UNPAID" target="PAID" event="PAY_SUCCESS"/>
<transition source="UNPAID" target="CANCELLED" event="PAY_TIMEOUT"/>
<transition source="PAID" target="SHIPPED" event="DELIVER_GOODS"/>
支付模块支持微信支付和支付宝双渠道,采用策略模式封装不同支付方式的差异。特别注意处理了支付结果异步通知的幂等性问题:
java复制@Transactional
public void handlePayNotify(String orderNo, String transactionId) {
Order order = orderMapper.selectByOrderNo(orderNo);
if (order.getStatus() != OrderStatus.UNPAID) {
log.warn("订单[{}]已处理过支付通知", orderNo);
return; // 幂等处理
}
// 更新订单状态
orderMapper.updateStatus(orderNo, OrderStatus.PAID);
// 记录支付流水
paymentService.recordPayment(order, transactionId);
}
商品详情页采用多级缓存方案:
通过@Cacheable注解实现透明缓存:
java复制@Cacheable(value = "product", key = "#productId",
unless = "#result == null")
public Product getProductById(Long productId) {
return productMapper.selectById(productId);
}
针对订单表数据量大的特点(实测3个月可达500万条),我们做了以下优化:
分库配置示例:
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1,...,ds15
sharding:
tables:
order:
actual-data-nodes: ds$->{0..15}.order_$->{0..15}
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: ds$->{user_id % 16}
所有API接口都经过三层防护:
针对常见攻击手段的防护措施:
系统采用容器化部署方案:
典型的service配置:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
spec:
containers:
- name: order-service
image: registry.example.com/order:v1.2.0
ports:
- containerPort: 8080
resources:
limits:
cpu: "2"
memory: 2Gi
在首次实现"扣库存->创建订单"流程时,没有考虑分布式事务,导致出现过库存扣减成功但订单创建失败的情况。最终采用Seata的AT模式解决:
java复制@GlobalTransactional
public void createOrder(OrderDTO orderDTO) {
// 扣减库存
productService.reduceStock(orderDTO.getProductId(), orderDTO.getQuantity());
// 创建订单
orderService.create(orderDTO);
// 扣减余额
accountService.debit(orderDTO.getUserId(), orderDTO.getAmount());
}
商品信息更新后,出现过缓存未及时失效导致用户看到旧数据的情况。解决方案:
关键代码:
java复制@Transactional
public void updateProduct(Product product) {
productMapper.updateById(product);
// 立即删除缓存
redisTemplate.delete("product::" + product.getId());
// 发布缓存失效消息
redisTemplate.convertAndSend("product.update", product.getId());
}
这套系统已经在三个不同规模的超市项目中得到验证,后续可以考虑的扩展方向包括:
特别在移动端适配方面,我们正在试验Uniapp框架,一套代码同时生成iOS和Android应用,预计能降低40%的客户端开发成本。