当前位置: 首页 > news >正文

php动态网站开发教程前言网站设计模板网站

php动态网站开发教程前言,网站设计模板网站,佛山住房和城乡建设部网站官网,珠海网站建设珠海前言 目前很多产品都用到过定时发布或者定时推送等功能,定时推送有两种定义,一种是后台自己有相关规则,通过定时器设置好相应的时间进行推送(例如定时任务框架QuartZ、xxl-job等实现,或者通过springboot自带定时任务Scheduled注解等实现)&am…

前言

目前很多产品都用到过定时发布或者定时推送等功能,定时推送有两种定义,一种是后台自己有相关规则,通过定时器设置好相应的时间进行推送(例如定时任务框架QuartZ、xxl-job等实现,或者通过springboot自带定时任务@Scheduled注解等实现),这些都是基于后台设定的规则来进行定时推送。
还有一种场景便是根据用户自己选择想要的时间进行推送,这时候再用到上面的方法来做会比较麻烦和复杂,就需要用到延时队列来实现

实现方式

在做这个功能之前,我在网上查阅了想要实现这种根据用户选择时间来推送的相关资料,发现方式还是挺多的,包括但不限于以下几种:

  1. 最简单暴力的方法,通过上述的定时任务框架或者springboot自带的定时器来实现,把cron表达式书写为每分钟一次,然后每分钟都去检查是否和用户设置的时间能匹配上,如能匹配上就进行相关的业务操作
  2. 通过实现springboot自带的SchedulingConfigurer接口来进行动态任务调用
  3. 通过DelayQueue队列进行实现
  4. 通过MQ中间件的发送消费来实现
  5. 通过Redis设置key过期时间触发来进行实现

    上面的几种实现第1、2点比较简单,也很有效果,但是容易出现效率问题和准确性的问题,下面45点的比较不错,但是相比较起来学习成本会高一些,具体实现的思路差不太多,这些都有相关的资料,通过上面的关键字搜索便能查阅到

功能实现

看了那么多的方案之后再结合自身的项目,最终决定用一种新的方案来实现,通过Redis自带的DelayedQueue延时队列来完成,和上面的第45点其实思路差不太多,只不过这个更简单方便一点

定义一个实体类来进行配置

@Data
public class TaskBodyDto implements Serializable {/*** 重试最大次数*/public static final int MAX_RETRY = 3;private String idKey;private String beanName;private String methodName;private Map<String, Object> paramMap;/*** 重试计时器*/private int cnt;/*** 延迟的时间*/private long delay;/*** 延迟的时间单位*/private TimeUnit timeUnit;}

定义RedissonDelayQueue类

