首页 > 其他分享 >6步带你用Spring Boot开发出商城高并发秒杀系统

6步带你用Spring Boot开发出商城高并发秒杀系统

时间:2023-04-06 11:02:49浏览次数:42  
标签:Spring 步带 Boot Long public 秒杀 private order id

摘要:本博客将介绍如何使用 Spring Boot 实现一个简单的商城秒杀系统,并通过使用 Redis 和 MySQL 来增强其性能和可靠性。

本文分享自华为云社区《Spring Boot实现商城高并发秒杀案例》,作者:林欣。

随着经济的发展和人们消费观念的转变,电子商务逐渐成为人们购物的主要方式之一。高并发是电子商务网站面临的一个重要挑战。本博客将介绍如何使用 Spring Boot 实现一个简单的商城秒杀系统,并通过使用 Redis 和 MySQL 来增强其性能和可靠性。

准备工作

在开始之前,您需要准备以下工具和环境:

  • JDK 1.8 或更高版本
  • Redis
  • MySQL
  • MyBatis

实现步骤

步骤一:创建数据库

首先,我们需要创建一个数据库来存储商品信息、订单信息和秒杀活动信息。在这里,我们使用 MySQL 数据库,创建一个名为 shop 的数据库,并建立三个表 goods、order 和 seckill。

表 goods 存储了所有的商品信息,包括商品编号、名称、描述、价格和库存数量等等。

CREATE TABLE `goods` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
 `name` varchar(50) NOT NULL COMMENT '商品名称',
 `description` varchar(100) NOT NULL COMMENT '商品描述',
 `price` decimal(10,2) NOT NULL COMMENT '商品价格',
 `stock_count` int(11) NOT NULL COMMENT '商品库存',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

表 order 存储了所有的订单信息,包括订单编号、用户ID、商品ID、秒杀活动ID 和订单状态等等。

CREATE TABLE `order` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
 `user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
 `goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
 `seckill_id` BIGINT(20) DEFAULT NULL COMMENT '秒杀活动ID',
 `status` TINYINT(4) NOT NULL COMMENT '订单状态,0-未支付,1-已支付,2-已发货,3-已收货,4-已退款,5-已完成',
 `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `unique_order` (`user_id`,`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

表 seckill 存储了所有的秒杀活动信息,包括秒杀活动编号、商品ID、开始时间和结束时间等等。

