1. 项目背景与技术选型
去年帮学弟调试毕业设计时,我意外发现市面上80%的JavaWeb商城项目都存在相同的架构缺陷——要么是JSP套模板的老旧项目,要么是过度封装导致二次开发困难的"黑盒"系统。这促使我重新思考:一个真正适合教学和中小型商城的SpringBoot项目应该具备哪些特质?
经过三个版本的迭代,最终形成的这套商城系统采用经典的MVC分层架构,但做了关键改进:
- 控制层:SpringBoot 2.7 + SpringMVC
- 业务层:Service层明确划分领域边界
- 持久层:MyBatis-Plus 3.5 + 动态数据源
- 视图层:Thymeleaf 3.0(兼容前后端分离方案)
特别说明:没有选择Vue等前端框架是考虑到教学场景的需求,纯JavaWeb方案更利于理解HTTP请求的全生命周期。
2. 核心模块设计与实现
2.1 用户认证模块的防重放攻击设计
大多数教程教的Session方案存在CSRF风险,我们采用JWT+Redis的双Token方案:
java复制// Token生成策略
public String generateToken(User user) {
String accessToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(user.getId().toString())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) // 30分钟过期
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
// 刷新Token设置更长有效期
String refreshToken = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(
"refresh:" + user.getId(),
refreshToken,
7, // 7天有效期
TimeUnit.DAYS);
return accessToken + "||" + refreshToken;
}
2.2 商品SKU的数据库设计陷阱
早期版本使用EAV(Entity-Attribute-Value)模型导致查询性能极差,最终方案采用:
sql复制CREATE TABLE `product_sku` (
`id` bigint(20) NOT NULL COMMENT '主键',
`product_id` bigint(20) NOT NULL COMMENT '商品ID',
`specs_json` json DEFAULT NULL COMMENT '规格键值对',
`price` decimal(10,2) NOT NULL COMMENT '售价',
`stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存',
PRIMARY KEY (`id`),
KEY `idx_product` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SKU表';
经验:JSON字段在MySQL 5.7+版本才有较好支持,低版本需改用TEXT+JSON解析方案
3. 订单系统的分布式事务处理
3.1 库存扣减的最终一致性方案
采用本地消息表+定时任务补偿机制:
- 创建订单时先扣减Redis库存
- 异步MQ消息触发数据库库存更新
- 每小时执行库存核对任务
java复制@Transactional
public Order createOrder(OrderDTO dto) {
// 1. Redis原子扣减
Long remain = redisTemplate.opsForValue()
.decrement("stock:" + dto.getSkuId(), dto.getQuantity());
if (remain < 0) {
throw new BusinessException("库存不足");
}
// 2. 创建订单记录
Order order = convertToOrder(dto);
orderMapper.insert(order);
// 3. 发送库存更新消息
mqTemplate.send("stock.update",
new StockMessage(order.getId(), dto.getSkuId(), dto.getQuantity()));
return order;
}
3.2 支付状态同步的幂等设计
支付宝回调接口必须实现幂等性:
java复制@PostMapping("/pay/callback")
public String alipayCallback(HttpServletRequest request) {
Map<String, String> params = convertParams(request);
// 1. 签名验证
if (!AlipaySignature.rsaCheckV1(params, alipayPublicKey, "UTF-8")) {
return "failure";
}
// 2. 幂等检查
String orderNo = params.get("out_trade_no");
Order order = orderService.getByNo(orderNo);
if (order.getStatus() != OrderStatus.UNPAID) {
return "success";
}
// 3. 业务处理
orderService.handlePaySuccess(orderNo);
return "success";
}
4. 性能优化实战记录
4.1 Nginx静态资源缓存配置
在nginx.conf中添加如下规则:
nginx复制location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 7d;
add_header Cache-Control "public, no-transform";
access_log off;
}
location /api/ {
proxy_pass http://backend;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 75s;
}
4.2 MyBatis二级缓存踩坑
错误配置导致脏读问题:
xml复制<!-- 错误的缓存配置 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
修正方案:
xml复制<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="1800"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>
关键点:需要实现Cache接口并重写equals/hashCode方法
5. 项目部署与监控方案
5.1 生产环境Docker编排文件
docker-compose.yml关键配置:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
redis:
image: redis:6-alpine
command: redis-server --requirepass ${REDIS_PASS}
ports:
- "6379:6379"
app:
build: .
depends_on:
- mysql
- redis
environment:
SPRING_PROFILES_ACTIVE: prod
ports:
- "8080:8080"
5.2 Prometheus监控指标暴露
SpringBoot Actuator配置:
properties复制management.endpoints.web.exposure.include=health,info,prometheus
management.metrics.tags.application=${spring.application.name}
对应的Prometheus配置:
yaml复制scrape_configs:
- job_name: 'mall'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['host.docker.internal:8080']
6. 教学指导建议
在指导本科生使用本项目时,建议重点讲解:
- 从Controller→Service→Mapper的完整调用链路
- 使用Postman测试接口的规范流程
- 数据库事务边界的设计原则
- 异常处理统一方案(含自定义异常)
常见问题解答:
-
Q:为什么我的订单查询很慢?
A:检查是否缺少user_id索引,大数据量需分库分表 -
Q:图片上传失败怎么办?
A:确认Nginx上传大小限制(默认1MB),需要调整client_max_body_size
项目二次开发建议:
- 增加ELK日志分析模块
- 集成Spring Security OAuth2
- 实现分布式ID生成方案
- 添加Prometheus+Grafana监控看板
