当前位置:首页 » 《随便一记》 » 正文

SpringBoot + 秒杀系统_qq_1403034144的博客

28 人参与  2021年11月11日 09:23  分类 : 《随便一记》  评论

点击全文阅读


本书源码
https://github.com/huangwenyi10/spring-boot-book-v2

目录

  • 创建
  • 秒杀系统
    • 雏形
      • 小结
      • 详情
    • 高并发优化
    • 流量削峰

创建


解决 Cannot resolve plugin org.apache.maven.plugins:maven-site-plugin:3.8.2:
https://ld246.com/article/1584103058009

秒杀系统

雏形

源码:
https://github.com/13407196713/SpringBoot2

小结

创建数据库speed-kill-system
三个表 ay_product、ay_user、ay_user_kill_product
分别是用户、商品、秒杀的商品

model/AyUser
model/AyProduct
model/AyUserKillProduct
model/KillStatus

mysql的依赖和设置
依赖:
application.properties配置:

repository/ProductRepository 商品的增删改查 CRUD
repository/AyUserKillProductRepository 用户秒杀商品记录Repository 增删查改 CRUD

service/ProductService
service/AyUserKillProductService
service/impl/ProductServiceImpl
service/impl/AyUserKillProductServiceImpl

controller/ProductController

前端依赖和配置
依赖:
application.properties配置:

templates/product_list.html
templates/success.html
templates/fail.html

详情

创建数据库speed-kill-system
三个表 ay_product、ay_user、ay_user_kill_product
分别是用户、商品、秒杀的商品

model/AyUser

// 用户

// 对实体注释。任何 Hibernate 映射对象都要有这个注释
@Entity

// 声明此对象映射到数据库的数据表,如果没有则系统使用默认值(实体的短类名)。
@Table(name = "ay_user")

public class AyUser implements Serializable {

    //主键
    @Id
    private Integer id;
    //用户名
    private String name;
    //电话号码
    private String phoneNumber;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}

model/AyProduct

// 商品

@Entity
@Table(name = "ay_product")
public class AyProduct implements Serializable {
    /**
     * 商品id
     */
    @Id
    private Integer id;
    /**
     * 商品图片
     */
    private String productImg;
    /**
     * 商品名称
     */
    private String name;
    /**
     * 商品数量
     */
    private Integer number;
    /**
     * 秒杀开始时间
      */
     private Date startTime;
    /**
     * 秒杀结束时间
     */
    private Date endTime;
    /**
     * 创建时间
     */
    private Date createTime;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Integer getNumber() {
        return number;
    }
    public void setNumber(Integer number) {
        this.number = number;
    }

    public Date getStartTime() {
        return startTime;
    }
    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getEndTime() {
        return endTime;
    }
    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getProductImg() {
        return productImg;
    }
    public void setProductImg(String productImg) {
        this.productImg = productImg;
    }
}

model/AyUserKillProduct

// 秒杀商品

@Entity
@Table(name = "ay_user_kill_product")

public class AyUserKillProduct implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    //指定主键的生成策略。有如下四个值 id是主键
    // TABLE:使用表保存id值
    // IDENTITY:由数据库自动生成
    // SEQUENCR :根据底层数据库的序列来生成主键,条件是数据库支持序列
    // AUTO:主键由程序控制

    private Integer id;
    /**
     * 商品id
     */
    private Integer productId;
    /**
     * 用户id
     */
    private Integer userId;
    /**
     * 状态,-1:无效;0:成功;1:已付款'
      */
    private Integer state;
    /**
     * 创建时间
     */
    private Date createTime;

    public Integer getProductId() {
        return productId;
    }
    public void setProductId(Integer productId) {
        this.productId = productId;
    }

    public Integer getUserId() {
        return userId;
    }
    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getState() {
        return state;
    }
    public void setState(Integer state) {
        this.state = state;
    }

    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
}

model/KillStatus

// 描述:秒杀状态类
public enum KillStatus {

    IN_VALID(-1, "无效"),
    SUCCESS(0, "成功"),
    PAY(1,"已付款");


    private int code;
    private String name;

    KillStatus(){}

    KillStatus(int code, String name){
        this.code = code;
        this.name = name;
    }

    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
}

mysql的依赖和设置
依赖:

		<!-- mysql start -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