CREATE TABLE `seckill` (
 `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀活动ID',
 `goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
 `start_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '开始时间',
 `end_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '结束时间',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀活动表';

步骤二:创建 Spring Boot 项目

接下来,我们需要创建一个 Spring Boot 项目,用于实现商城高并发秒杀案例。可以使用 Spring Initializr 来快速创建一个基本的 Spring Boot 项目。

步骤三:配置 Redis 和 MySQL

在 Spring Boot 项目中,我们需要配置 Redis 和 MySQL 的连接信息。可以在 application.properties 文件中设置以下属性:

spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456

步骤四:编写实体类和 DAO 接口

在这一步中,我们需要定义三个实体类分别对应数据库中的 goods、order 和 seckill 表。同时,我们需要编写相应的 DAO 接口,用于操作这些实体类。

// 商品实体类
@Data
public class Goods {
 private Long id;
 private String name;
 private String description;
 private BigDecimal price;
 private Integer stockCount;
}
// 商品 DAO 接口
@Mapper
public interface GoodsDao {
    @Select("SELECT * FROM goods WHERE id = #{id}")
    Goods getGoodsById(Long id);
    @Update("UPDATE goods SET stock_count = stock_count - 1 WHERE id = #{id} AND stock_count > 0")
    int reduceStockCount(Long id);
}
// 订单实体类
@Data
public class Order {
 private Long id;
 private Long userId;
 private Long goodsId;
 private Long seckillId;
 private Byte status;
 private Date createTime;
 private Date updateTime;
}
// 订单 DAO 接口
@Mapper
public interface OrderDao {
    @Select("SELECT * FROM `order` WHERE user_id = #{userId} AND goods_id = #{goodsId}")
    Order getOrderByUserIdAndGoodsId(@Param("userId") Long userId, @Param("goodsId") Long goodsId);
    @Insert("INSERT INTO `order` (user_id, goods_id, seckill_id, status, create_time, update_time) VALUES (#{userId}, #{goodsId}, #{seckillId}, #{status},#{createTime},#{updateTime})")
    int insertOrder(Order order);
    @Select("SELECT o.*, g.name, g.price FROM `order` o LEFT JOIN goods g ON o.goods_id = g.id WHERE o.user_id = #{userId}")
    List<OrderVo> getOrderListByUserId(Long userId);
}
// 秒杀活动实体类
@Data
public class Seckill {
 private Long id;
 private Long goodsId;
 private Date startTime;
 private Date endTime;
}
// 秒杀活动 DAO 接口
@Mapper
public interface SeckillDao {
    @Select("SELECT * FROM seckill WHERE id = #{id}")
    Seckill getSeckillById(Long id);
    @Update("UPDATE seckill SET end_time = #{endTime} WHERE id = #{id}")
    int updateSeckillEndTime(@Param("id") Long id, @Param("endTime") Date endTime);
}

步骤五:编写 Service 层和 Controller

在这一步中,我们需要编写 Service 层和 Controller 类,用于实现商城高并发秒杀案例的核心功能。

  • 商品 Service 层:用于获取商品信息和减少商品库存数量。
@Service
public class GoodsService {
 private final GoodsDao goodsDao;
    @Autowired
 public GoodsService(GoodsDao goodsDao) {
 this.goodsDao = goodsDao;
 }
 public Goods getGoodsById(Long id) {
 return goodsDao.getGoodsById(id);
 }
 public boolean reduceStockCount(Long id) {
 return goodsDao.reduceStockCount(id) > 0;
 }
}
  • 订单 Service 层:用于创建订单和获取订单信息。
@Service
public class OrderService {
 private final OrderDao orderDao;
    @Autowired
 public OrderService(OrderDao orderDao) {
 this.orderDao = orderDao;
 }
 public Order createOrder(Long userId, Long goodsId, Long seckillId) {
        Order order = new Order();
        order.setUserId(userId);
        order.setGoodsId(goodsId);
        order.setSeckillId(seckillId);
        order.setStatus((byte) 0);
        order.setCreateTime(new Date());
        order.setUpdateTime(new Date());
        orderDao.insertOrder(order);
 return order;
 }
 public List<OrderVo> getOrderListByUserId(Long userId) {
 return orderDao.getOrderListByUserId(userId);
 }
}
  • 秒杀活动 Service 层:用于获取秒杀活动信息和更新秒杀活动结束时间。
@Service
public class SeckillService {
 private final SeckillDao seckillDao;
    @Autowired
 public SeckillService(SeckillDao seckillDao) {
 this.seckillDao = seckillDao;
 }
 public Seckill getSeckillById(Long id) {
 return seckillDao.getSeckillById(id);
 }
 public boolean updateSeckillEndTime(Long id, Date endTime) {
 return seckillDao.updateSeckillEndTime(id, endTime) > 0;
 }
}
  • 订单 Controller:用于处理订单相关的请求。
@RestController
@RequestMapping("/order")
public class OrderController {
 private final OrderService orderService;
    @Autowired
 public OrderController(OrderService orderService) {
 this.orderService = orderService;
 }
    @PostMapping("/create")
 public CommonResult<Order> createOrder(@RequestParam("userId") Long userId,
                                           @RequestParam("goodsId") Long goodsId,
                                           @RequestParam("seckillId") Long seckillId) {
        Order order = orderService.createOrder(userId, goodsId, seckillId);
 if (order == null) {
 return CommonResult.failed(ResultCode.FAILURE);
 }
 return CommonResult.success(order);
 }
    @GetMapping("/list")
 public CommonResult<List<OrderVo>> getOrderListByUserId(@RequestParam("userId") Long userId) {
        List<OrderVo> orderList = orderService.getOrderListByUserId(userId);
 return CommonResult.success(orderList);
 }
}

秒杀活动 Controller:用于处理秒杀活动相关的请求。

@RestController
@RequestMapping("/seckill")
public class SeckillController {
 private final SeckillService seckillService;
 private final GoodsService goodsService;
 private final OrderService orderService;
    @Autowired
 public SeckillController(SeckillService seckillService, GoodsService goodsService, OrderService orderService) {
 this.seckillService = seckillService;
 this.goodsService = goodsService;
 this.orderService = orderService;
 }
    @PostMapping("/start")
 public CommonResult<Object> startSeckill(@RequestParam("userId") Long userId,
                                             @RequestParam("goodsId") Long goodsId,
                                             @RequestParam("seckillId") Long seckillId) {
 // 查询秒杀活动是否有效
        Seckill seckill = seckillService.getSeckillById(seckillId);
 if (seckill == null || seckill.getStartTime().after(new Date()) || seckill.getEndTime().before(new Date())) {
 return CommonResult.failed(ResultCode.FAILURE, "秒杀活动不存在或已结束");
 }
 // 判断商品库存是否充足
        Goods goods = goodsService.getGoodsById(goodsId);
 if (goods == null || goods.getStockCount() <= 0) {
 return CommonResult.failed(ResultCode.FAILURE, "商品库存不足");
 }
 // 生成订单
        Order order = orderService.createOrder(userId, goodsId, seckillId);
 if (order == null) {
 return CommonResult.failed(ResultCode.FAILURE, "订单创建失败,请稍后再试");
 }
 // 减少商品库存
        boolean success = goodsService.reduceStockCount(goodsId);
 if (!success) {
 return CommonResult.failed(ResultCode.FAILURE, "减少商品库存失败,请稍后再试");
 }
 return CommonResult.success("秒杀成功");
 }
}

步骤六:使用 Redis 实现分布式锁

在商城高并发秒杀案例中,一个重要的问题是如何保证商品库存数量的一致性和秒杀结果的正确性。为了解决这个问题,我们可以使用 Redis 实现分布式锁。

在 RedisService 类中实现分布式锁:

@Service
public class RedisService {
 private final RedisTemplate<String, Object> redisTemplate;
    @Autowired
 public RedisService(RedisTemplate<String, Object> redisTemplate) {
 this.redisTemplate = redisTemplate;
 }
 public boolean lock(String key, String value, long expire) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(expire));
 return result != null && result;
 }
 public void unlock(String key, String value) {
 if (value.equals(redisTemplate.opsForValue().get(key))) {
            redisTemplate.delete(key);
 }
 }
}

