项目源码下载地址:
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());
}
}