1. 项目概述:SpringBoot广理文创产品线上销售平台
这个基于SpringBoot框架开发的文创产品线上销售平台,是我为广东理工学院设计的电商系统实战项目。系统采用经典的MVC分层架构,前端使用Thymeleaf模板引擎,后端基于SpringBoot 2.7.4开发,数据库选用MySQL 8.0,完整实现了商品展示、购物车、订单管理、支付对接等电商核心功能模块。
项目源码(编号46413)已经过完整测试,包含详细的数据库脚本和API文档。相比市面上简单的Demo项目,这个平台特别强化了高并发场景下的库存管理方案,采用Redis分布式锁防止超卖,并实现了基于Spring Scheduler的定时订单状态检查任务。
2. 技术架构解析
2.1 核心框架选型
选择SpringBoot作为基础框架主要基于以下考量:
- 自动配置特性大幅减少XML配置(对比传统Spring MVC项目配置量减少约70%)
- 内嵌Tomcat服务器简化部署流程
- Starter依赖机制快速集成Redis、MyBatis等常用组件
- Actuator端点提供完善的系统监控能力
技术栈组成:
markdown复制- 前端:Thymeleaf + Bootstrap 5 + jQuery
- 后端:SpringBoot 2.7.4 + Spring Security
- 数据库:MySQL 8.0 + MyBatis-Plus
- 中间件:Redis 6.2(缓存/分布式锁)
- 开发工具:IDEA + Maven 3.8
2.2 数据库设计要点
商品核心表结构设计:
sql复制CREATE TABLE `product` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '雪花ID',
`name` varchar(100) NOT NULL COMMENT '商品名称',
`price` decimal(10,2) NOT NULL COMMENT '售价',
`stock` int NOT NULL DEFAULT '0' COMMENT '库存',
`category_id` int DEFAULT NULL COMMENT '分类ID',
`detail` text COMMENT '商品详情',
`status` tinyint DEFAULT '1' COMMENT '状态(1-上架 0-下架)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设计特点:
- 采用雪花算法生成分布式ID(避免自增ID暴露业务量)
- 价格字段使用DECIMAL精确存储(避免Float精度问题)
- 建立分类索引提升查询效率
- 添加状态字段控制商品上下架
3. 核心功能实现
3.1 商品展示模块
商品列表页实现关键技术:
java复制@GetMapping("/products")
public String productList(
@RequestParam(required = false) Integer categoryId,
@RequestParam(defaultValue = "1") Integer pageNum,
Model model) {
// 构建查询条件
LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Product::getStatus, 1); // 只查询上架商品
if (categoryId != null) {
queryWrapper.eq(Product::getCategoryId, categoryId);
}
// 分页查询
Page<Product> page = new Page<>(pageNum, 10);
IPage<Product> productPage = productService.page(page, queryWrapper);
// 获取分类信息
List<Category> categories = categoryService.list();
model.addAttribute("pageInfo", productPage);
model.addAttribute("categories", categories);
return "product/list";
}
前端页面关键优化:
- 使用Bootstrap卡片布局展示商品
- 实现懒加载图片(滚动到视口再加载)
- 添加分类筛选和搜索框联动
- 分页组件支持URL参数保持
3.2 购物车系统设计
购物车实现方案对比:
| 方案类型 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| Cookie存储 | 纯前端存储 | 减轻服务器压力 | 容量限制(4KB) |
| Session存储 | 服务器内存存储 | 数据安全 | 集群环境下需同步 |
| 数据库存储 | 持久化到DB | 数据不丢失 | 读写性能较低 |
| Redis存储 | 内存数据库存储 | 高性能+持久化 | 需要额外中间件 |
本项目采用Redis Hash结构存储购物车:
java复制public void addToCart(Long userId, Long productId, Integer quantity) {
String key = "cart:" + userId;
String field = productId.toString();
// 检查商品是否存在且库存充足
Product product = productService.getById(productId);
if (product == null || product.getStatus() == 0) {
throw new BusinessException("商品已下架");
}
// Redis原子操作
redisTemplate.opsForHash().increment(key, field, quantity);
}
3.3 订单与支付流程
订单状态机设计:
mermaid复制stateDiagram
[*] --> PENDING
PENDING --> PAID: 支付成功
PENDING --> CANCELLED: 用户取消
PENDING --> TIMEOUT: 30分钟未支付
PAID --> SHIPPED: 发货
SHIPPED --> COMPLETED: 确认收货
SHIPPED --> REFUNDING: 申请退款
REFUNDING --> REFUNDED: 退款成功
支付对接关键代码(以支付宝为例):
java复制@PostMapping("/pay")
public String createPayment(@RequestParam Long orderId,
HttpServletRequest request) {
// 1. 验证订单状态
Order order = orderService.getById(orderId);
if (order.getStatus() != OrderStatus.PENDING) {
throw new BusinessException("订单状态异常");
}
// 2. 创建支付请求
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
privateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2");
AlipayTradePagePayRequest payRequest = new AlipayTradePagePayRequest();
payRequest.setReturnUrl(returnUrl);
payRequest.setNotifyUrl(notifyUrl);
// 3. 构造业务参数
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", order.getOrderNo());
bizContent.put("total_amount", order.getActualPrice());
bizContent.put("subject", "广理文创订单-" + order.getOrderNo());
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
payRequest.setBizContent(bizContent.toString());
// 4. 发起支付
return alipayClient.pageExecute(payRequest).getBody();
}
4. 高级特性实现
4.1 高并发库存管理
秒杀场景下的库存扣减方案:
java复制public boolean reduceStock(Long productId, Integer quantity) {
String lockKey = "product_lock:" + productId;
String uuid = UUID.randomUUID().toString();
try {
// 获取分布式锁(设置10秒过期)
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, uuid, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("系统繁忙,请重试");
}
// 验证库存
Product product = productService.getById(productId);
if (product.getStock() < quantity) {
throw new BusinessException("库存不足");
}
// 扣减库存(乐观锁)
UpdateWrapper<Product> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", productId)
.eq("stock", product.getStock())
.set("stock", product.getStock() - quantity);
return productService.update(updateWrapper);
} finally {
// 释放锁(Lua脚本保证原子性)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
uuid);
}
}
4.2 定时任务设计
订单超时自动取消实现:
java复制@Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次
public void cancelTimeoutOrders() {
// 查询超时未支付订单
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Order::getStatus, OrderStatus.PENDING)
.le(Order::getCreateTime,
LocalDateTime.now().minusMinutes(30));
List<Order> timeoutOrders = orderService.list(queryWrapper);
// 批量更新状态
timeoutOrders.forEach(order -> {
order.setStatus(OrderStatus.TIMEOUT);
orderService.updateById(order);
// 释放库存
order.getItems().forEach(item -> {
productService.addStock(item.getProductId(), item.getQuantity());
});
// 记录日志
log.info("订单超时取消:{}", order.getOrderNo());
});
}
5. 部署与运维
5.1 多环境配置
SpringBoot Profile配置示例:
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/shop_dev
username: devuser
password: dev123
redis:
host: localhost
port: 6379
---
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/shop_prod
username: ${DB_USER}
password: ${DB_PASSWORD}
redis:
host: redis-cluster
port: 6379
password: ${REDIS_PASSWORD}
启动时指定Profile:
bash复制java -jar shop.jar --spring.profiles.active=prod
5.2 监控与日志
Actuator监控端点配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
prometheus:
enabled: true
日志切割配置(Logback):
xml复制<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
6. 项目优化方向
6.1 性能优化建议
-
缓存策略升级:
- 商品详情页加入二级缓存(Redis + Caffeine)
- 实现缓存预热机制(定时任务加载热点数据)
- 使用BloomFilter防止缓存穿透
-
SQL优化:
java复制// 反例:N+1查询问题 List<Order> orders = orderService.list(); orders.forEach(order -> { List<OrderItem> items = orderItemService.getByOrderId(order.getId()); order.setItems(items); }); // 正例:使用MyBatis-Plus连表查询 orderService.listWithItems(); -
静态资源优化:
- 启用Gzip压缩
- 配置CDN加速
- 实现图片懒加载
6.2 安全加固措施
-
接口防刷:
java复制@RateLimiter(value = 10, key = "#userId") // 10次/秒 @PostMapping("/api/cart/add") public Result addToCart(@RequestParam Long productId) { // 业务逻辑 } -
SQL注入防护:
- 始终使用MyBatis参数绑定
- 禁止字符串拼接SQL
- 定期使用SQL注入工具扫描
-
XSS防护:
java复制@ControllerAdvice public class XssProtectionAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 使用HTML过滤器处理响应内容 return XssFilterUtils.filter(body); } }
7. 源码结构与使用说明
项目目录结构:
code复制src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── gditc/
│ │ ├── config/ # 配置类
│ │ ├── controller/ # 控制器
│ │ ├── entity/ # 实体类
│ │ ├── enums/ # 枚举类
│ │ ├── exception/ # 异常处理
│ │ ├── mapper/ # MyBatis Mapper
│ │ ├── service/ # 服务层
│ │ ├── util/ # 工具类
│ │ └── ShopApplication.java # 启动类
│ ├── resources/
│ │ ├── static/ # 静态资源
│ │ ├── templates/ # 模板文件
│ │ ├── application.yml # 主配置
│ │ └── application-dev.yml # 开发环境配置
└── test/ # 测试代码
启动步骤:
- 导入MySQL脚本(位于
docs/sql目录) - 修改
application-dev.yml中的数据库配置 - 安装Redis并确保服务运行
- 运行
ShopApplication主类 - 访问 http://localhost:8080
测试账号:
- 普通用户:user123 / 123456
- 管理员:admin / admin123
8. 开发心得与避坑指南
-
MyBatis-Plus主键策略:
- 默认使用雪花算法(需配置
@TableId(type = IdType.ASSIGN_ID)) - 自增ID需在实体类添加
@TableId(type = IdType.AUTO) - 注意:MySQL需设置主键自增(
AUTO_INCREMENT)
- 默认使用雪花算法(需配置
-
事务失效场景:
java复制// 反例:同类方法调用导致事务失效 public void createOrder(OrderDTO dto) { validateStock(dto); // 事务不生效 } @Transactional public void validateStock(OrderDTO dto) { // 库存校验逻辑 } // 正例:通过代理对象调用 @Service public class OrderService { @Autowired private OrderService selfProxy; // 注入自身代理 public void createOrder(OrderDTO dto) { selfProxy.validateStock(dto); // 事务生效 } @Transactional public void validateStock(OrderDTO dto) { // 库存校验逻辑 } } -
日期时间处理:
- 数据库使用
datetime类型存储 - Java端统一使用
LocalDateTime - 前端传参格式:
yyyy-MM-dd HH:mm:ss - JSON序列化配置:
java复制@Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder -> { builder.serializers(new LocalDateTimeSerializer( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); builder.deserializers(new LocalDateTimeDeserializer( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }; }
- 数据库使用
-
跨域解决方案:
java复制@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .maxAge(3600); } } -
API版本控制:
java复制@RestController @RequestMapping("/api/v1/products") public class ProductController { // v1版本接口 } @RestController @RequestMapping("/api/v2/products") public class ProductControllerV2 { // v2版本接口 }