在 SeckillService 中使用分布式锁实现秒杀接口:

@Service
public class SeckillService {
 private final RedisService redisService;
 private final SeckillDao seckillDao;
 private final GoodsDao goodsDao;
 private final OrderDao orderDao;
    @Autowired
 public SeckillService(RedisService redisService, SeckillDao seckillDao, GoodsDao goodsDao, OrderDao orderDao) {
 this.redisService = redisService;
 this.seckillDao = seckillDao;
 this.goodsDao = goodsDao;
 this.orderDao = orderDao;
 }
 public CommonResult<Object> startSeckill(Long userId, Long goodsId, Long seckillId) {
        String lockKey = "seckill:lock:" + goodsId;
        String lockValue = UUID.randomUUID().toString();
 try {
 // 获取分布式锁
 if (!redisService.lock(lockKey, lockValue, 10)) {
 return CommonResult.failed(ResultCode.FAILURE, "当前请求太过频繁,请稍后再试");
 }
 // 查询秒杀活动是否有效
            Seckill seckill = seckillDao.getSeckillById(seckillId);
 if (seckill == null || seckill.getStartTime().after(new Date()) || seckill.getEndTime().before(new Date())) {
 return CommonResult.failed(ResultCode.FAILURE, "秒杀活动不存在或已结束");
 }
 // 判断商品库存是否充足
            Goods goods = goodsDao.getGoodsById(goodsId);
 if (goods == null || goods.getStockCount() <= 0) {
 return CommonResult.failed(ResultCode.FAILURE, "商品库存不足");
 }
 // 创建订单
            Order order = new Order();
            order.setUserId(userId);
            order.setGoodsId(goodsId);
            order.setSeckillId(seckillId);
            order.setStatus((byte) 0);
            order.setCreateTime(new Date());
            order.setUpdateTime(new Date());
            int count = orderDao.insertOrder(order);
 if (count <= 0) {
 return CommonResult.failed(ResultCode.FAILURE, "订单创建失败,请稍后再试");
 }
 // 减少商品库存
            boolean success = goodsDao.reduceStockCount(goodsId) > 0;
 if (!success) {
 throw new Exception("减少商品库存失败,请稍后再试");
 }
 return CommonResult.success("秒杀成功");
 } catch (Exception e) {
            e.printStackTrace();
 return CommonResult.failed(ResultCode.FAILURE, "秒杀失败," + e.getMessage());
 } finally {
 // 释放分布式锁
            redisService.unlock(lockKey, lockValue);
 }
 }
}


