=====================本文章的案列实现为定时发布文章====================
定时扫描:
开启自动定时扫描:SpringbootApplication上添加注解@EnableScheduling,
定义的扫描方法上添加@Scheduled(fixedRate=1000) 设置定时扫描的时间,时间单位为毫秒
实现思路:
解读:文章审核通过后先判断发布的时间,小于或等于当前时间则立即发布,大于当前时间则定时发布,。
//判断用户选择的发布时间大于当前时间,进行定时发布
if(wmNews.getPublishTime() != null && wmNews.getPublishTime().after(new Date())){
//修改文章状态为8
wmNews.setStatus(WmNews.Status.SUCCESS.getCode());
wmNews.setReason("文章审核通过,进入定时发布队列");
wmNewsMapper.updateById(wmNews);
//=========把当前自媒体文章发布任务添加到延迟队列中==========
Long taskId = wmNewsTaskService.addWmNewsTask(wmNews);
return; //必须退出
}
//如果发布时间小于或等于当前时间,进行立即发布文章(调用Feign接口保存App文章,更新自媒体表状态等信息)
//调用feign接口保存文章
publishApArticle(wmNews);
解读:定义添加任务的方法,Task对象用于存储每个任务的数据,可以在微服务之间传输任务数据时使用
package com.heima.wemedia.service.impl;
import com.heima.common.constants.ScheduleConstants;
import com.heima.model.schedule.dtos.Task;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.schedule.feign.TaskFeign;
import com.heima.utils.common.JsonUtils;
import com.heima.wemedia.service.WmNewsTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class WmNewsTaskServiceImpl implements WmNewsTaskService {
@Autowired
private TaskFeign taskFeign;
/**
* 添加自媒体定时发布延迟任务
*
* @param wmNews
*/
@Override
public Long addWmNewsTask(WmNews wmNews) {
Task task = new Task();
task.setTaskTopic(ScheduleConstants.TASK_TOPIC_NEWS_PUBLISH);
task.setExecuteTime(wmNews.getPublishTime().getTime()); //文章发布的时间,可能是客户自己选择的
//为了统一格式JSON,重新创建一个对象,只传一个id过去
WmNews news = new WmNews();
news.setId(wmNews.getId());
task.setParameters(JsonUtils.toString(news));
Long taskId = taskFeign.addTask(task);
return taskId;
}
解读:
1.将文章任务以及任务日志表添加到数据库
2.将未来5分钟内要发布的文章,添加到redis中
/**
* 添加任务
*
* @param task
*/
@Override
@Transactional
public long addTask(Task task) {
//把任务添加到DB
addTaskToDb(task);
//把任务添加Redis
addTaskToCache(task);
return task.getTaskId();
}
/**
* 把任务添加到DB
* @param task
*/
private void addTaskToDb(Task task) {
log.info("开始将任务添加到DB.................");
//添加任务列表
Taskinfo taskinfo = BeanHelper.copyProperties(task, Taskinfo.class);
taskinfo.setExecuteTime(new Date(task.getExecuteTime())); //因为两个实体类的时间类型不用,一个Long一个date,需要自己重新设置
taskinfoMapper.insert(taskinfo);
//把新产生的任务ID赋值给Task对象
task.setTaskId(taskinfo.getTaskId());
//添加任务日志表
TaskinfoLogs taskinfoLogs = BeanHelper.copyProperties(taskinfo, TaskinfoLogs.class);
taskinfoLogs.setVersion(1); //设置初始值,后续依靠MybatisPlus乐观锁拦截器实现更新
taskinfoLogs.setStatus(ScheduleConstants.SCHEDULED);
taskinfoLogsMapper.insert(taskinfoLogs);
log.info("成功将任务添加到DB.................");
}
/**
* 把任务添加Redis
* @param task
*/
private void addTaskToCache(Task task) {
//判断任务的执行时间是否在未来5分钟以内
//获取当前时间的未来5分钟的时间
long futureTime = DateTime.now().plusMinutes(5).getMillis();
if(task.getExecuteTime() <= futureTime) {
log.info("开始将任务添加到redis.................");
String key = RedisContants.TASK_TOPIC_PREFIX + task.getTaskTopic();
redisTemplate.opsForZSet().add(key, JsonUtils.toString(task),task.getExecuteTime());
log.info("成功将任务添加到redis.................");
}
}
解读:这个时候就会有数据一致的问题,需要数据库和redis的数据进行同步,定时去扫描数据库是否有符合条件的文章,导入到redis中
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
/**
* 每间隔10秒就会去扫描数据库,同步数据到redis
*/
@Component
@Slf4j
public class SyncDbToCacheJob {
@Autowired
private TaskinfoMapper taskinfoMapper;
@Autowired
private StringRedisTemplate redisTemplate;
@Scheduled(fixedRate = 10000) //固定速率(毫秒)
public void syncData(){
log.info("同步MySQL任务数据到Redis缓存");
//查询符合条件的DB数据(只导入未来5分钟执行的任务)
QueryWrapper queryWrapper = new QueryWrapper<>();
Date futureTime = DateTime.now().plusMinutes(5).toDate();
queryWrapper.le("execute_time", futureTime);
List taskinfos = taskinfoMapper.selectList(queryWrapper);
//导入Redis缓存
if(CollectionUtils.isNotEmpty(taskinfos)){
//log.info("开始将DB的数据导入到redis中................");
for (Taskinfo taskinfo : taskinfos) {
Task task = BeanHelper.copyProperties(taskinfo, Task.class);
task.setExecuteTime(taskinfo.getExecuteTime().getTime()); //时间的类型不同,需要重新设置,date类型转为long
String key = RedisContants.TASK_TOPIC_PREFIX + task.getTaskTopic();
redisTemplate.opsForZSet().add(key, JsonUtils.toString(task),task.getExecuteTime());
}
//log.info("同步完成.................");
}
}
}
解读:设置每隔1秒就扫描消费定时任务一次,看redis是否有到时间发布的文章,有的话就调用发布文章的方法去发布文章。
/**
* 定时扫描到期的文章,消费任务进行发布
*/
@Component
@Slf4j
public class WmNewsTaskJob {
@Autowired
private TaskFeign taskFeign;
@Autowired
private WmNewsService wmNewsService;
@Autowired
private WmNewsAutoScanService wmNewsAutoScanService;
@Scheduled(fixedRate = 1000) //每隔1秒扫描一次到期发布的文章
public void pollWmNewsTask(){
//查询到期的文章
List taskList = taskFeign.pollTask(ScheduleConstants.TASK_TOPIC_NEWS_PUBLISH);
//发布到期的文章
if(CollectionUtils.isNotEmpty(taskList)){
for (Task task : taskList) {
//获取参数
String json = task.getParameters();
//转为Java对象
WmNews news = JsonUtils.toBean(json, WmNews.class);
//通过Id查文章
WmNews wmNews = wmNewsService.getById(news.getId());
//发布文章
wmNewsAutoScanService.publishApArticle(wmNews);
log.info("文章已经成功发布到App端,ID:{}",wmNews.getId());
}
}
}
}
/**
* 从延迟队列消费任务
* 重点:从延迟队列取出符合条件(根据score查询,score小于或等于当前时间毫秒值)
*
* @param taskTopic
*/
@Override
@Transactional
public List pollTask(Integer taskTopic) {
String key = RedisContants.TASK_TOPIC_PREFIX + taskTopic;
//查询redis中符合执行条件的任务
Set taskSet = redisTemplate.opsForZSet().rangeByScore(key, 0, System.currentTimeMillis());
ArrayList taskList = new ArrayList<>();
if(CollectionUtils.isNotEmpty(taskSet)) {
log.info("redis中有符合执行条件的任务...............");
for (String taskJson : taskSet) {
Task task = JsonUtils.toBean(taskJson, Task.class);
log.info("开始更新DB数据和删除redis中的数据..............");
//更新DB数据
updateTaskToDb(task);
//删除redis数据
redisTemplate.opsForZSet().remove(key,taskJson);
taskList.add(task);
}
}
return taskList;
}
/**
* 调用feign接口从自媒体文章发布到App端文章
* @param wmNews
*/
public void publishApArticle(WmNews wmNews) {
ApArticleDto dto = BeanHelper.copyProperties(wmNews, ApArticleDto.class);
//重要代码:========重新绑定ap_article表的id=====
dto.setId(wmNews.getArticleId()); //刚开始为空的
//设置文章作者信息
WmUser wmUser = wmUserMapper.selectById(wmNews.getArticleId());
if(wmUser != null) {
dto.setAuthorId(wmUser.getId());
dto.setAuthorName(wmUser.getNickname());
}
//设置文章频道信息
WmChannel channel = wmChannelMapper.selectById(wmNews.getChannelId());
if(channel != null) {
dto.setChannelId(channel.getId());
dto.setChannelName(channel.getName());
}
//设置封面类型
dto.setLayout((int)wmNews.getType());
//设置文章类型
dto.setFlag(0);
//设置文章参数
dto.setLikes(0);
dto.setViews(0);
dto.setComment(0);
dto.setCollection(0);
ResponseResult responseResult = apArticleFeign.save(dto);
if(responseResult.getCode().equals(200)){
//获取app的文章ID
Long articleId = responseResult.getData();
//设置自媒体文章的article_id
wmNews.setArticleId(articleId);
//修改文章状态为9(已发布)
wmNews.setStatus(WmNews.Status.PUBLISHED.getCode());
wmNews.setReason("文章已发布成功");
wmNewsMapper.updateById(wmNews);
}
}
有缺点:没取消发布的逻辑