咖啡汪推荐————基于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);
            }
        }
    }
}

猜你喜欢

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