1. 项目概述
最近在重构一个电商项目,采用了SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0的技术栈,这套组合在实际开发中表现非常出色。前后端分离的架构让团队协作更加高效,后端API开发速度提升了近40%,前端页面响应时间控制在300ms以内。下面我会详细拆解这个项目的技术实现,包括架构设计、核心模块和性能优化策略。
这个系统主要面向中小型电商企业,包含完整的用户管理、商品展示、订单处理和支付流程。相比传统电商系统,我们在性能和安全方面做了大量优化,特别是在高并发场景下,系统稳定性得到了显著提升。
2. 技术架构设计
2.1 整体架构方案
我们采用前后端分离的架构模式,这种设计带来了几个明显优势:
- 开发效率提升:前后端可以并行开发,通过API文档约定接口规范
- 性能优化空间大:前端可以做组件级缓存,后端可以专注业务逻辑
- 技术栈灵活:前后端可以独立升级技术栈
后端采用经典的MVC分层:
- Controller层:处理HTTP请求,参数校验
- Service层:核心业务逻辑实现
- DAO层:数据库访问,通过MyBatis-Plus简化操作
2.2 技术栈选型
后端技术栈:
- SpringBoot 2.7.3:快速构建微服务
- MyBatis-Plus 3.5.3:强大的ORM框架
- MySQL 8.0.28:事务型数据库
- Redis 6.2.6:缓存和会话管理
- JWT:无状态认证方案
前端技术栈:
- Vue 3.2.45:响应式前端框架
- Element Plus 2.2.28:UI组件库
- Axios 1.1.3:HTTP客户端
- ECharts 5.3.3:数据可视化
提示:MySQL 8.0相比5.7版本在JSON支持、窗口函数和性能上有显著提升,建议新项目直接使用8.0版本。
3. 数据库设计
3.1 核心表结构
用户表(user)设计考虑了多种登录方式:
sql复制CREATE TABLE `user` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录名',
`password_hash` varchar(100) NOT NULL COMMENT 'BCrypt加密',
`email` varchar(100) NOT NULL COMMENT '可用于登录',
`phone_number` varchar(20) DEFAULT NULL COMMENT '可用于登录',
`register_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_login` datetime DEFAULT NULL,
`user_status` tinyint NOT NULL DEFAULT '1' COMMENT '0-禁用 1-启用',
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`),
KEY `idx_phone` (`phone_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
商品表(product)设计支持多维度查询:
sql复制CREATE TABLE `product` (
`product_id` bigint NOT NULL AUTO_INCREMENT,
`product_name` varchar(100) NOT NULL,
`category_id` int NOT NULL,
`price` decimal(10,2) NOT NULL,
`stock_quantity` int NOT NULL DEFAULT '0',
`description` text,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`is_on_sale` tinyint NOT NULL DEFAULT '0',
`sales_volume` int DEFAULT '0' COMMENT '销量',
PRIMARY KEY (`product_id`),
KEY `idx_category` (`category_id`),
KEY `idx_sales` (`sales_volume`),
FULLTEXT KEY `ft_name_desc` (`product_name`,`description`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3.2 索引优化实践
在订单表(order)中,我们建立了复合索引来优化查询:
sql复制CREATE INDEX idx_user_status ON `order` (user_id, order_status);
CREATE INDEX idx_create_time ON `order` (create_time);
这种设计使得以下查询非常高效:
java复制// 查询用户待支付订单
List<Order> orders = orderMapper.selectList(
new LambdaQueryWrapper<Order>()
.eq(Order::getUserId, userId)
.eq(Order::getOrderStatus, 0)
.orderByDesc(Order::getCreateTime)
);
4. 核心功能实现
4.1 用户认证模块
采用JWT+Redis的方案实现无状态认证:
- 用户登录成功后生成JWT token
- token中存储用户基础信息和权限标识
- Redis缓存用户权限数据,避免频繁查库
核心认证逻辑:
java复制public String login(LoginDTO dto) {
User user = userMapper.selectOne(
new LambdaQueryWrapper<User>()
.eq(User::getUsername, dto.getUsername())
);
if(user == null || !passwordEncoder.matches(dto.getPassword(), user.getPasswordHash())){
throw new BusinessException("用户名或密码错误");
}
String token = JwtUtil.generateToken(user.getUserId());
redisTemplate.opsForValue().set(
"USER:AUTH:" + user.getUserId(),
new UserAuthVO(user),
2, TimeUnit.HOURS
);
return token;
}
4.2 商品搜索功能
结合MySQL全文索引和Elasticsearch实现多级搜索:
- 简单搜索:使用MySQL的FULLTEXT索引
- 高级搜索:同步数据到Elasticsearch集群
商品搜索Service实现:
java复制public Page<ProductVO> searchProducts(ProductQuery query) {
if(StringUtils.isBlank(query.getKeyword())){
// 无关键词时走普通分页查询
return baseMapper.selectPage(
new Page<>(query.getPage(), query.getSize()),
buildQueryWrapper(query)
).convert(this::convertToVO);
}
// 有关键词时判断是否使用ES
if(esEnabled && query.isAdvancedSearch()){
return searchFromES(query);
}else{
return searchFromMySQL(query);
}
}
private LambdaQueryWrapper<Product> buildQueryWrapper(ProductQuery query) {
return new LambdaQueryWrapper<Product>()
.eq(query.getCategoryId() != null, Product::getCategoryId, query.getCategoryId())
.eq(Product::getIsOnSale, 1)
.between(query.getMinPrice() != null && query.getMaxPrice() != null,
Product::getPrice, query.getMinPrice(), query.getMaxPrice())
.orderByDesc(query.isSortBySales(), Product::getSalesVolume)
.orderByDesc(query.isSortByPrice(), Product::getPrice)
.orderByDesc(Product::getUpdateTime);
}
5. 性能优化策略
5.1 缓存设计
采用多级缓存架构:
- 本地缓存(Caffeine):缓存静态数据
- Redis缓存:缓存热点数据
- 数据库:持久化存储
商品详情缓存方案:
java复制public ProductDetailVO getProductDetail(Long productId) {
// 1. 先查本地缓存
ProductDetailVO detail = localCache.getIfPresent(productId);
if(detail != null) {
return detail;
}
// 2. 查Redis缓存
String redisKey = "PRODUCT:" + productId;
detail = (ProductDetailVO)redisTemplate.opsForValue().get(redisKey);
if(detail != null) {
localCache.put(productId, detail);
return detail;
}
// 3. 查数据库
detail = productMapper.selectDetailById(productId);
if(detail != null) {
// 异步加载关联数据
CompletableFuture.runAsync(() -> loadRelatedData(detail));
// 写入缓存
redisTemplate.opsForValue().set(redisKey, detail, 30, TimeUnit.MINUTES);
localCache.put(productId, detail);
}
return detail;
}
5.2 高并发处理
秒杀场景下的解决方案:
- Redis预减库存:避免超卖
- 消息队列削峰:异步处理订单
- 限流措施:保护系统稳定性
秒杀核心逻辑:
java复制public boolean seckill(Long userId, Long productId) {
// 1. 校验活动是否进行中
if(!isSeckillActive(productId)) {
throw new BusinessException("活动未开始或已结束");
}
// 2. Redis原子减库存
Long remain = redisTemplate.opsForValue().decrement("SECKILL:STOCK:" + productId);
if(remain == null || remain < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment("SECKILL:STOCK:" + productId);
throw new BusinessException("库存不足");
}
// 3. 生成预订单
SeckillOrder order = new SeckillOrder();
order.setUserId(userId);
order.setProductId(productId);
order.setOrderId(snowflake.nextId());
// 4. 发送MQ消息
rabbitTemplate.convertAndSend(
"seckill.order.exchange",
"seckill.order.create",
order
);
return true;
}
6. 安全防护措施
6.1 接口安全
- SQL注入防护:使用MyBatis-Plus的Wrapper构建查询条件
- XSS防护:前端使用vue-dompurify-html插件
- CSRF防护:关键操作需要验证token
安全配置示例:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/product/list").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.exceptionHandling()
.authenticationEntryPoint(new JwtAuthenticationEntryPoint());
}
}
6.2 数据安全
- 密码存储:使用BCrypt强哈希算法
- 敏感数据:手机号、邮箱等字段加密存储
- 日志脱敏:避免记录敏感信息
密码加密实现:
java复制@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
// 用户注册时加密密码
public void register(RegisterDTO dto) {
if(userMapper.exists(
new LambdaQueryWrapper<User>()
.eq(User::getUsername, dto.getUsername())
)){
throw new BusinessException("用户名已存在");
}
User user = new User();
user.setUsername(dto.getUsername());
user.setPasswordHash(passwordEncoder.encode(dto.getPassword()));
user.setEmail(dto.getEmail());
userMapper.insert(user);
}
7. 部署与监控
7.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
MYSQL_DATABASE: mall
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6.2-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
7.2 监控方案
- Spring Boot Actuator:暴露健康检查端点
- Prometheus + Grafana:监控系统指标
- ELK:日志收集与分析
Actuator配置示例:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
prometheus:
enabled: true
8. 开发经验分享
在实际开发中,我们遇到并解决了几个典型问题:
-
MyBatis-Plus逻辑删除与唯一索引冲突
- 解决方案:在唯一索引字段上添加删除状态条件,如:
sql复制ALTER TABLE user ADD UNIQUE idx_username (username, is_deleted);
- 解决方案:在唯一索引字段上添加删除状态条件,如:
-
Vue3组合式API的合理使用
- 经验:将相关逻辑组织在同一个setup函数中,使用自定义hook抽取复用逻辑
-
分布式环境下的缓存一致性问题
- 解决方案:采用Redis发布订阅机制通知各节点清理本地缓存
-
前端长列表性能优化
- 实践:使用虚拟滚动技术,只渲染可视区域内的元素
这个项目从技术选型到最终上线历时3个月,期间我们不断调整优化架构设计。最大的收获是认识到良好的分层设计和清晰的模块边界对项目可维护性的重要性。特别是在电商这种业务逻辑复杂的系统中,合理的代码组织能显著降低后期维护成本。