咖啡汪推荐————基于Redisson缓存映射MapCache,实现会员到期前N天邮件提醒

会员到期提醒,在我们生活中,还是比较常见的
腾讯视频会员到期前一个星期提醒
阿里云服务器购买后,到期前一个月,一个星期都会有邮件和短信的提醒
QQ音乐到期前半个月,一个星期,3天也都会有到期的提醒
那么今天,本汪就带大家一起来看一下,如何用Redisson缓存映射MapCache
来实现会员到期前N天提醒

先看效果:

在这里插入图片描述

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

(一)开篇有益
本汪先讲使用Redisson的MapCache的优点
1.Redisson组件,缓存映射MapCache 可以很好地解决DB负载过高的问题,每次进行校验,只需从缓存中查询,不需再查数据库。

2.可以解决定时任务处理不及时的问题,通过实现ApplicationRunner, Ordered两个接口,可以在应用启动和运行期间,不间断监听,并执行我们所需的业务逻辑代码。

3.解决了批次查询的数据量可能过大占用过多的缓存的问题

4.MapCache提供了对其中单独元素的失效功能,同时提供了自动监听Key添加,失效,更新,移除的机制
( 二 ) 思维导图+源码
本汪来了:现在由本汪带大家一起看看他的思维导图,其实结合思维导图和源码,就可以解决60%的问题了
依照惯例,先上思维导图和源码链接
思维导图:
在这里插入图片描述

源码链接:https://github.com/HuskyCorps/distributeMiddleware
强烈建议下载源码,搭建环境进行学习。

(三)上代码,开讲
1.建立会员到期提醒类别的枚举类
First(1)代表是会员到期前进行提醒的,
End(2)代表是会员到期后进行提醒的
因为我们需要对不同类型提醒,发送不同的提醒内容

#用户会员到期提醒
vip.expire.first.subject=会员即将到期提醒【腾讯视频-https://v.qq.com/】
vip.expire.first.content=手机为:%s 的用户,您好!您的腾讯视频会员有效期即将失效,请您前往平台续费~祝您生活愉快【腾讯视频-https://v.qq.com/】

vip.expire.end.subject=会员到期提醒【腾讯视频-https://v.qq.com/】
vip.expire.end.content=手机为:%s 的用户,您好!您的腾讯视频会员有效期已经失效,为了您有更好的体验,请您前往平台继续续费~祝您生活愉快【腾讯视频-https://v.qq.com/
  /**
     *   用户会员到期前的多次提醒的标识
     */
  
    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;
        }
    }

1.controller

/**
 * Vip到期提醒Controller
 *
 * @author Yuezejian
 * @date 2020年 09月03日 21:29:43
 */
@RestController
@RequestMapping("user/vip")
public class UserVipController extends AbstractController {

    @Autowired
    private UserVipService 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);
        } catch (Exception e) {
            log.error("——————————充值会员-发生异常:",e.fillInStackTrace());
            response = new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }
}

2.service
在service中我们需要做什么呢?
(1)将用户的充值信息先行插入DB
(2) DB插入成功后,再将充值信息放入缓存中,那么怎么放呢?
假设该用户充值了一个月即30天的会员,而我们想要在会员到期的3天进行提醒
我们可以设置这条充值信息的缓存有效时间为 ttl= 30 - 3
这样也就意味着这条缓存信息将会在充值后,第 27 天时失效
通过对缓存失效的监听,进行会员充值提醒的邮件发送就OK了!

/**
 * Vip到期提醒Service
 *
 * @author Yuezejian
 * @date 2020年 09月03日 21:31:51
 */
@Service
public class UserVipService {
    private static final Logger log = LoggerFactory.getLogger(UserVipService.class);

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private UserVipMapper userVipMapper;