点击关注,第一时间了解华为云新鲜技术~

标签:Spring,步带,Boot,Long,public,秒杀,private,order,id
From: https://blog.51cto.com/u_15214399/6172422

相关文章

  • 6步带你用Spring Boot开发出商城高并发秒杀系统
    摘要:本博客将介绍如何使用SpringBoot实现一个简单的商城秒杀系统,并通过使用Redis和MySQL来增强其性能和可靠性。本文分享自华为云社区《SpringBoot实现商城高并发秒杀案例》,作者:林欣。随着经济的发展和人们消费观念的转变,电子商务逐渐成为人们购物的主要方式之一。高并......
  • idea启动spring项目
    原文连接:https://blog.csdn.net/w_t_y_y/article/details/100337840 一、没有安装tomcat:1、点击进入:2、点maven,点击左上角+号:配置spring项目:(1)在parameters的commandfile中输入:org.mortbay.jetty:maven-jetty-plugin:6.1.26:run(2)在runner的vmoption中输入-Djetty.po......
  • 聊聊spring中bean的作用域
    前言今天分享一下springbean的作用域,理解bean的作用域能够在使用过程中避免一些问题,bean的作用域也是springbean创建过程中一个重要的点。Springbean的作用域类型singleton(单例模式):在整个应用程序的生命周期中,只创建一个Bean实例。默认情况下,所有的Bean都是单例模式。p......
  • springboot项目图片不显示的问题
    首先确认你的图片路径是对的那么大概率就是浏览器缓存的原因,因为页面直接用的是缓存的旧数据,所以显示不出来。再不修改浏览器设置的情况下,最简单的办法就是直接项目在pom.xml文件里引入devtools如下:<dependency><groupId>org.springframework.boot</groupId><artifactId>sp......
  • SpringBoot如何进行限流,老鸟们还可以这样玩!
    大家好,我是飘渺。在SpringBoot如何进行限流,老鸟们都这么玩的!一文中我们详细介绍了为什么需要对接口进行限流,也介绍了常见的限流算法,最后还基于Guava工具类实现了接口限流。但是这种方式有个问题,无法实现分布式限流。那今天我们来利用Redis+Lua来实现分布式限流。Lua脚本......
  • Java SpringBoot Bean InitializingBean
    Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean。工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂Bean的getObject方法所返回的对象。Spring初始化bean有两种方式:实现InitializingBean接口,实现afterPropertiesSet方法。(比通过......
  • spring事务
      步骤;1.在要开事务的方法上面加一个@Transactional  2.设置事务管理器:  3.在config里enable一下事务管理,写个@EnableTransactionManagement        角色:  spring事务相关配置: ......
  • 解决在创建springboot项目中遇到:Error:(3, 32) java: 无法访问org.springframework.bo
    解决在创建springboot项目中遇到:Error:(3,32)java:无法访问org.springframework.boot.SpringApplication File--->ProjectStructure    选择对应版本        File--->Settings--->Build,Execution,Deployment    修改对应版本 ......
  • Spring框架学习
    一、第一部分:SpringIoC&DI1.Spring概述1.1Spring是什么(1)Spring是分层的JavaSE/EE应用full-stack轻量级开源框架;(2)Spring以IoC和AOP为内核;InverseOfControl:反转控制AspectOrientedProgramming:面向切面编程(3)提供了展现层SpringMVC和持久层SpringJDBC......
  • SpringBoot启动流程
    启动类@SpringBootApplicationpublicclassApp{publicstaticvoidmain(String[]args){ SpringApplication.run(App.class,args);}}@SpringBootApplication对于一个SpringBoot程序的启动首先需要一个加了@SpringBootApplication注解的启动类。@Spr......