使用spring boot + mybatis + RabbitMQ + redis模拟商城秒杀功能

谢谢观看,希望对读友有用,话不多说,直接讲解

首先我们需要安装RabbitMQ,在安装RabbitMQ之前需要先安装Erlang,因为RabbitMQ是基于Erlang的,具体的步骤点击下面的这个链接:

点击此处:安装Erlang和RabbitMQ的详细步骤

安装完RabbitMQ如下图:


安装完成后可以在浏览器的地址栏上输入:http://localhost:15672/#/users默认账户和密码都是 guset

环境搭好之后,接下来就可以开始撸代码了

由于我是maven环境的,这里就直接使用jar坐标
rabbitMQ

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
        <version>1.5.8.RELEASE</version>
    </dependency>

redis

	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

mybatis

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator</artifactId>
        <version>1.3.5</version>
        <type>pom</type>
    </dependency>

    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.5</version>
    </dependency>

创建测试数据库结构


– Table structure for stock


DROP TABLE IF EXISTS stock;
CREATE TABLE stock (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
stock varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;


– Table structure for t_order


DROP TABLE IF EXISTS t_order;
CREATE TABLE t_order (
id int(11) NOT NULL AUTO_INCREMENT,
order_name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
order_user varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

创建对应的dao mapper xml

创建完成后,首先创建一个监听器初始化数据到redis中,这一步可以根据自己的业务逻辑进行相应的改动

package com.ceshi.demo.service;

import com.ceshi.demo.dao.StockMapper;
import com.ceshi.demo.pojo.Stock;
import com.ceshi.demo.pojo.StockExample;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Iterator;
import java.util.List;

/**
 * @author nonpool
 * @version 1.0
 * @since 2020/2/11
 */
@Component
@Slf4j
public class ApplicationInitListener implements ApplicationListener<ContextRefreshedEvent> {
    @Resource
    private StockMapper stockMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 先获取到 application 上下文
        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        log.info(">>>>>>>>>>>>项目初始化完成,执行监听器中逻辑");
            //mapper中的sql,返回全部上架(支持秒杀)的商品集合
            StockExample stockExample = new StockExample();
            List<Stock> stocks = stockMapper.selectByExample(stockExample);
            Iterator<Stock> it = stocks.iterator();
            while(it.hasNext()) {
                Stock p = it.next();
                log.info("商品名称:"+p.getName()+"商品库存:"+p.getStock());
                try {
                    stringRedisTemplate.opsForValue().set(String.valueOf(p.getName()), String.valueOf(p.getStock()));
                } catch (Exception e) {
                    log.error("当前商品名称:"+p.getName()+"库存:"+p.getStock()+"放入Redis缓存异常<<<<<<<<<<<<<<<<<<<<");
                    e.printStackTrace();
                }
            }
    }
}

配置rabbitMQConfig队列

package com.ceshi.demo.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 配置rabbitMQConfig队列
 * @version 1.0
 * @since 2020/2/11
 */
@Configuration
public class RabbitMQCongig {
    //库存交换机
    public static final String STORY_EXCHANGE = "STORY_EXCHANGE";

    //订单交换机
    public static final String ORDER_EXCHANGE = "ORDER_EXCHANGE";

    //库存队列
    public static final String STORY_QUEUE = "STORY_QUEUE";

    //订单队列
    public static final String ORDER_QUEUE = "ORDER_QUEUE";

    //库存路由键
    public static final String STORY_ROUTING_KEY = "STORY_ROUTING_KEY";

    //订单路由键
    public static final String ORDER_ROUTING_KEY = "ORDER_ROUTING_KEY";
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    //创建库存交换机
    @Bean
    public Exchange getStoryExchange() {
        return ExchangeBuilder.directExchange(STORY_EXCHANGE).durable(true).build();
    }
    //创建库存队列
    @Bean
    public Queue getStoryQueue() {
        return new Queue(STORY_QUEUE);
    }
    //库存交换机和库存队列绑定
    @Bean
    public Binding bindStory() {
        return BindingBuilder.bind(getStoryQueue()).to(getStoryExchange()).with(STORY_ROUTING_KEY).noargs();
    }
    //创建订单队列
    @Bean
    public Queue getOrderQueue() {
        return new Queue(ORDER_QUEUE);
    }
    //创建订单交换机
    @Bean
    public Exchange getOrderExchange() {
        return ExchangeBuilder.directExchange(ORDER_EXCHANGE).durable(true).build();
    }
    //订单队列与订单交换机进行绑定
    @Bean
    public Binding bindOrder() {
        return BindingBuilder.bind(getOrderQueue()).to(getOrderExchange()).with(ORDER_ROUTING_KEY).noargs();
    }

}

创建MQ监听订单消息列队,并消费

package com.ceshi.demo.service;

import com.ceshi.demo.config.RabbitMQCongig;
import com.ceshi.demo.dao.TOrderMapper;
import com.ceshi.demo.pojo.TOrder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author 
 * @version 1.0
 * @since 2020/2/11
 */
@Service
@Slf4j
public class MQOrderService {
    @Resource
    private TOrderMapper orderMapper;
    /**
     * 监听订单消息队列,并消费
     *
     * @param order
     */
    @RabbitListener(queues = RabbitMQCongig.ORDER_QUEUE)
    public void createOrder(TOrder order) {
        log.info("收到订单消息,订单用户为:{},商品名称为:{}", order.getOrderUser(), order.getOrderName());
        /**
         * 调用数据库orderService创建订单信息
         */
        orderMapper.insert(order);
    }
}

MQ创建监听库存消息列队,并消费

package com.ceshi.demo.service;

import com.ceshi.demo.config.RabbitMQCongig;
import com.ceshi.demo.dao.StockMapper;
import com.ceshi.demo.pojo.Stock;
import com.ceshi.demo.pojo.StockExample;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author 
 * @version 1.0
 * @since 2020/2/11
 * 监听消息队列里面的消费商品进行消费
 */
@Service
@Slf4j
public class MQStockService {
    //消费数据库中的商品表
    @Resource
    private StockMapper stockMapper;
    /**
     * 监听库存消息队列,并消费
     * @param stockName
     */
    @RabbitListener(queues = RabbitMQCongig.STORY_QUEUE)
    public void decrByStock(String stockName) {
        log.info("库存消息队列收到的消息商品信息是:{}", stockName);
        /**
         * 调用数据库service给数据库对应商品库存减一
         */
        //获取当前商品的数量
        StockExample stockExample = new StockExample();
        StockExample.Criteria criteria = stockExample.createCriteria();
        criteria.andNameEqualTo(stockName);
        List<Stock> stocks = stockMapper.selectByExample(stockExample);
        //修改
        Stock stock = new Stock();
        stock.setId(stocks.get(0).getId());
        stock.setName(stocks.get(0).getName());
        stock.setStock((Integer.parseInt(stocks.get(0).getStock())-1)+"");

        stockMapper.updateByPrimaryKey(stock);
    }
}

创建reidsService

package com.ceshi.demo.service;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author chy
 * @version 1.0
 * @since 2020/2/11
 * 设置redisService
 */
@Service
public class RedisService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 对指定key的键值减一
     * @param key
     * @return
     */
    public Long decrBy(String key) {
        return stringRedisTemplate.opsForValue().decrement(key);
    }
}