    //充值会员-redisson的MapCache
    @Transactional(rollbackFor = Exception.class)
    public void addVip(UserVip vip) throws Exception {
        vip.setVipTime(DateTime.now().toDate());
        int res = userVipMapper.insertSelective(vip);
        if (res > 0 ) {
            //假设(vipDay = 20,即会员充值20天),20天后失效:第一次提醒 ttl = vipDay - x; 第二次提醒 ttl = vipDay
            //1.到期前N天提醒 2.到期后提醒
            RMapCache<String,Integer> rMapCache = redissonClient.getMapCache(Constant.RedissonUserVIPKey);

            //TODO:第一次提醒,x默认值10,提前10天提醒
            //key =vipId_1 过期前提醒,1是枚举类型First(1)
            String key = vip.getId() + Constant.SplitCharUserVip + Constant.VipExpireFlg.First.getType();//vipId_1 过期前提醒
            Long firstTTL = Long.valueOf(String.valueOf(vip.getVipDay()-Constant.x));
            if (firstTTL > 0) {
                rMapCache.put(key, vip.getId(), firstTTL, TimeUnit.SECONDS);
            }

            //TODO:第二次提醒
            //key =vipId_2 过期前提醒,1是枚举类型End(2)
            key = vip.getId() + Constant.SplitCharUserVip + Constant.VipExpireFlg.End.getType();//vipId_1 过期后提醒
            Long secondTTL = Long.valueOf(String.valueOf(vip.getVipDay()));
            rMapCache.put(key,vip.getId(),secondTTL, TimeUnit.SECONDS);

        }

    }

}

3.监听器
缓存失效时,将会被监听到,我们取出失效的数据(key,value)
取出key= vip用户的ID + "_" + 进行提醒的类型(First(1)或End(2))
例如:vip用户id为 3087 ,进行的是到期前提醒First(1),那么我们取出的key,就会是"3087_1"
我们对key,进行拆分, String [] arr = StringUtils.split(key,"_");
第一个数据就是3087,我们用getVipUserById(3087)来取出vip全部数据
第二个数据就是 1, (Constant.VipExpireFlg.First.getType().equals(type)),我们选用到期前提醒模板,进行邮件发送

/**
 * vip过期提醒的监听器
 *
 * @author Yuezejian
 * @date 2020年 09月03日 22:20:50
 */
@Component
public class RedissonMapCacheUserVip implements ApplicationRunner, Ordered {

    private static final Logger log = LoggerFactory.getLogger(RedissonMapCacheUserVip.class);

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private Environment env;

    @Autowired
    private UserVipMapper vipMapper;

    @Autowired
    private MailService mailService;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("不间断执行自定义操作——————————————————————————————order1");
        this.listenUserVip();

    }

//设置为1,让她以较高的优先级进行执行
    @Override
    public int getOrder() {
        return 1;
    }

    //监听会员过期的数据 1.到期前N天提醒 2.到期后的提醒 需要给相应的用户发送通知(邮件)
    private void listenUserVip() {
        RMapCache<String , Integer> rMapCache = redissonClient.getMapCache(Constant.RedissonUserVIPKey);
          //EntryExpiredListener(org.redisson.api.map.event)缓存失效监听
        rMapCache.addListener(new EntryExpiredListener<String,Integer>() {

            @Override
            public void onExpired(EntryEvent<String, Integer> entryEvent) {
                //key = 充值记录id -类型
                String key = String.valueOf(entryEvent.getKey());
                //value = 充值记录id
                String value = String.valueOf(entryEvent.getValue());
               log.info("————监听用户会员过期信息,监听到数据:key={},value={}",key,value);

               if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
                   String [] arr = StringUtils.split(key,Constant.SplitCharUserVip);
                   Integer id = Integer.valueOf(value);

                   UserVip vip = vipMapper.selectByPrimaryKey(id);
                   if (vip != null && 1==vip.getIsActive() && StringUtils.isNotBlank(vip.getEmail())) {
                       //TODO:区分第几次提醒,发送对应消息
                       Integer type = Integer.valueOf(arr[1]);
                       if (Constant.VipExpireFlg.First.getType().equals(type)) {
                           String content=String.format(env.getProperty("vip.expire.first.content"),vip.getPhone());
                           mailService.sendSimpleEmail(id.toString(),env.getProperty("vip.expire.first.subject"),content,vip.getEmail());
                       } else {
                           //设置数据库內会员信息失效
                          int res = vipMapper.updateExpireVip(id);
                           if (res > 0) {
                               String content=String.format(env.getProperty("vip.expire.end.content"),vip.getPhone());
                               mailService.sendSimpleEmail(id.toString(),env.getProperty("vip.expire.end.subject"),content,vip.getEmail());
                           }
                       }
                   }
               }
            }
        });
    }


}

4.邮件发送组件