@Slf4j
@Component
public class RedissonDelayQueueDemo {@Resourceprivate RedissonClient redissonClient;private RBlockingQueue rBlockingQueue;private RDelayedQueue rDelayedQueue;@PostConstructprivate void init() {rBlockingQueue = redissonClient.getBlockingQueue(TaskListener.class.getName());rDelayedQueue = redissonClient.getDelayedQueue(rBlockingQueue);}public void add(TaskBodyDto reqVo) {rDelayedQueue.offer(reqVo, reqVo.getDelay(), reqVo.getTimeUnit());log.info("增加了延时队列{}", reqVo);}/*** 增加订单延时队列 -单位为秒** @param id         id,传入一个唯一标识,可以是业务ID* @param beanName   类名* @param methodName 方法名* @param paramMap   参数* @param delay      延迟时间*/public void add(String id, String beanName, String methodName, Map<String, Object> paramMap, long delay) {TaskBodyDto reqVo= new TaskBodyDto();String idKey = beanName + ":" + methodName + ":" + id;log.info("增加了延时队列" + idKey);reqVo.setIdKey(idKey);reqVo.setBeanName(beanName);reqVo.setMethodName(methodName);reqVo.setParamMap(paramMap);reqVo.setTimeUnit(TimeUnit.SECONDS);reqVo.setDelay(delay);this.add(reqVo);}/*** 删除延时队列** @param id         id,传入一个唯一标识,可以是业务ID* @param beanName   类名* @param methodName 方法名*/public void remove(String id, String beanName, String methodName) {String idKey = beanName + ":" + methodName + ":" + id;log.info("删除了延时队列:" + idKey);RDelayedQueue<TaskBodyDto> delayedQueue = rDelayedQueue;Stream<TaskBodyDto> stream = delayedQueue.stream().filter(s -> idKey.equals(s.getIdKey()));List<TaskBodyDto> c = stream.collect(Collectors.toList());if (!c.isEmpty()) {
//            log.info("删除延时队列{}", c);delayedQueue.remove(c.get(0));}}}

然后写一个工具类方便调用

@Component
public class RedissionDelayQueueUtils {@Autowiredprivate RedissonDelayQueue redissonDelayQueue;@Autowiredprivate static RedissonDelayQueue staticRedissonDelayQueue;@PostConstructpublic void init() {staticRedissonDelayQueue = redissonDelayQueue;}/*** 添加定时任务* @param id 唯一标识,可以是业务ID* @param paramMap 参数 key-value* @param beanName bean类名称 注意类名需要小写* @param methodName 方法名* @param seconds 延迟时间 单位为秒*/public static void addDelayQueue(String id, Map<String, Object> paramMap, String beanName, String methodName, Integer seconds) {staticRedissonDelayQueue.add(id, beanName, methodName, paramMap, seconds);}/*** 删除定时任务* @param id 唯一标识,可以是业务ID* @param beanName bean类名称 注意类名需要小写* @param methodName 方法名*/public static void removeDelayQueue(String id, String beanName, String methodName) {staticRedissonDelayQueue.remove(id, beanName, methodName);}}

然后再配置好监听器,在监听器里面通过反射获取到相关的方法然后执行里面的业务

@Slf4j
@Component
public class TaskListener implements RedisDelayedQueueListener<TaskBodyDto> {private static final List<Class> WRAP_CLASS = Arrays.asList(Integer.class, Boolean.class, Double.class, Byte.class, Short.class, Long.class, Float.class, Double.class, BigDecimal.class, String.class);//队列Queue@Autowiredprivate RedissonDelayQueue redissonDelayQueue;@Autowiredprivate TaskSender taskSender;@Overridepublic void invoke(TaskBodyDto reqVo) {log.info("开始执行监听...{}", reqVo);reqVo.setCnt(reqVo.getCnt() + 1);try {Object bean = ApplicationContextUtil.getBean(reqVo.getBeanName());Method method = ReflectUtil.getMethodByName(bean.getClass(), reqVo.getMethodName());Class target = AopUtils.getTargetClass(bean);Method targetMethod = ReflectUtil.getMethodByName(target, reqVo.getMethodName());List<Object> objects = getMethodParamList(targetMethod, reqVo.getParamMap());method.invoke(bean, objects.toArray());} catch (Exception e) {log.error("invoke task err!", e);if (reqVo.getCnt() > TaskBodyDto.MAX_RETRY) {log.error("重试次数超过最大次数,不再重试。");DeadQueDto deadQueDto = new DeadQueDto();deadQueDto.setBeanName(reqVo.getBeanName());deadQueDto.setMethodName(reqVo.getMethodName());deadQueDto.setParamMap(reqVo.getParamMap());taskSender.sendTask(deadQueDto);} else {//重试,30分钟后重试,秒为单位则用原数据if (reqVo.getTimeUnit().name().equals(TimeUnit.DAYS.name()) || reqVo.getTimeUnit().name().equals(TimeUnit.HOURS.name())) {reqVo.setDelay(30);reqVo.setTimeUnit(TimeUnit.MINUTES);redissonDelayQueue.add(reqVo);} else if (reqVo.getTimeUnit().name().equals(TimeUnit.MINUTES.name()) && reqVo.getDelay() > 30) {reqVo.setDelay(30);reqVo.setTimeUnit(TimeUnit.MINUTES);redissonDelayQueue.add(reqVo);} else {redissonDelayQueue.add(reqVo);}}}}private List<Object> getMethodParamList(Method method, Map<String, Object> paramMap) throws Exception {List<Object> objectList = new ArrayList<>();// 利用Spring提供的类获取方法形参名DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();String[] param = nameDiscoverer.getParameterNames(method);for (int i = 0; i < method.getParameterTypes().length; i++) {Class<?> parameterType = method.getParameterTypes()[i];Object object = null;// 基本类型不支持,支持包装类String paramKey = param[i];if (WRAP_CLASS.contains(parameterType)) {if (param != null && paramMap.containsKey(paramKey)) {object = paramMap.get(paramKey);object = ConvertUtils.convert(object, parameterType);}} else if (!parameterType.isPrimitive()) {if (parameterType.isAssignableFrom(List.class) || parameterType.isAssignableFrom(Map.class) || parameterType.isAssignableFrom(Set.class)) {object = paramMap.get(paramKey);} else {object = parameterType.newInstance();BeanUtils.populate(object, paramMap);}}objectList.add(object);}return objectList;}}

都配置好之后,可以写个方法进行测试
比如我要三十分钟之后执行test方法

public class Test {private void test(String name,String value){//执行业务代码}}

然后在需要执行这个功能的地方进行调用,比如用户在界面选择了发布时间之后,后端接口收到请求进行处理

