咖啡汪日志————使用Redisson延迟队列,实现会员到期前N天提醒

这个其实不怎么推荐,当然她使用的话,也是没有问题的,只是因为用到了定时器
所以不如RMapCache好用,大家可以当作是了解Redisson延时队列来看待这篇博客
源码链接:
https://github.com/HuskyCorps/distributeMiddleware
在这里插入图片描述
0.application.properties

#用户会员到期提醒
vip.expire.first.subject=会员即将到期提醒【泰达便民服务平台-http://www.tjxstech.com/】
vip.expire.first.content=手机为:%s 的用户,您好!您的会员有效期即将失效,请您前往平台续费~祝您生活愉快【泰达便民服务平台-http://www.tjxstech.com/】

vip.expire.end.subject=会员到期提醒【泰达便民服务平台-http://www.tjxstech.com/】
vip.expire.end.content=手机为:%s 的用户,您好!您的会员有效期已经失效,为了您有更好的体验,请您前往平台继续续费~祝您生活愉快【泰达便民服务平台-http://www.tjxstech.com/

1.controller

/**
 * Redisson延迟队列DelayedQueue,实现会员到期前N天提醒
 *
 * @author Yuezejian
 * @date 2020年 09月09日 19:57:53
 */
@RestController
@RequestMapping("vip")
public class VipController {
    
    
    private static final Logger log = LoggerFactory.getLogger(VipController.class);

    @Autowired
    private VipService vipService;

    @RequestMapping(value = "put" ,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)application/json;charset=UTF-8
    public BaseResponse putVip(@RequestBody @Validated UserVip userVip, BindingResult result) {
    
    
        String checkRes = ValidatorUtil.checkResult(result);
        if (StringUtils.isNotBlank(checkRes)) {
    
    
            return new BaseResponse(StatusCode.InvalidParams.getCode(),checkRes);
        }
        BaseResponse response = new BaseResponse(StatusCode.Success);
        try {
    
    
            vipService.addVip(userVip);
            log.info("————成功充值会员  "+userVip.getVipDay()+"  天");

        } catch (Exception e) {
    
    
            log.error("——————————充值会员-发生异常:",e.fillInStackTrace());
            response = new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }
}

2.service


/**
 * 基于Redisson延迟队列DelayQueue,实现会员到期前N天提醒
 *
 * @author Yuezejian
 * @date 2020年 09月09日 20:16:35
 */
@Service
public class VipService {
    
    
    private static final Logger log = LoggerFactory.getLogger(VipService.class);

    @Autowired
    private UserVipMapper userVipMapper;

    @Autowired
    private RedissonClient redissonClient;

    @Transactional(rollbackFor = Exception.class)
    public void addVip(UserVip vip) throws Exception {
    
    
        vip.setVipTime(DateTime.now().toDate());
        int res = userVipMapper.insertSelective(vip);
        //TODO:充值成功(现实一般是需要走支付的..在这里以成功插入db为准) - 设置两个过期提醒时间,
        //TODO:一个是vipDay后的;一个是在到达vipDay前 x 的时间
        //TODO:如,vipDay=10天,x=2,即代表vip到期 前2天 提醒一次,vip到期时提醒一次,即
        //TODO:第一次提醒的时间点为:ttl=10-2=8,即距离现在开始的8天后进行第一次提醒;
        //TODO:第二次提醒的时间点是:ttl=10,即距离现在开始的10天后进行第二次提醒   -- 以此类推
        //TODO: (时间的话,建议转化为s;当然啦,具体业务具体设定即可)
        //TODO:基于redisson的延迟队列实现,重点就在于 ttl 的计算
        // (为了测试方便,在这里我们以 s 为单位);
        //TODO:如果是多次提醒的话,需要做标记
        if (res > 0 ) {
    
    
            RBlockingDeque<String> blockingDeque = redissonClient.getBlockingDeque(Constant.RedissonUserVipQueue);
            RDelayedQueue<String> rDelayedQueue = redissonClient.getDelayedQueue(blockingDeque);

            //TODO:第一次提醒
            //这个value,实际是这样的value=vipId+"-"+"1",{value="14-1"}
            // 下面的处理只是为了构架考虑,所以外提了出去。
            //为了方便大家理解,本汪把相关代码都加到了注释里
            // public static final String SplitCharUserVip="-";
            // 用户会员到期前的多次提醒的标识
            //    public enum VipExpireFlg{
    
    
            //        First(1),
            //        End(2),
            //        ;
            //
            //        private Integer type;
            //
            //        VipExpireFlg(Integer type) {
    
    
            //            this.type = type;
            //        }
            //
            //        public Integer getType() {
    
    
            //            return type;
            //        }
            //
            //        public void setType(Integer type) {
    
    
            //            this.type = type;
            //        }
            //    }
            String value = vip.getId()+Constant.SplitCharUserVip+Constant.VipExpireFlg.First.getType();
            Long firstTTL = Long.valueOf(String.valueOf(vip.getVipDay()-Constant.x));
            if ( firstTTL > 0 ) {
    
    
                //在firstTTL秒内,把value对象移动到目标队列中去
                rDelayedQueue.offer(value,firstTTL,TimeUnit.SECONDS);
            }
            //TODO:第二次提醒
            //这个value,实际是value=vipId+"-"+"2",{value="14-2"}
            value = vip.getId()+Constant.SplitCharUserVip+Constant.VipExpireFlg.End.getType();
            Long secondTTL = Long.valueOf(vip.getVipDay());
            //在secondTTL秒内,把value对象移动到目标队列中去
            rDelayedQueue.offer(value,secondTTL,TimeUnit.SECONDS);
            //本汪带大家看一下官方的实例
            //Java的基于Redis的DelayedQueue对象允许将每个元素以指定的延迟传输到目标队列。
            // 对于将消息传递给消费者的指数退避策略可能很有用。目标队列可以是任何队列实现的RQueue接口。
            //RBlockingQueue<String> distinationQueue = ...
            //RDelayedQueue<String> delayedQueue = getDelayedQueue(distinationQueue);
            //--》move object to distinationQueue in 10 seconds
            //delayedQueue.offer("msg1", 10, TimeUnit.SECONDS);
            //--》move object to distinationQueue in 1 minutes
            //delayedQueue.offer("msg2", 1, TimeUnit.MINUTES);
            //
            //
            //--》 msg1 will appear in 15 seconds
            //distinationQueue.poll(15, TimeUnit.SECONDS);
            //
            //--》msg2 will appear in 2 seconds
            //distinationQueue.poll(2, TimeUnit.SECONDS);



        }

    }
}

3.监听器

/**
 * Redisson的延时队列DelayQueue,Vip提前N天提醒——Listener
 *
 * @author Yuezejian
 * @date 2020年 09月09日 21:07:25
 */
@Component
public class VipQueueListener {
    
    
    private static final Logger log = LoggerFactory.getLogger(VipQueueListener.class);


    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private UserVipMapper vipMapper;

