基于springboot+redis+rabbitmq的高并发秒杀系统实现-5

项目源码下载地址:

https://github.com/wangqianlong513/springboot-redis-rabbitmq-seckill

上一篇讲到秒杀过程,在后台的秒杀方法miaosha中通过sender.sendMiaoshaMessage(mm)向队列中发送了mm。

1、上面的mm是封装类MiaoshaMessage的一个实例对象,此类中封装了user和商品goodsId。

public class MiaoshaMessage {
	private MiaoshaUser user;
	private long goodsId;
	// set 和 get方法
}

2、sendMiaoshaMessage方法如下。通过rabbitmq的convertAndSend方法,向MIAOSHA_QUEUE发送数据msg,msg就是上面sender.sendMiaoshaMessage(mm)中的mm经过beanToString转化后的字符串。

public void sendMiaoshaMessage(MiaoshaMessage mm) {
		String msg = RedisService.beanToString(mm);
		log.info("send message:"+msg);
		rabbitTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE, msg);
	}

3、下面这个是监听队列MIAOSHA_QUEUE的方法。方法中也要对库存和是否秒杀到此商品进行判断。然后执行miaoshaService中的miaosha方法。

        @RabbitListener(queues=MQConfig.MIAOSHA_QUEUE)
		public void receive(String message) {
			log.info("receive message:"+message);
			MiaoshaMessage mm  = RedisService.stringToBean(message, MiaoshaMessage.class);
			MiaoshaUser user = mm.getUser();
			long goodsId = mm.getGoodsId();
			
			GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
	    	int stock = goods.getStockCount();
	    	if(stock <= 0) {
	    		return;
	    	}
	    	//判断是否已经秒杀到了
	    	MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
	    	if(order != null) {
	    		return;
	    	}
	    	//减库存 下订单 写入秒杀订单
	    	miaoshaService.miaosha(user, goods);
		}

4、miaosha方法如下。整个方法使用了@Transactional注解标记,是一组事务操作。其中,调用了orderService中的createOrder方法。

@Transactional
	public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
		//减库存 下订单 写入秒杀订单
		boolean success = goodsService.reduceStock(goods);
		if(success) {
			//order_info maiosha_order
			return orderService.createOrder(user, goods);
		}else {
			setGoodsOver(goods.getId());
			return null;
		}
	}

5、createOrder方法如下。此方法中调用了orderDao的insertMiaoshaOrder方法向数据库中插入订单,同时最后要把订单保存到redis中。还有比较重要的三个操作:

(1)mqSender.sendEmai(orderInfo);       往邮件队列中发送订单信息

(2)mqSender.sendSms(orderInfo);        往短信队列中发送订单信息

(3)mqSender.sendOrderMessage(orderInfo);      这个是配合死信队列的队列

@Autowired
    private JavaMailSender mailSender;

    public void sendMail(String mail, OrderInfo orderInfo) throws  Exception{
        //1、创建一个复杂的消息邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        //邮件设置
        helper.setSubject("秒杀成功邮件提醒");
        helper.setText("<b style='color:red'>恭喜,秒杀到了商品"+orderInfo.getId()+"</b>",true);
        helper.setTo(mail);
        helper.setFrom("1187674187@qq.com");
        mailSender.send(mimeMessage);
    }

6、mqSender.sendEmai(orderInfo) 往邮件队列中发送订单信息

(1)往邮件队列中发送订单数据

// 下单后发送邮件
	public void sendEmai(OrderInfo orderInfo){
		String msg = RedisService.beanToString(orderInfo);
		rabbitTemplate.convertAndSend(MQConfig.EMAIL_EXCHANGE,"",msg);
	}

(2)监听邮件队列。此方法中调用了mailService中的sendMail方法。接收邮件是账号miaoshaUser用户的email。

//监听邮件对列,监听到消息就向指定的用户邮箱发送邮件提醒
		@RabbitListener(queues=MQConfig.EMAIL_QUEUE)
		public void receiveEmail(String message) throws  Exception{
			System.out.println("邮件监听");
			OrderInfo orderInfo  = RedisService.stringToBean(message, OrderInfo.class);
			long userId = orderInfo.getUserId();
			MiaoshaUser miaoshaUser = miaoshaUserService.getById(userId);
			mailService.sendMail(miaoshaUser.getMail(),orderInfo);
		}

(3)sendMail发送邮件方法

public void sendMail(String mail, OrderInfo orderInfo) throws  Exception{
        //1、创建一个复杂的消息邮件
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        //邮件设置
        helper.setSubject("秒杀成功邮件提醒");
        helper.setText("<b style='color:red'>恭喜,秒杀到了商品"+orderInfo.getId()+"</b>",true);
        helper.setTo(mail);
        helper.setFrom("1187674187@qq.com");
        mailSender.send(mimeMessage);
    }

7、mqSender.sendSms(orderInfo)往短信队列中发送订单信息

(1)往短信队列中发送订单数据。首先使用了Math.random生成了6位随机码。Sms是封装的要发送到短信队列中的数据。Sms中封装了如下4个属性。

// 手机号

private String mobile;

// 随机生成的验证码

private String smsCode;

// 订阅阿里大于短信服务时的模板名称

private String templateCode;

// 订阅阿里大于短信服务时的签名 private String signName;

