专栏首页格子的个人博客Java定时发布文章简单方案

Java定时发布文章简单方案

没有需求,就没有折腾。

首先,阐述一下背景。

早上迷迷糊糊地开始了春节后的一天上班日程,脑袋还在噼里啪啦放烟花,项目管理部和SEO小伙伴就提了一桶凉水过来,往我头上一浇,瞬间烟花都湮灭了。

“给官网加个定时发布文章的功能吧。”

“啥?”

“我们的官网,每次新增文章都是立即执行静态化并进行发布,现在周末也需要发布文章,SEO周末是不上班哒,所以,请给我们开发一个定时发布文章的功能吧。”

“啊?”

“评估一下时间,越快越好。”

“...”

“需求了解了吧,那就这样,尽快产出哦。”

“...”

脑袋还在宕机中。

喂喂喂,那你们浇灭了我的烟花,都不用赔一下吗,真不厚道。

虽然我不喜欢频繁需求变动,但是我爱折腾。

不过这么简单的功能,貌似也算不上折腾,但是记录下来也许能帮助到别人呢,Hard to say。

环境说明

1、centOS 服务器一台

2、基于SSM + 一些没必要在这里提到的第三方控件

3、Bootstrap前端框架

4、最最重要的是:帅比码农一枚

其实,上面前三点都没必要提及,主要是基于Java环境来实现定时任务。所以最重要的,请记住第四点,强调,是第四点。

思路

SEO通过管理后台新增文章,但是并不是立即发布,而是可以手动选择发布方式,包括立即发布定时发布,定时发布可以指定一个时间,交由系统自动实现发布功能。

说了跟没说似的,原谅我,帅比码农说话都比较高(zhuang)深(shen)莫(nong)测(gui)。

1、前端通过bootstrap-datepicker插件,在文章表单中新增一个发布时间的选择控件。具体使用方式请参考官网API或留言。

<!-- 页面元素 -->
<div class="input-append date form_datetime">
    <input id="pubTime" name="pubTime" size="16" type="text" value="" readonly>
    <span class="add-on"><i class="icon-th"></i></span>
</div>

<!-- Javascript -->
<script type="text/javascript">
    $(".form_datetime").datetimepicker({
            language:"zh-CN",
            showMeridian: true,
            todayBtn:true,
            startDate:new Date(),
            format: "yyyy-mm-dd hh:ii:ss"
        }).on('changeDate', function(ev){
            $('#pubTiming').attr('checked',true);//通过事件,实现[定时发布]单选按钮的联动选择
        });
</script> 

界面

2、后台新增文章的方法,新增入参[发布方式-pubType]和[发布时间-pubTime]来接收表单传递过来的值,当用户选择发布方式为定时发布时,要求发布时间必须选择。

由于这里是以实体的方式来接收表单的,只需要在Article实体中新增pubType和pubTime两个属性,并生成getter和setter即可接收表单值。

部分代码如下

    /**
     * 新增文章
     *
     * @param article 文章实体
     * @param request 请求
     * @return ResponseBean 响应实体
     */
    @RequestMapping("/add")
    @ResponseBody
    public ResponseBean add(Article article, HttpServletRequest request) {
        boolean success = articleService.add(article, request);
        ...
    }
    
    /**
     * 文章
     *
     * @author zoro
     * @version 1.0
     * @since 2018/02/23
     */
    public class Article implements Serializable {
        ...
        private Integer pubType;//发布方式,1立即,2定时
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date pubTime;//定时发布的时间
        
        public Integer getPubType() {
            return pubType;
        }
    
        public void setPubType(Integer pubType) {
            this.pubType = pubType;
        }
    
        public Date getPubTime() {
            return pubTime;
        }
    
        public void setPubTime(Date pubTime) {
            this.pubTime = pubTime;
        }
        ...
    
    }

3、将文章内容和发布状态保存到数据库,如果是立即发布,则执行文章渲染,通过模板渲染成html文件,以供访问。

articleRender.rendering();

4、如果是定时发布的话,就需要建立定时任务。 这里有几种情况需要说明:

  • 新增文章 直接保存文章,并建立定时任务。
  • 修改文章 修改文章会存在不同时间点重复发布任务的可能性,所以需要特殊处理。

针对修改文章,每次新建定时任务的时候,先判断是否存在同一篇文章的定时任务,如果有,则标识该任务为取消状态(取消状态下的任务,任务体不会执行任何操作),并从id映射和缓存中移除

文章任务部分代码如下

package com.andatech.admin.service;

...
import com.andatech.tools.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 文章发布任务
 *
 * @author Zoro
 * @date 2018/2/23
 * @since 1.0
 */
public class ArticlePublishJob implements Runnable {

    /**logger*/
    private static final Logger LOGGER = LoggerFactory.getLogger(ArticlePublishJob.class);

    /**id映射*/
    private static volatile ConcurrentHashMap<String, String> idMapping = new ConcurrentHashMap<>();
    /**任务缓存*/
    private static volatile ConcurrentHashMap<String, ArticlePublishJob> cache = new ConcurrentHashMap<>();
    
