• SpringBoot秒杀系统demo


    我们在平时的开发中经常会遇到秒杀,抢单的一些需求,这些系统开发时如果考虑不全面就可能会产生库存不准,以及数据库压力大等问题。

    本文将以springboot为基础,结合Redis 和 RabbitMQ做一个秒杀系统的demo,主要展示Redis分布式锁以及消息队列的使用。

    秒杀系统的主要基于以下的原则去实现

    1. 系统初始化时,把商品存库数量加载到redis中
    2. 当收到秒杀请求后,redis预减库存,库存不足则直接返回
    3. 秒杀成功的请求入rabbitMQ,立即返回“正在抢购页面…”,当异步下单成功后才返回订单。
    4. 客户端轮询是否秒杀成功,服务器请求出队,生成订单,减少库存。

    1. 配置

       1.1 pom文件

      1 <?xml version="1.0" encoding="UTF-8"?>
      2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      3          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      4     <modelVersion>4.0.0</modelVersion>
      5     <parent>
      6         <groupId>org.springframework.boot</groupId>
      7         <artifactId>spring-boot-starter-parent</artifactId>
      8         <version>2.2.5.RELEASE</version>
      9         <relativePath/> <!-- lookup parent from repository -->
     10     </parent>
     11     <groupId>com.devin</groupId>
     12     <artifactId>order_grabbing_demo</artifactId>
     13     <version>1.0.0</version>
     14     <name>order_grabbing_demo</name>
     15     <description>Demo project for Spring Boot</description>
     16 
     17     <properties>
     18         <java.version>1.8</java.version>
     19     </properties>
     20 
     21     <dependencies>
     22 
     23         <dependency>
     24             <groupId>org.springframework.boot</groupId>
     25             <artifactId>spring-boot-starter-web</artifactId>
     26         </dependency>
     27 
     28 
     29         <dependency>
     30             <groupId>tk.mybatis</groupId>
     31             <artifactId>mapper-spring-boot-starter</artifactId>
     32             <version>2.0.4</version>
     33         </dependency>
     34 
     35         <dependency>
     36             <groupId>org.springframework.boot</groupId>
     37             <artifactId>spring-boot-starter-jdbc</artifactId>
     38             <version>2.0.0.RELEASE</version>
     39         </dependency>
     40 
     41         <dependency>
     42             <groupId>org.mybatis.spring.boot</groupId>
     43             <artifactId>mybatis-spring-boot-starter</artifactId>
     44             <version>2.0.1</version>
     45         </dependency>
     46 
     47         <dependency>
     48             <groupId>mysql</groupId>
     49             <artifactId>mysql-connector-java</artifactId>
     50             <version>5.1.17</version>
     51         </dependency>
     52 
     53         <dependency>
     54             <groupId>com.alibaba</groupId>
     55             <artifactId>druid</artifactId>
     56             <version>1.1.1</version>
     57         </dependency>
     58 
     59         <dependency>
     60             <groupId>org.projectlombok</groupId>
     61             <artifactId>lombok</artifactId>
     62             <version>1.16.22</version>
     63         </dependency>
     64 
     65 
     66         <dependency>
     67             <groupId>org.springframework.boot</groupId>
     68             <artifactId>spring-boot-starter-amqp</artifactId>
     69             <version>2.1.8.RELEASE</version>
     70         </dependency>
     71 
     72         <dependency>
     73             <groupId>org.springframework.boot</groupId>
     74             <artifactId>spring-boot-test</artifactId>
     75             <version>2.2.6.RELEASE</version>
     76         </dependency>
     77         <dependency>
     78             <groupId>junit</groupId>
     79             <artifactId>junit</artifactId>
     80             <version>4.12</version>
     81         </dependency>
     82         <dependency>
     83             <groupId>org.springframework</groupId>
     84             <artifactId>spring-test</artifactId>
     85             <version>5.2.5.RELEASE</version>
     86         </dependency>
     87 
     88 
     89 
     90         <!--  springboot整合 redis -->
     91         <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
     92         <dependency>
     93             <groupId>org.springframework.boot</groupId>
     94             <artifactId>spring-boot-starter-data-redis</artifactId>
     95             <version>2.2.0.RELEASE</version>
     96             <exclusions>
     97                 <exclusion>
     98                     <groupId>io.lettuce</groupId>
     99                     <artifactId>lettuce-core</artifactId>
    100                 </exclusion>
    101             </exclusions>
    102         </dependency>
    103 
    104         <dependency>
    105             <groupId>redis.clients</groupId>
    106             <artifactId>jedis</artifactId>
    107         </dependency>
    108 
    109 
    110         <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    111         <dependency>
    112             <groupId>com.alibaba</groupId>
    113             <artifactId>fastjson</artifactId>
    114             <version>1.2.57</version>
    115         </dependency>
    116 
    117 
    118         <dependency>
    119             <groupId>org.apache.commons</groupId>
    120             <artifactId>commons-lang3</artifactId>
    121             <version>3.5</version>
    122         </dependency>
    123 
    124         <dependency>
    125             <groupId>commons-codec</groupId>
    126             <artifactId>commons-codec</artifactId>
    127             <version>1.10</version>
    128         </dependency>
    129 
    130     </dependencies>
    131 
    132     <build>
    133         <plugins>
    134             <plugin>
    135                 <groupId>org.springframework.boot</groupId>
    136                 <artifactId>spring-boot-maven-plugin</artifactId>
    137             </plugin>
    138         </plugins>
    139     </build>
    140 
    141     <repositories>
    142         <repository>
    143             <id>maven-ali</id>
    144             <url>http://maven.aliyun.com/nexus/content/groups/public//</url>
    145             <releases>
    146                 <enabled>true</enabled>
    147             </releases>
    148             <snapshots>
    149                 <enabled>true</enabled>
    150                 <updatePolicy>always</updatePolicy>
    151                 <checksumPolicy>fail</checksumPolicy>
    152             </snapshots>
    153         </repository>
    154     </repositories>
    155 
    156 
    157 </project>

       1.2. application.yml 配置 

        主要配置了数据库,Redis,RabbitMQ的配置

         

     1 server:
     2   port: 7999
     3 spring:
     4   servlet:
     5     multipart:
     6       max-request-size: 100MB
     7       max-file-size: 20MB
     8   http:
     9     encoding:
    10       charset: utf-8
    11       force: true
    12       enabled: true
    13   datasource:
    14     platform: mysql
    15     type: com.alibaba.druid.pool.DruidDataSource
    16     initialSize: 5
    17     minIdle: 3
    18     maxActive: 500
    19     maxWait: 60000
    20     timeBetweenEvictionRunsMillis: 60000
    21     minEvictableIdleTimeMillis: 30000
    22     validationQuery: select 1
    23     testOnBorrow: true
    24     poolPreparedStatements: true
    25     maxPoolPreparedStatementPerConnectionSize: 20
    26     driverClassName: com.mysql.jdbc.Driver
    27     url: jdbc:mysql://192.168.0.91:3306/order_db?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8&useAffectedRows=true&rewriteBatchedStatements=true
    28     username: root
    29     password: root
    30   rabbitmq:
    31     host: localhost
    32     port: 5672
    33     username: guest
    34     password: guest
    35   redis:
    36     host: 192.168.0.91
    37     port: 6379
    38     password: myredis
    39     timeout: 2000
    40     pool:
    41       max-idle: 100
    42       min-idle: 1
    43       max-active: 1000
    44       max-wait: -1

     2. 订单model以及对应的mybatis配置

       本文只是做一个订单的记录,所以表的字段比较简单

         

    1 CREATE TABLE `order_t` (
    2   `order_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键 订单ID',
    3   `user_id` varchar(128) DEFAULT NULL COMMENT '用户Id',
    4   `product_id` varchar(128) DEFAULT NULL COMMENT '产品Id',
    5   `create_time` bigint(20) DEFAULT NULL COMMENT '时间',
    6   PRIMARY KEY (`order_id`)
    7 ) ENGINE=InnoDB AUTO_INCREMENT=36255 DEFAULT CHARSET=utf8mb4;

      

     1 package com.devin.order.model;
     2 
     3 import lombok.Data;
     4 
     5 import javax.persistence.*;
     6 import java.io.Serializable;
     7 
     8 /**
     9  * @author Devin Zhang
    10  * @className Order
    11  * @description TODO
    12  * @date 2020/4/25 11:03
    13  */
    14 @Data
    15 @Table(name = "order_t")
    16 public class Order implements Serializable {
    17 
    18     @Id
    19     @Column(name = "order_id")
    20     @GeneratedValue(strategy= GenerationType.IDENTITY)
    21     private Integer orderId;
    22 
    23     private String userId;
    24     private String productId;
    25     private Long createTime;
    26 
    27 }

      

      本项目中使用了mybatis的通用mapper tkmybatis,所以配置文件中都是空的

       

    OrderMapper.java
     1 package com.devin.order.mapper;
     2 
     3 import com.devin.order.model.Order;
     4 import tk.mybatis.mapper.common.Mapper;
     5 import tk.mybatis.mapper.common.MySqlMapper;
     6 
     7 /**
     8  * @author Devin Zhang
     9  * @className OrderMapper
    10  * @description TODO
    11  * @date 2020/4/22 16:24
    12  */
    13 
    14 public interface OrderMapper extends Mapper<Order>, MySqlMapper<Order> {
    15 }

     OrderMapper.xml

    1 <?xml version="1.0" encoding="UTF-8" ?>
    2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    3 <mapper namespace="com.devin.order.mapper.OrderMapper" >
    4 
    5 </mapper>

     3.  Redis工具类

       

      1 package com.devin.order.util;
      2 
      3 import lombok.extern.slf4j.Slf4j;
      4 import org.apache.commons.lang3.StringUtils;
      5 import org.springframework.data.redis.core.RedisTemplate;
      6 import org.springframework.data.redis.serializer.GenericToStringSerializer;
      7 import org.springframework.stereotype.Component;
      8 
      9 import javax.annotation.PostConstruct;
     10 import javax.annotation.Resource;
     11 
     12 /**
     13  * @author Devin Zhang
     14  * @className RedisClient
     15  * @description TODO
     16  * @date 2020/4/24 17:51
     17  */
     18 
     19 @Slf4j
     20 @Component
     21 public class RedisClient {
     22 
     23     @Resource
     24     private RedisTemplate<String, Object> redisTemplate;
     25 
     26     @PostConstruct
     27     public void init() {
     28         redisTemplate.setKeySerializer(new GenericToStringSerializer<>(String.class));
     29     }
     30 
     31 
     32     /**
     33      * redis存值
     34      *
     35      * @param key   键
     36      * @param value 值
     37      */
     38     public void set(String key, Object value) {
     39         redisTemplate.opsForValue().set(key, value);
     40     }
     41 
     42     /**
     43      * hash存
     44      *
     45      * @param key   键
     46      * @param hash  hash
     47      * @param value 值
     48      */
     49     public void set(String key, String hash, String value) {
     50         redisTemplate.opsForHash().put(key, hash, value);
     51     }
     52 
     53 
     54     /**
     55      * redis获取值
     56      *
     57      * @param key 键
     58      * @return 返回值
     59      */
     60     public Object get(String key) {
     61         return redisTemplate.opsForValue().get(key);
     62     }
     63 
     64     /**
     65      * hash取值
     66      *
     67      * @param key  键
     68      * @param hash hash
     69      * @return 返回redis存储的值
     70      */
     71     public String get(String key, String hash) {
     72         return (String) redisTemplate.opsForHash().get(key, hash);
     73     }
     74 
     75 
     76     /**
     77      * 获取redis的锁
     78      *
     79      * @param key   键
     80      * @param value 值为当前毫秒数+过期时间毫秒数
     81      * @return 返回true/false
     82      */
     83     public boolean lock(String key, String value) {
     84         if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
     85             //加锁成功就返回true
     86             return true;
     87         }
     88         //不加下面这个可能出现死锁情况
     89         //value为当前时间+超时时间
     90         //获取上一个锁的时间,并判断是否小于当前时间,小于就下一步判断,就返回true加锁成功
     91         //currentValue=A 这两个线程的value都是B 其中一个线程拿到锁
     92         String currentValue = (String) redisTemplate.opsForValue().get(key);
     93         //如果锁过期
     94         if (!StringUtils.isEmpty(currentValue)
     95                 && Long.parseLong(currentValue) < System.currentTimeMillis()) {
     96             //存储时间要小于当前时间
     97             //出现死锁的另一种情况,当多个线程进来后都没有返回true,接着往下执行,执行代码有先后,而if判断里只有一个线程才能满足条件
     98             //oldValue=currentValue
     99             //多个线程进来后只有其中一个线程能拿到锁(即oldValue=currentValue),其他的返回false
    100             //获取上一个锁的时间
    101             String oldValue = (String) redisTemplate.opsForValue().getAndSet(key, value);
    102             if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
    103                 //上一个时间不为空,并且等于当前时间
    104                 return true;
    105             }
    106 
    107         }
    108         return false;
    109     }
    110 
    111 
    112     /**
    113      * redis释放锁
    114      *
    115      * @param key   键
    116      * @param value 值
    117      */
    118     public void unlock(String key, String value) {
    119         //执行删除可能出现异常需要捕获
    120         try {
    121             String currentValue = (String) redisTemplate.opsForValue().get(key);
    122             if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
    123                 //如果不为空,就删除锁
    124                 redisTemplate.opsForValue().getOperations().delete(key);
    125             }
    126         } catch (Exception e) {
    127             log.error("[redis分布式锁] 解锁", e);
    128         }
    129     }
    130 
    131 }

     4.  RabbitMQ配置

       

          4.1 定义消息队列的一些常量

         

     1 package com.devin.order.config;
     2 
     3 /**
     4  * @author Devin Zhang
     5  * @className RabbitConstants
     6  * @description TODO
     7  * @date 2020/4/25 10:11
     8  */
     9 
    10 public class RabbitConstants {
    11 
    12     /**
    13      * 分列模式
    14      */
    15     public final static String FANOUT_MODE_QUEUE = "fanout.mode";
    16 
    17     /**
    18      * 日志打印队列
    19      */
    20     public final static String QUEUE_LOG_PRINT = "queue.log.recode";
    21 
    22     /**
    23      * 主题模式
    24      */
    25     public final static String TOPIC_MODE_QUEUE = "topic.mode";
    26 
    27     /**
    28      * 主题模式
    29      */
    30     public final static String TOPIC_ROUTING_KEY = "topic.*";
    31 
    32 }

         4.2 消息队列配置类

          

     1 package com.devin.order.config;
     2 
     3 /**
     4  * @author Devin Zhang
     5  * @className RabbitMqConfig
     6  * @description TODO
     7  * @date 2020/4/25 10:12
     8  */
     9 
    10 
    11 import lombok.extern.slf4j.Slf4j;
    12 import org.springframework.amqp.core.*;
    13 import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
    14 import org.springframework.amqp.rabbit.core.RabbitTemplate;
    15 import org.springframework.context.annotation.Bean;
    16 import org.springframework.context.annotation.Configuration;
    17 
    18 @Slf4j
    19 @Configuration
    20 public class RabbitMqConfig {
    21 
    22     @Bean
    23     public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
    24         connectionFactory.setPublisherConfirms(true);
    25         connectionFactory.setPublisherReturns(true);
    26         RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
    27         rabbitTemplate.setMandatory(true);
    28         rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData[{}],ack[{}],cause[{}]", correlationData, ack, cause));
    29         rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange[{}],route[{}],replyCode[{}],replyText[{}],message:{}", exchange, routingKey, replyCode, replyText, message));
    30         return rabbitTemplate;
    31     }
    32 
    33     /**
    34      * 日志打印队列
    35      */
    36     @Bean
    37     public Queue logPrintQueue() {
    38         return new Queue(RabbitConstants.QUEUE_LOG_PRINT);
    39     }
    40 
    41     /**
    42      * 分列模式队列
    43      */
    44     @Bean
    45     public FanoutExchange fanoutExchange() {
    46         return new FanoutExchange(RabbitConstants.FANOUT_MODE_QUEUE);
    47     }
    48 
    49     /**
    50      * 分列模式绑定队列
    51      *
    52      * @param logPrintQueue  绑定队列
    53      * @param fanoutExchange 分列模式交换器
    54      */
    55     @Bean
    56     public Binding fanoutBinding(Queue logPrintQueue, FanoutExchange fanoutExchange) {
    57         return BindingBuilder.bind(logPrintQueue).to(fanoutExchange);
    58     }
    59 
    60     /**
    61      * 主题队列
    62      */
    63     @Bean
    64     public Queue topicQueue() {
    65         return new Queue(RabbitConstants.TOPIC_ROUTING_KEY);
    66     }
    67 
    68     /**
    69      * 主题模式队列
    70      * <li>路由格式必须以 . 分隔,比如 user.email 或者 user.aaa.email</li>
    71      * <li>通配符 * ,代表一个占位符,或者说一个单词,比如路由为 user.*,那么 user.email 可以匹配,但是 user.aaa.email 就匹配不了</li>
    72      * <li>通配符 # ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 user.#,那么 user.email 可以匹配,user.aaa.email 也可以匹配</li>
    73      */
    74     @Bean
    75     public TopicExchange topicExchange() {
    76         return new TopicExchange(RabbitConstants.TOPIC_MODE_QUEUE);
    77     }
    78 
    79     /**
    80      * 主题模式绑定队列2
    81      *
    82      * @param topicQueue    主题队列
    83      * @param topicExchange 主题模式交换器
    84      */
    85     @Bean
    86     public Binding topicBinding(Queue topicQueue, TopicExchange topicExchange) {
    87         return BindingBuilder.bind(topicQueue).to(topicExchange).with(RabbitConstants.TOPIC_ROUTING_KEY);
    88     }
    89 
    90 }

     5.  抢单逻辑service

        5.1  OrderService

          

     1 package com.devin.order.Service;
     2 
     3 import com.devin.order.config.RabbitConstants;
     4 import com.devin.order.mapper.OrderMapper;
     5 import com.devin.order.model.Order;
     6 import com.devin.order.util.RedisClient;
     7 import org.springframework.amqp.rabbit.core.RabbitTemplate;
     8 import org.springframework.stereotype.Component;
     9 
    10 import javax.annotation.PostConstruct;
    11 import javax.annotation.Resource;
    12 
    13 /**
    14  * @author Devin Zhang
    15  * @className OrderService
    16  * @description TODO
    17  * @date 2020/4/25 11:14
    18  */
    19 
    20 @Component
    21 public class OrderService {
    22 
    23     public static final String PRODUCT_ID_KEY = "PID001_";
    24     private static final Integer PRODUCT_COUNT = 5000;
    25 
    26     private static final String HAS_BUY_USER_KEY = "HAS_BUY_USER_KEY_";
    27 
    28     private static final String LOCK_KEY = "LOCK_KEY_";
    29 
    30 
    31     private static final String FAIL_BUYED = "已经买过了";
    32     private static final String BUYE_SUCCESS = "抢到了,订单生成中";
    33     private static final String FAIL_SOLD_OUT = "没货了";
    34     private static final String FAIL_BUSY = "排队中,请重试!";
    35 
    36     @Resource
    37     private RedisClient redisClient;
    38 
    39     @Resource
    40     private OrderMapper orderMapper;
    41 
    42     @Resource
    43     private RabbitTemplate rabbitTemplate;
    44 
    45 
    46     @PostConstruct
    47     public void initOrder() {
    48         redisClient.set(PRODUCT_ID_KEY, PRODUCT_COUNT);
    49         System.out.println("商品已经初始化完成:数量:" + PRODUCT_COUNT);
    50     }
    51 
    52     /**
    53      * 下单
    54      *
    55      * @param userId
    56      */
    57     public String insertOrder(String userId) {
    58 
    59         //判断用户是否已买
    60         Object hasBuy = redisClient.get(HAS_BUY_USER_KEY, userId);
    61         if (hasBuy != null) {
    62             return FAIL_BUYED;
    63         }
    64 
    65         //10s自动过期
    66         int redisExpireTime = 10 * 1000;
    67         long lockValue = System.currentTimeMillis() + redisExpireTime;
    68         //后去redis锁,只有获取成功才能继续操作
    69         boolean getLock = redisClient.lock(LOCK_KEY, String.valueOf(lockValue));
    70         System.out.println(userId + " getLock:" + getLock);
    71         if (getLock) {
    72             Integer productCount = (Integer) redisClient.get(PRODUCT_ID_KEY);
    73             System.out.println("productCount:" + productCount);
    74             //库存大于0才能继续下单
    75             if (productCount > 0) {
    76 
    77                 rabbitTemplate.convertAndSend(RabbitConstants.TOPIC_MODE_QUEUE, "topic.queue", userId);
    78 
    79                 //减库存
    80                 redisClient.set(PRODUCT_ID_KEY, (productCount - 1));
    81                 //记录用户已买
    82                 redisClient.set(HAS_BUY_USER_KEY, userId, "1");
    83                 //手动释放锁
    84                 redisClient.unlock(LOCK_KEY, String.valueOf(lockValue));
    85                 return BUYE_SUCCESS;
    86             } else {
    87                 System.out.println("亲," + FAIL_SOLD_OUT);
    88                 //手动释放锁
    89                 redisClient.unlock(LOCK_KEY, String.valueOf(lockValue));
    90                 return FAIL_SOLD_OUT;
    91             }
    92         } else {
    93             return FAIL_BUSY;
    94         }
    95     }
    96 
    97 
    98 }

        5.2  消息队列处理订单入库

           

     1 package com.devin.order.Service;
     2 
     3 /**
     4  * 【消息队列处理器】
     5  *
     6  * @author Devin Zhang
     7  * @className RabbitMqHandler
     8  * @description TODO
     9  * @date 2020/4/25 10:54
    10  */
    11 
    12 
    13 import com.devin.order.config.RabbitConstants;
    14 import com.devin.order.mapper.OrderMapper;
    15 import com.devin.order.model.Order;
    16 import com.devin.order.util.RedisClient;
    17 import lombok.extern.slf4j.Slf4j;
    18 import org.springframework.amqp.rabbit.annotation.RabbitListener;
    19 import org.springframework.stereotype.Component;
    20 
    21 import javax.annotation.Resource;
    22 
    23 
    24 @Slf4j
    25 @Component
    26 public class RabbitMqHandler {
    27 
    28     @Resource
    29     private RedisClient redisClient;
    30 
    31     @Resource
    32     private OrderMapper orderMapper;
    33 
    34     /**
    35      * 日志打印处理handler
    36      *
    37      * @param message 待处理的消息体
    38      */
    39     @RabbitListener(queues = RabbitConstants.QUEUE_LOG_PRINT)
    40     public void queueLogPrintHandler(String message) {
    41         log.info("接收到操作日志记录消息:[{}]", message);
    42     }
    43 
    44     /**
    45      * 主题模式处理handler
    46      *
    47      * @param message 待处理的消息体
    48      */
    49     @RabbitListener(queues = RabbitConstants.TOPIC_ROUTING_KEY)
    50     public void queueTopicHandler(String message) {
    51         log.info("主题模式处理器,接收消息:[{}]", message);
    52 
    53         //todo
    54 
    55         String userId = message;
    56 
    57         //产生订单
    58         System.out.println("userId:" + userId);
    59         Order order = new Order();
    60         order.setProductId(OrderService.PRODUCT_ID_KEY);
    61         order.setUserId(userId);
    62         order.setCreateTime(System.currentTimeMillis());
    63         orderMapper.insert(order);
    64 
    65 
    66         System.out.println("用户:" + userId + "下单成功");
    67 
    68     }
    69 
    70 }

       6. 启动类和Controller

          6.1 启动类

          

     1 package com.devin.order;
     2 
     3 import org.springframework.boot.SpringApplication;
     4 import org.springframework.boot.autoconfigure.SpringBootApplication;
     5 import tk.mybatis.spring.annotation.MapperScan;
     6 
     7 
     8 @MapperScan("com.devin.Order.mapper")
     9 @SpringBootApplication
    10 public class OrderGrabbingApplication {
    11 
    12     public static void main(String[] args) {
    13         SpringApplication.run(OrderGrabbingApplication.class, args);
    14     }
    15 
    16 }

       6.2  抢单Controller

          OrderController

          

    package com.devin.order.controller;
    
    import com.devin.order.Service.OrderService;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * @author Devin Zhang
     * @className JobController
     * @description TODO
     * @date 2020/4/22 16:36
     */
    @RestController
    @RequestMapping("/order")
    public class OrderController {
    
        @Resource
        private OrderService orderService;
    
        @GetMapping("/addOrder")
        public String addOrder(String userId) {
            return orderService.insertOrder(userId);
        }
    
    }

     后续只需要在controller中添加方法用于查询用户对应订单,前台定时轮询即可

         7. 测试

         写一个多线程程序进行测试,可以看到最后的数据完全正确

          

     1 import java.io.BufferedReader;
     2 import java.io.IOException;
     3 import java.io.InputStreamReader;
     4 import java.net.HttpURLConnection;
     5 import java.net.URL;
     6 import java.util.UUID;
     7 import java.util.concurrent.ExecutorService;
     8 import java.util.concurrent.Executors;
     9 
    10 /**
    11  * @author Devin Zhang
    12  * @className Mythread
    13  * @description TODO
    14  * @date 2020/4/20 14:02
    15  */
    16 
    17 public class OrderThreadTest implements Runnable {
    18     @Override
    19     public void run() {
    20         try {
    21             httpURLGETCase();
    22         } catch (Exception e) {
    23             e.printStackTrace();
    24         }
    25 
    26     }
    27 
    28     private void httpURLGETCase() {
    29         String userId = UUID.randomUUID().toString().replaceAll("-", "");
    30         String methodUrl = "http://192.168.0.91:7999/order/addOrder?userId=" + userId;
    31 
    32         System.out.println("开始访问:" + methodUrl);
    33 
    34         HttpURLConnection connection = null;
    35         BufferedReader reader = null;
    36         String line = null;
    37         try {
    38             URL url = new URL(methodUrl);
    39             connection = (HttpURLConnection) url.openConnection();
    40             // 根据URL生成HttpURLConnection
    41             connection.setRequestMethod("GET");
    42             // 默认GET请求
    43             connection.connect();
    44             // 建立TCP连接
    45             if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
    46                 reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
    47                 // 发送http请求
    48                 StringBuilder result = new StringBuilder();
    49                 // 循环读取流
    50                 while ((line = reader.readLine()) != null) {
    51                     result.append(line).append(System.getProperty("line.separator"));
    52                     // "
    "
    53                 }
    54                 System.out.println("结果" + result.toString());
    55                 if (result.toString().contains("没货了")) {
    56                     long endTine = System.currentTimeMillis();
    57                     long useTime = endTine - beginTime;
    58                     //共耗时:102041毫秒
    59                     //共耗时:82159毫秒
    60                     System.out.println("共耗时:" + useTime + "毫秒");
    61                     System.exit(0);
    62                 }
    63             }
    64         } catch (IOException e) {
    65             e.printStackTrace();
    66         } finally {
    67             try {
    68                 reader.close();
    69             } catch (IOException e) {
    70                 e.printStackTrace();
    71             }
    72             connection.disconnect();
    73         }
    74     }
    75 
    76     static long beginTime;
    77 
    78     public static void main(String[] args) {
    79 
    80         beginTime = System.currentTimeMillis();
    81 
    82         ExecutorService es = Executors.newFixedThreadPool(10000);
    83         OrderThreadTest mythread = new OrderThreadTest();
    84         Thread thread = new Thread(mythread);
    85         for (int i = 0; i < 1000001; i++) {
    86             es.execute(thread);
    87         }
    88     }
    89 }

      git地址 https://github.com/devinzhang0209/order_grabbing_demo.git

  • 相关阅读:
    简单构建一个xmlhttp对象池合理创建和使用xmlhttp对象
    iBATIS.net获取运行时sql语句
    不做自了汉,大家好才是真的好
    sql查询,nolock写还是不写,这是一个问题
    Sublime Text 2 快捷键用法大全(转)
    javascript设计模式入门之策略模式
    记一次外单前端页面编写小结
    代码驾驭
    一次项目总结,内容设置页面
    【百度地图API】今日小年大进步,齐头共进贺佳节——API优化升级上线,不再增加内存消耗
  • 原文地址:https://www.cnblogs.com/DevinZhang1990/p/12795908.html
Copyright © 2020-2023  润新知