						//延时队列Map<String, Object> map = new HashMap<>();map.put("name","张三");map.put("value","这是value");RedissionDelayQueueUtils.addDelayQueue("唯一标识",map,"test","test", (int) DateUtil.between("用户选择的时间",new Date(), DateUnit.SECOND));

注意事项:这上面的map便是被执行的方法需要的一些参数,切记不能直接传入Object类,只能通过基本数据类型进行传递,传入的bean类名也需要小写,DateUtil.between()这个方法是用的hutool工具类里面的日期工具类,为了算出用户选择的时间和当前时间相差多少秒,可自行更改为适合自己的方法,反正最后只需要取到两者时间差多少秒即可

后续redis的配置那些照常配置即可

总结

总结下来其实思路还是比较明确,就是通过redis延时队列的机制,这边配置好相关的参数然后加入到redis里面去,配置好监听器之后由redis进行监听触发,然后再通过反射的方式取到需要执行的bean和方法进行执行即可,其实延时队列的方法很多,我上面还推荐了一些其他的方法,通过给出的关键字即可查阅相关的资料,总之根据自身的情况选择最适合的方法就行

最后不管采取哪种方式,建议在触发以及执行的地方及时把日志打印出来,方便后期调试以及对问题的定位

http://www.yidumall.com/news/42616.html

相关文章:

  • 北外新闻行业门户网站建设宁波网站推广
  • 做网站如何写需求推广任务接单平台
  • 做王境泽gif的网站网络营销具有什么特点
  • 不用框架做网站北京seo运营
  • 成都网站建设怎么样搜索引擎营销的步骤
  • 网站内容改版河南网站推广电话
  • 上海网站seo策划网站外链工具
  • 上海公司注册代理记账北京度seo排名
  • 长寿网站建设一级域名二级域名三级域名的区别
  • wordpress如何静态化平板电视seo优化关键词
  • 遵义市政府网站建设概况百度一下百度知道
  • 直播做网站seo做什么网站赚钱
  • 品牌网站的愿望清单怎么做网络营销项目策划方案
  • phpstudy搭建网站教程dw网站制作
  • 深圳建设集团地址在哪里天津seo优化排名
  • 杭州公司做网站网站推广优化设计方案
  • 装修网站怎么做的衡阳seo服务
  • 动态网站建设公司长尾词排名优化软件
  • 县城做信息网站seo外包公司多少钱
  • 国外做设计赚钱的网站一键制作单页网站
  • 网站发帖做业务重庆森林讲了什么故事
  • 员工管理网站模板百度识图搜索网页版
  • 网站 栏目做下拉线上推广方式有哪些
  • 做网站要学哪些班级优化大师免费下载学生版
  • 潍坊知名网站建设价格低seo网站优化服务
  • 什么是营销网站建设游戏app拉新平台
  • 做网站必须网站备案广州网络seo公司
  • 北京网站定制流程泰州seo网络公司
  • 百度网页打不开无法访问怎么办游戏优化是什么意思?
  • 揭阳网站建设价格网站推广和seo