    private volatile AtomicBoolean canceled = new AtomicBoolean(false);//任务取消状态
    private String jobId;//任务id
    private String articleUuid;//文章id
    private ArticleRender articleRender;//文章渲染器

    
    public ArticlePublishJob(ArticleRender articleRender) {
        this.jobId = IdGenerator.plainJdkUUID();
        this.articleRender = articleRender;
        this.articleUuid = articleRender.getArticle().getUuid();
        //取消并清除上一次任务
        cancelAndClearLastJobIfExist();
        //缓存本次任务
        cacheThisJob();
    }

    /**
     * 取消并清除上一次任务
     */
    private void cancelAndClearLastJobIfExist(){
        if (StringUtil.isNotEmpty(idMapping.get(articleUuid))) {
            ArticlePublishJob lastJob = cache.get(idMapping.get(articleUuid));
            if (null != lastJob) {
                lastJob.cancelJob();
                cache.remove(idMapping.get(articleUuid));
                idMapping.remove(articleUuid);
            }
        }
    }

    /**
     * 缓存本次任务
     */
    private void cacheThisJob(){
        //id映射
        idMapping.put(this.articleUuid, this.jobId);
        //文章发布任务缓存
        cache.put(this.jobId, this);
    }

    @Override
    public void run() {
        //判断任务是否被取消
        if (!canceled.get()) {
            ArticleService articleService = (ArticleService) SpringContextHolder.getBean("articleService");
            //渲染
            try {
                articleRender.rendering();
            } catch (IOException e) {
                LOGGER.error("render log err:" + e.getMessage(), e);
            }
            //更新文章状态
            Article updArticle = new Article();
            updArticle.setUuid(articleRender.getArticle().getUuid());
            updArticle.setStatus(Article.STATUS_NORMAL);
            articleService.edit(updArticle);

            //从缓存中清理本任务
            clear();
        }
    }

    /**
     * 取消任务
     */
    public void cancelJob() {
        this.canceled.set(true);
    }

    /**
     * 清理缓存
     */
    public void clear() {
        idMapping.remove(articleUuid);
        cache.remove(this.jobId);
    }

}

5、具体定时任务方式,包括以下几种

  • Thread方式:线程等待,不安全。
  • timer方式:线程资源没有复用。
  • 任务调度框架,比如Quartz等:需要继承框架。
  • ScheduledExecutorService方式:被相中了。

综上分析,选择了最后一种,也是较好的选择之一,下面给出最简单的用法,如有深入需要,建议查看JavaAPI。

部分代码如下

/**创建线程池*/
public static ScheduledExecutorService service = Executors.newScheduledThreadPool(50);


/**新建任务,并设定执行时间*/
ArticlePublishJob job = new ArticlePublishJob(articleRender);
long delay = article.getPubTime().getTime() - System.currentTimeMillis();
service.schedule(job, delay, TimeUnit.MILLISECONDS);

测试,大功告成。

总结 不结合业务来说,定时任务的创建无非就"第5点"中的几种方式,熟悉API并熟练使用即可。 结合业务情况下,需要考虑任务是否会重复,重复了怎么处理等问题。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【Java】大文本字符串滤重的简单方案

    一枝花算不算浪漫
  • 定时任务最简单的3种实现方法(Java)

    定时任务在实际的开发中特别常见,比如电商平台 30 分钟后自动取消未支付的订单,以及凌晨的数据汇总和备份等,都需要借助定时任务来实现,那么我们本文就来看一下定时...

    AlbertZhang
  • wordpress实现发布文章主动推送(实时)给百度的方法

    很多人为了节约时间,都选择了自动提交的方式。然而你们知道的,自动提交又可以分为主动推送(实时)、自动推送和sitemap三种链接提交通道。

    可定
  • 超硬核Java学习路线图+学习资源+实战项目汇总,看完以后不用再问我怎么学Java了!

    于是这次黄小斜决定来一波狠的,把所有这些内容都整理起来,做成一份非常硬核的Java学习指南+路线图,一篇文章搞定Java学习,360度无死角(可能)如果你们喜欢...

    黄小斜学Java
  • ​ 之前写过很多次关于Java学习指南、Java技术路线图的文章。

    之前写过很多次关于Java学习指南、Java技术路线图的文章。但是总还是有小伙伴来问我,Java怎么学,项目怎么做,资源怎么找,真是让人头秃。

    黄小斜学Java
  • 服务端性能优化之双重检查锁

    早前的文章中讨论过服务端性能优化之异步查询转同步,在本文中,将讨论双重检查锁定设计模式。通过简单地事先检查锁定条件,该模式减少了锁定获取的次数,通常可以提高性能...

    FunTester
  • Java程序员必看的 13 本 Java 书籍!

    毕竟,书是学习的海洋呢!So,Java程序员你们准备好了吗?双手奉上Java程序员必读之热门书单。

    Java技术栈
  • 进BAT这些互联网大厂,面试之前刷几百道面试题真的有用吗?

    面试之前先刷几篇面经,或者做几道热门面试题,想必是大家很熟悉的一种复习方式了,就像我们当年经常做五年高考三年模拟一样。但是可不要把面试题和面经当成你的主要复习...

    美的让人心动
  • 你不可错过的Java学习资源清单

    学习Java和其他技术的资源其实非常多,但是我们需要取其精华去其糟粕,选择那些最好的,最适合我们的,同时也要由浅入深,先易后难。基于这样的一个标准,我在这里为大...

    Java技术江湖

扫码关注云+社区

领取腾讯云代金券