    @Autowired
    private MailService mailService;

    @Autowired
    private Environment env;//环境变量实例


    //实时监听延时队列中的代理消息
    @Async("threadPoolTaskExecutor")
    @Scheduled(cron = "0/5 * * * * ?")
    public void listenExpireVip() {
    
    
        RBlockingDeque<String> blockingDeque = redissonClient.getBlockingDeque(Constant.RedissonUserVipQueue);
        if (blockingDeque != null && !blockingDeque.isEmpty()) {
    
    
            //本汪说下,此处我们所取到的element,就是我们
            //rDelayedQueue.offer(value,secondTTL,TimeUnit.SECONDS);
            //放进去的value了,他的格式是“14-1”
            String element = blockingDeque.poll();

            if (StringUtils.isNotBlank(element)) {
    
    
                log.info("Vip提前N天提醒,Redisson的延时队列DelayQueue监听器——Listener,监听到 element={}",element);
                //这时候,你应该知道为什么把分隔符提出去,就是为了使用的统一
                // public static final String SplitCharUserVip="-";
                String[] arr = StringUtils.split(element,Constant.SplitCharUserVip);
                Integer id = Integer.valueOf( arr[0]);
                Integer type = Integer.valueOf(arr[1]);
                UserVip vip = vipMapper.selectByPrimaryKey(id);
                if (vip != null && 1==vip.getIsActive() && StringUtils.isNotBlank(vip.getEmail())) {
    
    
                    //TODO:区分第几次提醒,发送对应消息
                    if (Constant.VipExpireFlg.First.getType().equals(type)) {
    
    
                        log.info("Vip提前N天提醒,第一次提醒");
                        String content=String.format(env.getProperty("vip.expire.first.content"),vip.getPhone());
                        mailService.sendSimpleEmail(env.getProperty("vip.expire.first.subject"),content,vip.getEmail());
                    } else {
    
    
                        //设置数据库內会员信息失效,就是把isActive由“1”变为“0”
                        int res = vipMapper.updateExpireVip(id);
                        if (res > 0) {
    
    
                            log.info("Vip提前N天提醒,第二次提醒");
                            String content=String.format(env.getProperty("vip.expire.end.content"),vip.getPhone());
                            mailService.sendSimpleEmail(env.getProperty("vip.expire.end.subject"),content,vip.getEmail());
                        }
                    }
                }

            }
        }
    }

4.线程池配置

/**
 * 线程池配置类
 *
 * @author Yuezejian
 * @date 2020年 08月22日 22:09:26
 */
@Configuration
public class ThreadConfig {
    
    

    @Bean("threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor(){
    
    
        //ThreadPoolTaskExecutor 底层直接使用了一个BlockingQueue,
        // 初始容量为2147483647(0x7fffffff,2的31次方-1),即无界队列
        //线程池维护线程所允许的空闲时间,默认为60s, keepAliveSeconds = 60
        //其内部使用队列:BlockingQueue<Runnable> queue = this.createQueue(this.queueCapacity);
        //createQueue()方法底层使用了new LinkedBlockingQueue(内部基于链表来存放元素)
        //本汪说下:
        //LinkedBlockingQueue内部分别使用了takeLock 和 putLock 对并发进行控制,也就是说,
        //添加和删除操作并不是互斥操作,可以同时进行,这样也就可以大大提高吞吐量
        ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
        /*executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setKeepAliveSeconds(10);
        executor.setQueueCapacity(8);*/

        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(4);
        executor.setKeepAliveSeconds(10);
        executor.setQueueCapacity(4);

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

5.运行结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42994251/article/details/108502476