1. 项目概述:欢迪迈手机商城系统技术全景
这个基于Java技术栈的手机商城系统,是我去年带队为某3C零售品牌交付的线上销售平台。系统采用前后端分离架构,后端基于SpringBoot+MyBatis实现业务逻辑,前端使用Vue+ElementUI构建管理后台,MySQL作为核心数据存储。上线后支撑日均3万+订单量,峰值QPS达到800+,是典型的B2C电商解决方案。
从技术选型来看,这套组合拳在中小型电商项目中非常具有代表性:SpringBoot的快速开发特性完美适配互联网项目的迭代节奏,MyBatis的灵活SQL编写满足电商复杂的查询需求,Vue+ElementUI的组合则让管理后台开发效率提升40%以上。特别在促销活动期间,这套架构展现了良好的弹性扩展能力。
2. 技术架构深度解析
2.1 后端技术栈设计
SpringBoot 2.7版本作为基础框架,其自动配置机制大幅减少了XML配置。我们特别优化了以下配置:
# 应用启动端口 server.port=8088 # MyBatis配置 mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.handimai.model # 启用事务管理 spring.transaction.default-timeout=30s数据库连接池选用HikariCP,这是目前性能最好的Java连接池实现。在application.yml中配置了连接池参数:
spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000特别注意:连接池大小设置需要根据实际DB服务器配置调整,一般建议(maximum-pool-size) = (CPU核心数 * 2) + 有效磁盘数
2.2 前端架构设计
采用Vue 2.6 + ElementUI 2.15的组合,通过vue-cli 4搭建项目骨架。关键目录结构如下:
src/ ├── api/ # 接口定义 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # Vuex状态管理 ├── utils/ # 工具类 └── views/ # 页面组件在main.js中全局引入ElementUI并配置按需加载:
import Vue from 'vue' import { Button, Table, Form } from 'element-ui' Vue.use(Button) Vue.use(Table) Vue.use(Form)3. 核心功能模块实现
3.1 商品管理模块
采用三级分类设计(手机→品牌→型号),数据库表结构如下:
CREATE TABLE `product` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL COMMENT '三级分类ID', `name` varchar(100) NOT NULL, `subtitle` varchar(200) DEFAULT NULL, `price` decimal(10,2) NOT NULL, `stock` int(11) NOT NULL DEFAULT '0', `status` tinyint(4) DEFAULT '1' COMMENT '1-在售 0-下架', `create_time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;后端接口采用RESTful风格设计:
@RestController @RequestMapping("/api/product") public class ProductController { @Autowired private ProductService productService; @GetMapping("/{id}") public Result<ProductDetailVO> getDetail(@PathVariable Long id) { return Result.success(productService.getDetail(id)); } @PostMapping("/list") public Result<PageInfo<ProductVO>> getList(@RequestBody ProductQueryDTO query) { return Result.success(productService.queryByPage(query)); } }3.2 购物车与订单系统
购物车采用Redis Hash结构存储,Key设计为"cart:{userId}",field为商品ID,value为JSON格式的购物项数据。订单状态机设计如下:
public enum OrderStatus { UNPAID(0, "待支付"), PAID(1, "已支付"), SHIPPED(2, "已发货"), COMPLETED(3, "已完成"), CANCELLED(4, "已取消"); // 省略构造函数和getter }订单创建时采用分布式锁防止重复提交:
public String createOrder(OrderCreateDTO dto) { String lockKey = "order:lock:" + dto.getUserId(); try { // 尝试获取锁,有效期3秒 boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS); if (!locked) { throw new BusinessException("操作太频繁,请稍后再试"); } // 真正的订单创建逻辑 return orderService.create(dto); } finally { redisTemplate.delete(lockKey); } }4. 性能优化实战记录
4.1 缓存策略设计
采用多级缓存架构:
- 本地缓存(Caffeine):缓存热点商品信息,TTL 5分钟
- Redis缓存:缓存商品详情、分类数据等,TTL 30分钟
- MySQL:持久化存储
缓存更新策略采用"先更新DB再删除缓存"的方式:
@Transactional public void updateProduct(ProductUpdateDTO dto) { // 1. 更新数据库 productMapper.update(dto); // 2. 删除缓存 String cacheKey = "product:" + dto.getId(); redisTemplate.delete(cacheKey); // 3. 发送MQ消息更新其他缓存 mqProducer.sendProductUpdateMsg(dto.getId()); }4.2 数据库优化
针对商品列表查询的慢SQL优化:
-- 优化前 SELECT * FROM product WHERE status = 1 ORDER BY create_time DESC; -- 优化后 SELECT id,name,price,stock FROM product WHERE status = 1 AND category_id = ? ORDER BY sales_volume DESC LIMIT ?,?;建立复合索引:
ALTER TABLE product ADD INDEX idx_category_status (category_id, status);5. 部署与监控方案
5.1 容器化部署
使用Docker Compose编排服务,docker-compose.yml关键配置:
version: '3' services: mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} ports: - "3306:3306" volumes: - ./mysql/data:/var/lib/mysql redis: image: redis:6 ports: - "6379:6379" command: redis-server --requirepass ${REDIS_PASSWORD} backend: build: ./backend ports: - "8080:8080" depends_on: - mysql - redis5.2 监控配置
SpringBoot Actuator接入Prometheus监控:
@Configuration public class MetricsConfig { @Bean MeterRegistryCustomizer<PrometheusMeterRegistry> configureMetrics() { return registry -> registry.config().commonTags("application", "handimai-mall"); } }在application.yml中暴露监控端点:
management: endpoints: web: exposure: include: health,info,prometheus metrics: tags: application: ${spring.application.name}6. 典型问题排查实录
6.1 库存超卖问题
初期直接使用MySQL更新库存:
UPDATE product SET stock = stock - 1 WHERE id = ? AND stock >= 1优化方案:引入Redis分布式锁+乐观锁
public boolean reduceStock(Long productId, int quantity) { String lockKey = "product:lock:" + productId; try { // 获取商品级锁 Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 1, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { // 乐观锁版本号控制 Product product = productMapper.selectById(productId); if (product.getStock() >= quantity) { int rows = productMapper.updateStockWithVersion( productId, quantity, product.getVersion()); return rows > 0; } } return false; } finally { redisTemplate.delete(lockKey); } }6.2 前端内存泄漏
在商品列表页发现切换路由时内存持续增长。通过Chrome Memory工具定位到问题:
// 错误示例:在created钩子中注册全局事件未移除 created() { window.addEventListener('resize', this.handleResize) } // 正确做法:在beforeDestroy中移除 beforeDestroy() { window.removeEventListener('resize', this.handleResize) }7. 安全防护措施
7.1 接口防刷设计
采用Guava RateLimiter实现API限流:
@Aspect @Component public class RateLimitAspect { private final Map<String, RateLimiter> limiterMap = new ConcurrentHashMap<>(); @Around("@annotation(rateLimit)") public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable { String key = getRequestKey(); // 根据IP+接口生成key RateLimiter limiter = limiterMap.computeIfAbsent(key, k -> RateLimiter.create(rateLimit.value())); if (!limiter.tryAcquire()) { throw new BusinessException("请求过于频繁,请稍后再试"); } return joinPoint.proceed(); } }7.2 SQL注入防护
MyBatis严格使用参数化查询,禁止拼接SQL:
<!-- 错误示例 --> <select id="queryByCondition" resultType="Product"> SELECT * FROM product WHERE name LIKE '%${name}%' </select> <!-- 正确做法 --> <select id="queryByCondition" resultType="Product"> SELECT * FROM product WHERE name LIKE CONCAT('%',#{name},'%') </select>8. 项目演进方向
技术架构的持续优化路线:
- 服务拆分:将订单、商品、用户等服务拆分为独立微服务
- 引入Kafka:实现订单创建、支付成功等事件的异步处理
- 灰度发布:通过SpringCloud Gateway实现API级别的流量控制
- 全链路压测:使用JMeter+InfluxDB+Grafana搭建压测平台
在商品详情页已尝试实现静态化方案:通过Vue SSR生成静态HTML,配合Nginx缓存,使页面加载时间从1.2s降低到400ms左右。具体实现采用vue-server-renderer:
const renderer = require('vue-server-renderer').createRenderer() const app = require('./app') server.get('*', (req, res) => { const vm = app.createApp() renderer.renderToString(vm, (err, html) => { if (err) { res.status(500).end('Internal Server Error') return } res.end(` <!DOCTYPE html> <html> <head>...</head> <body>${html}</body> </html> `) }) })