最后创建controller

package com.ceshi.demo.controller;

import com.ceshi.demo.config.RabbitMQCongig;
import com.ceshi.demo.dao.StockMapper;
import com.ceshi.demo.dao.TOrderMapper;
import com.ceshi.demo.pojo.Stock;
import com.ceshi.demo.pojo.StockExample;
import com.ceshi.demo.pojo.TOrder;
import com.ceshi.demo.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author nonpool
 * @version 1.0
 * @since 2020/2/11
 */
@RestController
@RequestMapping("activity")
@ResponseBody
@Slf4j
public class Ceshi {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisService redisService;
    @Resource
    private TOrderMapper tOrderMapper;
    @Resource
    private StockMapper stockMapper;
    /**
     * 使用redis+消息队列进行秒杀实现
     *
     * @param username
     * @param stockName
     * @return
     */
    @RequestMapping("sec")
    @ResponseBody
    public String sec(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) {

        log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, stockName);
        String message = null;
        //调用redis给相应商品库存量减一
        Long decrByResult = redisService.decrBy(stockName);
        if (decrByResult >= 0) {
            /**
             * 说明该商品的库存量有剩余,可以进行下订单操作
             */
            log.info("用户:{}秒杀该商品:{}库存有余,可以进行下订单操作", username, stockName);
            //发消息给库存消息队列,将库存数据减一
            rabbitTemplate.convertAndSend(RabbitMQCongig.STORY_EXCHANGE, RabbitMQCongig.STORY_ROUTING_KEY, stockName);

            //发消息给订单消息队列,创建订单
            TOrder order = new TOrder();
            order.setOrderName(stockName);
            order.setOrderUser(username);
            rabbitTemplate.convertAndSend(RabbitMQCongig.ORDER_EXCHANGE, RabbitMQCongig.ORDER_ROUTING_KEY, order);
            message = "用户" + username + "秒杀" + stockName + "成功";
        } else {
            /**
             * 说明该商品的库存量没有剩余,直接返回秒杀失败的消息给用户
             */
            log.info("用户:{}秒杀时商品的库存量没有剩余,秒杀结束", username);
            message = "用户:"+ username + "商品的库存量没有剩余,秒杀结束";
        }
        return message;
    }


    /**
     * 实现纯数据库操作实现秒杀操作
     * @param username
     * @param stockName
     * @return
     */
    @RequestMapping("secDataBase")
    @ResponseBody
    public String secDataBase(@RequestParam(value = "username") String username, @RequestParam(value = "stockName") String stockName) {
        log.info("参加秒杀的用户是:{},秒杀的商品是:{}", username, stockName);
        String message = null;
        //查找该商品库存
        StockExample stockExample = new StockExample();
        StockExample.Criteria criteria = stockExample.createCriteria();
        criteria.andNameEqualTo(stockName);
        List<Stock> stocks = stockMapper.selectByExample(stockExample);
        Integer stockCount = Integer.parseInt(stocks.get(0).getStock());
        log.info("用户:{}参加秒杀,当前商品库存量是:{}", username, stockCount);
        if (stockCount > 0) {
            /**
             * 还有库存,可以进行继续秒杀,库存减一,下订单
             */
            //1、库存减一
            Stock stock = new Stock();
            stock.setId(stocks.get(0).getId());
            stock.setStock((stockCount-1)+"");
            stock.setName(stocks.get(0).getName());

            stockMapper.updateByPrimaryKey(stock);
            //2、下订单
            TOrder order = new TOrder();
            order.setOrderUser(username);
            order.setOrderName(stockName);
            tOrderMapper.insert(order);
            log.info("用户:{}.参加秒杀结果是:成功", username);
            message = username + "参加秒杀结果是:成功";
        } else {
            log.info("用户:{}.参加秒杀结果是:秒杀已经结束", username);
            message = username + "参加秒杀活动结果是:秒杀已经结束";
        }
        return message;
    }
}