import com.google.gson.Gson;
import com.tencent.bigdata.convenience.model.mapper.MsgLogMapper;
import com.tencent.bigdata.convenience.server.controller.AbstractController;
import com.tencent.bigdata.convenience.server.enums.Constant;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
/**
 * 邮件service
 *
 * @author Yuezejian
 * @date 2020年 08月24日 20:23:04
 */
@Service
public class MailService extends AbstractController {
    @Autowired
    private Environment env;

    @Autowired
    private JavaMailSender mailSender;

    @Autowired
    private MsgLogMapper msgLogMapper;

    //TODO:发送简单的邮件消息
    //@Async("threadPoolTaskExecutor")此处切记不可使用AOP注解,方法进入切面后,由于@Around,返回类型boolean会被设为void,会造成异常
    //Caused by: org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type
    //仍想使用异步线程处理,可以修改ACK实现方法,将返回类型设为void即可
    //或对mailSender.send做包装
    public Boolean sendSimpleEmail(final String msgId,final String subject,final String content,final String ... tos) throws Exception {
           Boolean res = true;
            SimpleMailMessage message=new SimpleMailMessage();
            message.setSubject(subject);
            message.setText(content);
            message.setTo(tos);
            message.setFrom(env.getProperty("mail.send.from"));
        try {
            mailSender.send(message);
        } catch (MailException e) {
            log.error("邮件发送失败, to={}, title={}, e={}", new Gson().toJson(tos), subject, e);
            res = false;
            throw e;
        } finally {
            this.updateMsgSendStatus(msgId,res);
        }
        return res;
    }

    //TODO:更新消息处理的结果
    private void updateMsgSendStatus(final String msgId,Boolean res){
        if (StringUtils.isNotBlank(msgId)){
            if (res){
                msgLogMapper.updateStatus(msgId, Constant.CONSUME_SUCCESS);
            }else{
                msgLogMapper.updateStatus(msgId, Constant.CONSUME_FALSE);
            }
        }
    }
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
概要介绍:本门课程属于“Java分布式中间件大汇聚实战”系列课程,主要介绍了企业级项目中真实的应用场景的实现及主流的Java核心技术栈(Redis、RabbitMQ、Spring AOP、Redisson、ZooKeeper…)的实战等等。除此之外,还介绍了如何基于Redis设计并实战一款点赞系统(点赞、取消点赞、排行榜、用户中心、文章点赞用户列表…)可以说技术干货甚多,不仅可以巩固企业级应用系统的开发实战能力,相信在面试、跳槽涨薪方面也能带来相应的帮助!课程内容:传说中的金三银四、面试跳槽涨薪季已经来临,Debug特地为大家准备了一系列跟面试、跳槽、巩固核心技术栈相关的课程,本门课程属于第一季,其中的内容包括企业级项目中真实的应用场景实战、面试相关的技术点分享、主流的Java技术栈(Undertow、Redis、RabbitMQ、Spring AOP、Redisson、ZooKeeper…)实战等等。除此之外,我们还基于Redis设计并实战了一款点赞系统,可以说技术干货甚多。在课程的最后,Debug给大家整理了一份最新的面向BAT大厂招聘 ~ 2020年程序猿最新的Java面试题(附带目录和答案),希望对各位小伙伴的成长有所帮助!值得一提的是,本季课程实战的应用场景包括“日志记录”、“邮件发送”、“通告消息通知”、“短信验证码失效验证”、“会员到期自动提醒/到期N自动提醒”以及“点赞系统”的设计与实战,其大纲如下所示:其中,涉及到的技术栈包括Spring Boot2.0、Mybatis、Undertow、Redis、RabbitMQ、RedissonSpring AOP、 Java8…下面罗列出本门课程重点介绍的价格应用案例以及业务场景的实现流程图!(1)基于Spring的消息驱动模型实现日志的异步记录:(2)基于消息中间件RabbitMQ的消息队列实现日志的异步记录:(3)基于缓存中间件Redis的订阅发布机制实现商户公告消息通知:(4)基于Redis的Key失效与定时任务实现实现短信验证码的过期失效验证:其他核心、典型的应用案例和业务场景的实战可以详细参考“课程目录”!除此之外,我们还基于缓存中间件Redis设计并实战实现了点赞系统中的点赞功能模块,下面罗列出其中涉及到的相关功能模块的实战流程图:其课程收益如下所示:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡汪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值