1. 项目概述:基于SpringBoot+Vue3的服装电商平台实战
去年接手了一个服装电商平台的升级项目,客户原有的PHP系统已经无法支撑日均5万UV的流量。经过技术评估,我们最终选择了SpringBoot2+Vue3的全栈方案进行重构。这个技术栈组合在性能和开发效率上达到了很好的平衡,今天就把这个项目的核心实现思路和踩坑经验分享给大家。
"衣依"平台本质上是一个标准的中型电商系统,包含商品展示、用户管理、订单处理等基础模块,特色在于针对服装行业的定制化功能:
- 多维度商品展示(支持360°展示图、视频介绍)
- 智能推荐系统(基于用户浏览行为的协同过滤算法)
- 实时库存预警(防止超卖的核心机制)
- 多端适配方案(PC+H5+小程序三端同构)
2. 技术架构设计与选型考量
2.1 后端技术栈深度解析
选择SpringBoot2而不是更新的SpringBoot3,主要基于以下考虑:
- 企业级项目对稳定性的要求高于新特性
- 现有团队对SpringBoot2的掌握更深入
- 关键依赖库(如MyBatis-Plus)对SpringBoot3的支持尚不完善
数据库选型时,我们对比了MySQL8.0和PostgreSQL12:
sql复制-- MySQL8.0的JSON字段性能测试(100万数据量)
SELECT
JSON_EXTRACT(product_attr, '$.color') AS color,
COUNT(*)
FROM products
GROUP BY color;
-- 执行时间:1.2s (PG12为0.8s)
虽然PG在复杂查询上略优,但考虑到:
- 团队MySQL运维经验更丰富
- 阿里云RDS对MySQL的支持更完善
- 电商业务90%以上是简单CRUD操作
最终选择了MySQL8.0,利用其窗口函数优化分页查询:
java复制// MyBatis-Plus分页优化方案
Page<Product> page = new Page<>(1, 20);
page.setOptimizeCountSql(false); // 禁用自动count查询
productMapper.selectPage(page,
Wrappers.<Product>query()
.eq("status", 1)
.orderByDesc("sales"));
2.2 前端架构设计要点
Vue3的组合式API大幅提升了代码组织效率,这是我们选择的核心理由。典型商品列表页的实现:
vue复制<script setup>
// 组合式API示例
const state = reactive({
products: [],
loading: false
});
const loadProducts = async (categoryId) => {
state.loading = true;
const { data } = await axios.get('/api/products', {
params: { categoryId }
});
state.products = data.map(p => ({
...p,
// 前端价格格式化
displayPrice: `¥${(p.price / 100).toFixed(2)}`
}));
state.loading = false;
};
</script>
3. 核心业务模块实现
3.1 商品管理系统
商品表设计的关键点在于支持服装行业的特殊属性:
java复制@Entity
@Table(name = "product")
public class Product {
@Id
private Long id;
// 多规格支持(JSON存储)
@Column(columnDefinition = "json")
private String specs; // {"color":["红","蓝"],"size":["S","M"]}
// 虚拟库存字段(防止超卖)
@Transient
public Integer getAvailableStock() {
return stock - lockedStock;
}
}
商品上架的并发控制方案:
java复制@Transactional
public Result addProduct(ProductDTO dto) {
// 乐观锁检查
Product exist = productMapper.selectOne(
Wrappers.<Product>query()
.eq("name", dto.getName())
.last("FOR UPDATE") // 加锁
);
if (exist != null) {
return Result.error("商品已存在");
}
Product product = new Product();
BeanUtils.copyProperties(dto, product);
productMapper.insert(product);
// 异步生成商品静态页
mqTemplate.send("generate-static-page", product.getId());
return Result.success();
}
3.2 订单支付流程实现
订单状态机的设计是核心难点,我们采用状态模式:
java复制public interface OrderState {
void pay(Order order);
void cancel(Order order);
void refund(Order order);
}
@Component("unpaidState")
public class UnpaidState implements OrderState {
@Override
public void pay(Order order) {
order.setState(OrderStatus.PAID);
// 扣减库存
inventoryService.reduceStock(order.getItems());
}
@Override
public void cancel(Order order) {
order.setState(OrderStatus.CANCELED);
}
}
支付回调的防重处理:
java复制@PostMapping("/pay/notify")
public String payNotify(@RequestBody String body,
HttpServletRequest request) {
// 1. 验签逻辑
if (!alipaySignature.verify(request)) {
return "failure";
}
// 2. 幂等处理
String orderNo = parseOrderNo(body);
if (redisTemplate.opsForValue().setIfAbsent(
"pay:notify:" + orderNo, "1", 30, TimeUnit.MINUTES)) {
// 处理业务逻辑
orderService.handlePaySuccess(orderNo);
return "success";
}
return "processing";
}
4. 性能优化实战记录
4.1 高并发场景解决方案
秒杀场景我们采用分级缓存策略:
- 本地缓存(Caffeine):商品基本信息,TTL 5秒
- Redis集群:库存计数,使用Lua脚本保证原子性
lua复制-- 库存扣减脚本
local key = KEYS[1]
local num = tonumber(ARGV[1])
local stock = tonumber(redis.call('GET', key))
if stock >= num then
redis.call('DECRBY', key, num)
return 1
end
return 0
4.2 数据库优化案例
商品搜索的慢查询优化过程:
sql复制-- 优化前(执行时间1.8s)
SELECT * FROM products
WHERE name LIKE '%卫衣%'
ORDER BY sales DESC
LIMIT 100;
-- 优化方案:
-- 1. 添加全文索引
ALTER TABLE products ADD FULLTEXT INDEX ft_idx(name);
-- 2. 改写查询(执行时间0.2s)
SELECT * FROM products
WHERE MATCH(name) AGAINST('卫衣' IN BOOLEAN MODE)
ORDER BY sales DESC
LIMIT 100;
5. 典型问题排查实录
5.1 内存泄漏排查案例
上线后出现OOM,通过MAT分析发现:
- MyBatis一级缓存未清理(SqlSession未关闭)
- 解决方案:配置自动清理
yaml复制mybatis-plus:
configuration:
local-cache-scope: statement # 避免Session级缓存
5.2 跨域问题解决方案
前后端分离时的CORS配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
// 解决预检请求缓存
.maxAge(3600)
// 允许凭证(需配合withCredentials使用)
.allowCredentials(true);
}
}
6. 部署与监控方案
6.1 容器化部署实践
Docker Compose编排示例:
yaml复制version: '3'
services:
app:
image: registry.cn-hangzhou.aliyuncs.com/yy-mall/app
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
volumes:
- ./mysql/data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=123456
6.2 监控系统搭建
Prometheus监控关键指标配置:
yaml复制# application.yml配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
7. 项目经验总结
这个项目让我深刻体会到技术选型需要平衡多方面因素。有三个特别值得分享的经验:
- 库存扣减的最终一致性方案:
- 预扣减(Redis)→ 创建订单 → 支付成功 → 真实扣减
- 支付超时(15分钟)→ 恢复预扣库存
- 商品详情页静态化策略:
- 常规商品:Nginx缓存+定时更新
- 热销商品:提前生成静态HTML
- 使用Nginx的proxy_cache实现:
nginx复制location /product {
proxy_cache mall_cache;
proxy_cache_key $uri$is_args$args;
proxy_cache_valid 200 10m;
proxy_pass http://app-server;
}
- 前后端联调的建议:
- 使用Swagger UI + YAPI管理接口文档
- 制定明确的字段命名规范(如时间字段统一用Unix时间戳)
- 建立全局错误码体系
这套系统目前已经稳定运行9个月,最高承受过10万QPS的流量冲击。整个技术栈的选择被证明是合理且高效的,特别是在快速迭代和后期维护方面展现出明显优势。对于准备采用类似架构的团队,建议重点关注领域驱动设计和监控体系的建设,这是我们在二期改进中最重要的两个方向。