application.properties配置:

### mysql连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/speed-kill-system?serverTimezone=UTC
###用户名
spring.datasource.username=root
###密码
spring.datasource.password=123456
###驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

repository/ProductRepository 商品的增删改查 CRUD

// 商品的增删改查 CRUD
public interface ProductRepository extends JpaRepository<AyProduct,Integer> {
}

repository/AyUserKillProductRepository 用户秒杀商品记录Repository 增删查改 CRUD

// 描述:用户秒杀商品记录Repository 增删查改 CRUD
public interface AyUserKillProductRepository extends JpaRepository<AyUserKillProduct,Integer> {
}

service/ProductService

public interface ProductService {
    //查询所有商品
    List<AyProduct> findAll();

    //查询所有商品
//    Collection<AyProduct> findAllCache();

    // 秒杀商品
    // @param productId 商品id
    // @param userId 用户id
    AyProduct killProduct(Integer productId, Integer userId);
}

service/AyUserKillProductService

//描述:用户秒杀商品记录接口
public interface AyUserKillProductService {
    //保存用户秒杀商品记录
    AyUserKillProduct save(AyUserKillProduct killProduct);
}

service/impl/ProductServiceImpl

// 商品服务
@Service
public class ProductServiceImpl implements ProductService {

    @Resource
    private ProductRepository productRepository;

    @Resource
    private AyUserKillProductService ayUserKillProductService;

    //日志
    Logger logger = LoggerFactory.getLogger(ProductServiceImpl.class);

    // 查询所有商品
    @Override
    public List<AyProduct> findAll() {
        try{
            List<AyProduct> ayProducts = productRepository.findAll();
            return ayProducts;
        }catch (Exception e){
            logger.error("ProductServiceImpl.findAll error", e);
            return Collections.EMPTY_LIST;
        }
    }

    // 秒杀商品
    // @param productId 商品id
    // @param userId 用户id
    @Override
    public AyProduct killProduct(Integer productId, Integer userId) {
        //查询商品
        AyProduct ayProduct = productRepository.findById(productId).get();
        //判断商品是否还有库存
        if(ayProduct.getNumber() < 0){
            return null;
        }
        //设置商品的库存:原库存数量 - 1
        ayProduct.setNumber(ayProduct.getNumber() - 1);
        //更新商品库存
        ayProduct = productRepository.save(ayProduct);

        //保存商品的秒杀记录
        AyUserKillProduct killProduct = new AyUserKillProduct();
        killProduct.setCreateTime(new Date());
        killProduct.setProductId(productId);
        killProduct.setUserId(userId);
        //设置秒杀状态
        killProduct.setState(KillStatus.SUCCESS.getCode());
        //保存秒杀记录详细信息
        ayUserKillProductService.save(killProduct);

        //商品秒杀成功后,更新缓存中商品库存数量
//        redisTemplate.opsForHash().put(KILL_PRODUCT_LIST, killProduct.getProductId(),ayProduct);
        return ayProduct;
    }

}

service/impl/AyUserKillProductServiceImpl

@Service
public class AyUserKillProductServiceImpl implements AyUserKillProductService {

    @Resource
    private AyUserKillProductRepository ayUserKillProductRepository;

    //保存用户秒杀商品记录
    //@param killProduct
    @Override
    public AyUserKillProduct save(AyUserKillProduct killProduct) {
        return ayUserKillProductRepository.save(killProduct);
    }
}

controller/ProductController

@Controller
@RequestMapping("/products")
public class ProductController {

    @Resource
    private ProductService productService;

    //查询所有的商品
    @RequestMapping("/all")
    public String findAll(Model model){
        List<AyProduct> products = productService.findAll();
        model.addAttribute("products", products);
        return "product_list";
    }

    // 秒杀商品
    @RequestMapping("/{id}/kill")
    public String killProduct(Model model,
                              @PathVariable("id") Integer productId,
                              @RequestParam("userId") Integer userId){
        AyProduct ayProduct = productService.killProduct(productId, userId);
        if(null != ayProduct){
            return "success";
        }
        return "fail";
    }
}

前端依赖和配置
依赖:

	<!-- thymeleaf -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
        <version>2.3.4.RELEASE</version>
    </dependency>

application.properties配置:

#thymeleaf配置
#模板的模式,支持如:HTML、XML、TEXT、JAVASCRIPT等
spring.thymeleaf.mode=HTML5
#编码,可不用配置
spring.thymeleaf.encoding=UTF-8
#内容类别,可不用配置
spring.thymeleaf.servlet.content-type=text/html
#开发配置为false,避免修改模板还要重启服务器
spring.thymeleaf.cache=false
#配置模板路径,默认就是templates,可不用配置
#spring.thymeleaf.prefix=classpath:/templates/

templates/product_list.html

<!DOCTYPE HTML>

<html xmlns:th="http://www.thymeleaf.org">

<head>
    <title>hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <!-- 引入 Bootstrap -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<!-- 页面显示部分-->
<div class="container">
    <div class="panel panel-default">
        <div class="panel-heading text-center">
            <h1>秒杀活动</h1>
        </div>
        <div class="panel-body">
            <table class="table table-hover">
                <thead>
                <tr>
                    <td>图片</td>
                    <td>名称</td>
                    <td>库存</td>
                    <td>开始时间</td>
                    <td>结束时间</td>
                    <td>操作</td>
                </tr>
                </thead>

                <tbody>
                <tr th:each="product:${products}">
                    <td>
                        <img border="1px" width="100px" height="110px" th:src="@{${product.productImg}}"/>
                    </td>
                    <td th:text="${product.name}"></td>
                    <td th:text="${product.number}"></td>
                    <td th:text="${product.startTime}"></td>
                    <td th:text="${product.endTime}"></td>
                    <td>
                        <!-- 发起秒杀请求,用户id先简单写死为 1 -->
                        <a class="btn btn-info" th:href="@{'/products/'+${product.id} + '/kill' + '?userId=1'}">秒杀</a>

                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
</div>

</body>
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</html>

templates/success.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div align="center">
    客官!!! 恭喜您,秒杀成功~~~
</div>
</body>
</html>

templates/fail.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div align="center">
    不要灰心,继续加油~~~
</div>
</body>

</html>

启动 SpringBoot2Application
进入 http://localhost:8080/products/all


高并发优化

源码
https://github.com/13407196713/SpringBoot2_2

引入redis缓存
application.properties

### redis缓存配置
### 默认redis数据库为db0
spring.redis.database=0
### 服务器地址,默认为localhost
spring.redis.host=localhost
### 链接端口,默认为6379
spring.redis.port=6379
### redis密码默认为空
spring.redis.password=
spring.redis.timeout=10000

service/ProductService

	//查询所有商品
	Collection<AyProduct> findAllCache();

service/impl/ProductServiceImpl


	//注入redisTemplate对象
    @Resource
    private RedisTemplate redisTemplate;
    //定义缓存key
    private static final String KILL_PRODUCT_LIST = "kill_product_list";

    //查询商品数据(带缓存)
    @Override
    public Collection<AyProduct> findAllCache() {
        try{
            //从缓存中查询商品数据
            Map<Integer, AyProduct> productMap = redisTemplate.opsForHash().entries(KILL_PRODUCT_LIST);
            Collection<AyProduct> ayProducts = null;
            //如果缓存中查询不到商品数据
            if(CollectionUtils.isEmpty(productMap)){
                //从数据库中查询商品数据
                ayProducts = productRepository.findAll();
                //将商品list转换为商品map
                productMap = convertToMap(ayProducts);
                //将商品数据保存到缓存中
                redisTemplate.opsForHash().putAll(KILL_PRODUCT_LIST, productMap);
                //设置缓存数据的过期时间,这里设置10s,具体时间需要结合业务需求而定
                //如果商品数据变化少,过期时间可以设置长一点;反之,过期时间可以设置短一点
                redisTemplate.expire(KILL_PRODUCT_LIST,10000 , TimeUnit.MILLISECONDS);
                return ayProducts;
            }
            ayProducts = productMap.values();
            return ayProducts;
        }catch (Exception e){
            logger.error("ProductServiceImpl.findAllCache error", e);
            return Collections.EMPTY_LIST;
        }
    }


    //list转换为map
    private Map<Integer, AyProduct> convertToMap(Collection<AyProduct> ayProducts){
        if(CollectionUtils.isEmpty(ayProducts)){
            return Collections.EMPTY_MAP;
        }
        Map<Integer, AyProduct> productMap = new HashMap<>(ayProducts.size());
        for(AyProduct product: ayProducts){
            productMap.put(product.getId(), product);
        }
        return productMap;
    }