// 下单后发送短信
	public void sendSms(OrderInfo orderInfo){
		//1.生成一个6位随机数(验证码)
		final String smsCode=  (long)(Math.random()*1000000)+"";
		//final String smsCode = "rabbit";
		System.out.println("验证码:"+smsCode);
		Sms sms =new Sms("18298029187",smsCode,template_code,sign_name);
		String msg = RedisService.beanToString(sms);
		rabbitTemplate.convertAndSend(MQConfig.SMS_EXCHANGE,"",msg);
	}

(2)监听短信队列。此方法中调用了smsUtil中的发送短信的方法sendSms

//监听短信对列,监听到消息就向指定的用户邮箱发送邮件提醒
	@RabbitListener(queues=MQConfig.SMS_QUEUE)
	public void receiveSms(String message) throws  Exception{
		System.out.println("短信监听");
		Sms sms  = RedisService.stringToBean(message, Sms.class);
		Map param = new HashMap();
		// getSmsCode是短信验证码。
		param.put("code",sms.getSmsCode());
		try {
			SendSmsResponse response = smsUtil.sendSms(sms.getMobile(),
					sms.getTemplateCode(),
					sms.getSignName(),
					JSON.toJSONString(param));
			System.out.println("code:"+response.getCode());
			System.out.println("message:"+response.getMessage());


		} catch (ClientException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

(3)smsUtil中的发送短信的方法sendSms如下。

@Component
public class SmsUtil {
    //产品名称:云通信短信API产品,开发者无需替换
    static final String product = "Dysmsapi";
    //产品域名,开发者无需替换
    static final String domain = "dysmsapi.aliyuncs.com";
    // TODO 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
    @Autowired
    private Environment env;
    public SendSmsResponse sendSms(String mobile,String template_code,String sign_name,String param) throws ClientException {
    	String accessKeyId=env.getProperty("accessKeyId");
    	String accessKeySecret=env.getProperty("accessKeySecret");
        //可自助调整超时时间
        System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
        System.setProperty("sun.net.client.defaultReadTimeout", "10000");
        //初始化acsClient,暂不支持region化
        IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
        DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
        IAcsClient acsClient = new DefaultAcsClient(profile);
        //组装请求对象-具体描述见控制台-文档部分内容
        SendSmsRequest request = new SendSmsRequest();
        //必填:待发送手机号
        request.setPhoneNumbers(mobile);
        //必填:短信签名-可在短信控制台中找到
        request.setSignName(sign_name);
        //必填:短信模板-可在短信控制台中找到
        request.setTemplateCode(template_code);
        //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
        request.setTemplateParam(param);
        //选填-上行短信扩展码(无特殊需求用户请忽略此字段)
        //request.setSmsUpExtendCode("90997");
        //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
        request.setOutId("yourOutId");
        //hint 此处可能会抛出异常,注意catch
        SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
        return sendSmsResponse;
    }
}

8、mqSender.sendOrderMessage(orderInfo)这个是配合死信队列的队列

(1)往队列中发送订单数据。这是个特殊的队列,特殊在其中的消息是设置了TTL时间,如下面mp.setExpiration(String.valueOf(1000*20)),设置了TTL时间是20秒。而且此队列在声明的时候也绑定了特殊的死信交换机。当队列中的消息达到了ttl时间,则把此消息发送到死信交换机中,由死信交换机再路由到死信队列中处理。本系统中,超时的订单被路由到死信队列中,然后被修改了订单状态。

// 下订单的时候,向普通队列中发送此消息
	public void sendOrderMessage(final OrderInfo orderInfo){
		String msg = RedisService.beanToString(orderInfo);
		try {
			if (orderInfo!=null){
				// 因为普通交换机是NORMAL_EXCHANGE,且普通交换机和正常队列的绑定键是NORMAL_KEY
				// 所以此处发送的消息会被路由到普通队列
				rabbitTemplate.convertAndSend("NORMAL_EXCHANGE","NORMAL_KEY",msg, new MessagePostProcessor() {
					@Override
					public Message postProcessMessage(Message message) throws AmqpException {
						MessageProperties mp=message.getMessageProperties();
						//TODO:动态设置TTL(为了测试方便,暂且设置10s)
						mp.setExpiration(String.valueOf(1000*20));
						System.out.println("in the sendOrderMessage hs");
						return message;
					}
				});
			}
		}catch (Exception e){
			log.error("秒杀成功后生成抢购订单-发送信息入死信队列,等待着一定时间失效超时未支付的订单-发生异常,消息为:{}",orderInfo.getId(),e.fillInStackTrace());
		}
	}

(2)监听死信队列。调用了orderService中的updateStatus方法修改了订单状态。然后增加了redis中的库存数量。

@RabbitListener(queues = "DEAD_LETTER_QUEUE")
	public void consumeExpireOrder(String message){
		OrderInfo orderInfo  = RedisService.stringToBean(message, OrderInfo.class);
		System.out.println("in the 死信监听中");
		try {
			log.info("用户秒杀成功后超时未支付-监听者-接收消息:{}",orderInfo);
			if (orderInfo!=null && orderInfo.getStatus().intValue()==0){
				orderService.updateStatus(orderInfo.getId());
				long goodsId = orderInfo.getGoodsId();
				//修改库存
				long stock = redisService.incr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);
			}
		}catch (Exception e){
			log.error("用户秒杀成功后超时未支付-监听者-发生异常:",e.fillInStackTrace());
		}
	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值