到此结束,各位读友有什么更好的,还请在共同交流下,不足之处还望各位大佬多多指导。

好的,针对您的问题,以下是我对大众商城的设计思路: 1. 架构设计 大众商城的整体架构采用微服务架构设计,将各个模块拆分为不同的服务,以实现服务之间的解耦,减小维护成本。使用Spring Cloud作为微服务框架,采用Eureka作为服务注册中心,Zuul作为网关来提供负载均衡、流量限制等功能。 2. 数据库设计 考虑到大众商城数据量较大,我们选用关系型数据库MySQL进行数据存储,采用主从复制技术来实现数据的高可用性。对于数据安全,我们使用阿里云RDS提供的数据加密技术和数据备份功能,确保数据的可靠性和安全性。 3. 技术选型 后端技术选用Spring Boot作为基础框架,使用MyBatis实现ORM映射;前端技术选用Bootstrap框架实现响应式布局,使用Thymeleaf作为模板引擎,通过AJAX技术实现数据的异步加载和交互。 消息队列技术选用RabbitMQ来实现异步消息传递,同时利用RabbitMQ自带的延迟消息功能来解决消息的定时发送问题。 4. 缓存方案 由于大众商城对数据的访问比较频繁,所以我们在架构中加入了Redis缓存,通过对常用数据的缓存来提升系统的响应速度和性能。 5. 安全性 系统安全方面,我们采用JWT令牌的方式实现用户登录和认证,同时对敏感数据采用AES加密算法进行加密,保证系统的数据安全性和用户隐私保护。 以上是大众商城的设计思路,希望能对您有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值