controller/ProductController

    // 查询所有的商品(缓存)
    @RequestMapping("/all/cache")
    public String findAllCache(Model model){
        Collection<AyProduct> products = productService.findAllCache();
        model.addAttribute("products", products);
        return "product_list";
    }

商品秒杀成功后,更新缓存中商品库存数量
service/impl/ProductServiceImpl

        //商品秒杀成功后,更新缓存中商品库存数量
        redisTemplate.opsForHash().put(KILL_PRODUCT_LIST, killProduct.getProductId(),ayProduct);

启动 redis的redis-server.exe、redis-cli.exe
启动程序

流量削峰

源码
https://github.com/13407196713/SpringBoot2_3

为了让并发请求更平缓,我们需要流量削峰

用消息队列缓冲瞬时流量

ActiveMQ下载安装
https://blog.csdn.net/weixin_38361347/article/details/83796570

ActiveMQ 依赖

    <!-- activemq start -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-activemq</artifactId>
    </dependency>

配置

### activemq 配置
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.in-memory=true
spring.activemq.pool.enabled=false
spring.activemq.packages.trust-all=true

生产者
producer/AyProductKillProducer

// 生产者

@Service
public class AyProductKillProducer {

    //日志
    Logger logger = LoggerFactory.getLogger(ProductServiceImpl.class);

    @Resource
    private JmsMessagingTemplate jmsMessagingTemplate;

    /**
     *  描述:发送消息
     * @param destination 目标地址
     * @param killProduct 描述商品
     */
    public void sendMessage(Destination destination, final AyUserKillProduct killProduct) {
        logger.info("AyProductKillProducer sendMessage , killProduct is" + killProduct);
        jmsMessagingTemplate.convertAndSend(destination, killProduct);
    }
}

消费者
consumer/AyProductKillConsumer

// 消费者
@Component
public class AyProductKillConsumer {

    //日志
    Logger logger = LoggerFactory.getLogger(ProductServiceImpl.class);

    @Resource
    private AyUserKillProductService ayUserKillProductService;

    //消费消息
    @JmsListener(destination = "ay.queue.asyn.save")
    public void receiveQueue(AyUserKillProduct killProduct){
        //保存秒杀商品数据
        ayUserKillProductService.save(killProduct);
        //记录日志
        logger.info("ayUserKillProductService save, and killProduct: " + killProduct);
    }
}

修改 service/impl/ProductServiceImpl

@Resource
    private AyProductKillProducer ayProductKillProducer;

    //队列
    private static Destination destination = new ActiveMQQueue("ay.queue.asyn.save");

    /**
     * 秒杀商品(引入MQ)
     * @param productId 商品id
     * @param userId 用户id
     */
    @Override
    public AyProduct killProduct(Integer productId, Integer userId) {
        //查询商品
        AyProduct ayProduct = productRepository.findById(productId).get();
        //判断商品是否还有库存
        if(ayProduct.getNumber() < 0){
            return null;
        }
        //设置商品的库存:原库存数量 - 1
        ayProduct.setNumber(ayProduct.getNumber() - 1);
        //更新商品库存
        ayProduct = productRepository.save(ayProduct);
        //保存商品的秒杀记录
        AyUserKillProduct killProduct = new AyUserKillProduct();
        killProduct.setCreateTime(new Date());
        killProduct.setProductId(productId);
        killProduct.setUserId(userId);
        //设置秒杀状态
        killProduct.setState(KillStatus.SUCCESS.getCode());
        //保存秒杀记录详细信息
        //ayUserKillProductService.save(killProduct);
        //异步保存商品的秒杀记录
        ayProductKillProducer.sendMessage(destination, killProduct);

        //商品秒杀成功后,更新缓存中商品库存数量
        redisTemplate.opsForHash().put(KILL_PRODUCT_LIST, killProduct.getProductId(),ayProduct);
        return ayProduct;
    }

启动 redis的redis-server.exe、redis-cli.exe
启动apache-activemq 的 bin/win64/activemq
启动程序


点击全文阅读


本文链接:http://zhangshiyu.com/post/30757.html

商品  秒杀  缓存  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1