JavaWeb_LeadNews_Day10-Xxljob, Redis实现定时热文章

news2024/12/23 17:00:34

JavaWeb_LeadNews_Day10-Xxljob, Redis实现定时热文章

  • xxl-job概述
    • windows部署调度中心
    • docker部署调度中心
  • xxl-job入门案例
  • xxl-job分片广播
  • 热点文章定时计算
    • 思路分析
    • 具体实现
      • 热文章计算
      • 定时计算
  • 查询文章接口改造
  • 来源
  • Gitee

xxl-job概述

windows部署调度中心

  1. 运行 xxl-job\doc\db\tables_xxl_job.sql
  2. 修改 xxl-job-admin子模块下的application.properties
    spring.datasource.password=1234
    
  3. 启动 xxl-job-admin子模块下的启动程序
  4. 访问localhost:8080/xxl-job-admin, 账号: admin, 密码:123456

docker部署调度中心

  1. 创建mysql容器
docker run -p 3306:3306 --name mysql57 \
-v /opt/mysql/conf:/etc/mysql \
-v /opt/mysql/logs:/var/log/mysql \
-v /opt/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7.25
  1. 拉取镜像
docker pull xuxueli/xxl-job-admin:2.3.0
  1. 创建xxl-job-admin容器
docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://192.168.174.133:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \
--spring.datasource.username=root \
--spring.datasource.password=root" \
-p 8888:8080 -v /tmp:/data/applogs \
--name xxl-job-admin -d xuxueli/xxl-job-admin:2.3.0

xxl-job入门案例

  1. 调度中心新建示例任务
  2. 依赖
    <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>2.3.0</version>
    </dependency>
    
  3. 配置
    application.yml
    server:
      port: 8881
    
    xxl:
      job:
        admin:
          addresses: http://192.168.174.133:8888/xxl-job-admin
        executor:
          appname: xxl-job-executor-sample
          port: 9999
    
    XxlJobConfig.java
    @Configuration
    public class XxlJobConfig {
        private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
    
        @Value("${xxl.job.admin.addresses}")
        private String adminAddresses;
    
        @Value("${xxl.job.executor.appname}")
        private String appname;
    
        @Value("${xxl.job.executor.port}")
        private int port;
    
        @Bean
        public XxlJobSpringExecutor xxlJobExecutor() {
            logger.info(">>>>>>>>>>> xxl-job config init.");
            XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
            xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
            xxlJobSpringExecutor.setAppname(appname);
            xxlJobSpringExecutor.setPort(port);
    
            return xxlJobSpringExecutor;
        }
    
        /**
         * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
         *
         *      1、引入依赖:
         *          <dependency>
         *             <groupId>org.springframework.cloud</groupId>
         *             <artifactId>spring-cloud-commons</artifactId>
         *             <version>${version}</version>
         *         </dependency>
         *
         *      2、配置文件,或者容器启动变量
         *          spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
         *
         *      3、获取IP
         *          String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
         */
    }
    
  4. 任务代码
    @Component
    public class HelloJob {
    
        @XxlJob("demoJobHandler")
        public void demoJobHandler()
        {
            XxlJobHelper.log("XXL-JOB, Hello World.");
            System.out.println("简单任务执行...");
        }
    
    }
    

xxl-job分片广播

  1. 创建分片执行器 xxl-job-sharding-sample
  2. 创建任务, 路由策略为分片广播
  3. 分片广播任务代码, 创建多个实例
    @XxlJob("shardingJobHandler")
    public void shardingJobHandler()
    {
        // 分片的参数
        int shardIndex = XxlJobHelper.getShardIndex(); // 实例在集群中的序号
        int shardTotal = XxlJobHelper.getShardTotal(); // 集群总量
    
        // 业务逻辑
        List<Integer> list = getList();
        for (Integer integer : list) {
            if(integer % shardTotal == shardIndex){
                System.out.println("当前分片: "+shardIndex+", 当前任务项: "+integer);
            }
        }
    }
    
    public List<Integer> getList()
    {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
        return list;
    }
    

热点文章定时计算

思路分析

具体实现

热文章计算

@Slf4j
@Service
@Transactional
public class HotArticleServiceImpl implements HotArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;

    /**
     * 计算热门文章
     */
    @Override
    public void computeHotArticle() {
        // 1. 查询前5天的文章数据
        Date dayParam = DateTime.now().minusDays(5).toDate();
        List<ApArticle> articleList = apArticleMapper.findArticleListByLast5days(dayParam);

        // 2. 计算文章的分值
        List<HotArticleVo> hotArticleVoList = getHotArticleVoList(articleList);

        // 3. 为每个频道缓存30条分值较高的文章
        cacheTagToRedis(hotArticleVoList);
    }

    @Autowired
    private IWemediaClient wemediaClient;

    @Autowired
    private CacheService cacheService;


    /**
     * 为每个频道缓存30条分值较高的文章
     * @param hotArticleVoList
     */
    private void cacheTagToRedis(List<HotArticleVo> hotArticleVoList) {
        // 为每个频道缓存30条分值较高的文章
        ResponseResult responseResult = wemediaClient.getChannels();
        if(responseResult.getCode().equals(200)){
            String jsonString = JSON.toJSONString(responseResult.getData());
            List<WmChannel> wmChannelList = JSON.parseArray(jsonString, WmChannel.class);
            // 检索出每个频道的文章
            if(wmChannelList!=null){
                for (WmChannel wmChannel : wmChannelList) {
                    List<HotArticleVo> hotArticleVos = hotArticleVoList.stream().filter(item ->
                        item.getChannelId().equals(wmChannel.getId())
                    ).collect(Collectors.toList());

                    // 给文章排序, 取30条分值较高的文章存入redis key: 频道id value: 30条分值较高的文章
                    sortAndCache(hotArticleVos, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + wmChannel.getId());
                }
            }
        }

        // 设置推荐数据
        // 给文章排序, 取30条分值较高的文章存入redis key: 频道id value: 30条分值较高的文章
        sortAndCache(hotArticleVoList, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 排序并且缓存数据
     * @param hotArticleVos
     * @param key
     */
    private void sortAndCache(List<HotArticleVo> hotArticleVos, String key) {
        hotArticleVos = hotArticleVos.stream().sorted(
                Comparator.comparing(HotArticleVo::getScore).reversed()
        ).collect(Collectors.toList());
        if(hotArticleVos.size() > 30){
            hotArticleVos = hotArticleVos.subList(0, 30);
        }
        cacheService.set(key, JSON.toJSONString(hotArticleVos));
    }


    /**
     * 获取热文章列表
     * @param articleList
     * @return
     */
    private List<HotArticleVo> getHotArticleVoList(List<ApArticle> articleList) {
        List<HotArticleVo> articleVoList = new ArrayList<>();
        if(articleList!=null) {
            for (ApArticle apArticle : articleList) {
                HotArticleVo hotArticleVo = new HotArticleVo();
                BeanUtils.copyProperties(apArticle, hotArticleVo);
                Integer score = computeArticleScore(apArticle);
                hotArticleVo.setScore(score);
                articleVoList.add(hotArticleVo);
            }
        }
        return articleVoList;
    }

    /**
     * 计算文章分数
     * @param apArticle
     * @return
     */
    private Integer computeArticleScore(ApArticle apArticle) {
        Integer score = 0;
        if(apArticle.getLikes() != null){
            score += ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT*apArticle.getLikes();
        }
        if(apArticle.getComment() != null){
            score += ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT*apArticle.getComment();
        }
        if(apArticle.getCollection() != null){
            score += ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT*apArticle.getCollection();
        }
        if(apArticle.getViews() != null){
            score += apArticle.getViews();
        }
        return score;
    }
}

// ApArticleMapper.java
List<ApArticle> findArticleListByLast5days(@Param("dayParam") Date dayParam);

// ApArticleMapper.xml
<select id="findArticleListByLast5days" resultType="com.heima.model.article.pojos.ApArticle">
    SELECT
    aa.*
    FROM
    `ap_article` aa
    LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
    <where>
        and aac.is_delete != 1
        and aac.is_down != 1
        <if test="dayParam != null">
            and aa.publish_time <![CDATA[>=]]> #{dayParam}
        </if>
    </where>
</select>

总结:

  1. ApArticleMapper.java中的形参必须添加@Param注解, 否则在ApArticleMapper.xml中会将dayParam解释为Date的属性然后报错.

定时计算

  1. 新建热文章计算执行器leadnews-hot-article-executor
  2. 新建定时任务
  3. 依赖和配置
  4. 任务代码
    @Component
    @Slf4j
    public class ComputeHotArticleJob {
    
        @Autowired
        private HotArticleService hotArticleService;
    
        @XxlJob("computeHotArticleJob")
        public void handle()
        {
            log.info("热文章分值计算调度任务开始执行...");
            hotArticleService.computeHotArticle();
            log.info("热文章分值计算调度任务执行结束...");
        }
    
    }
    

查询文章接口改造

// article-Controller
public class ArticleHomeController {

    ...

    public ResponseResult load(@RequestBody ArticleHomeDto articleHomeDto)
    {
        // return apArticleService.load(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_MORE);
        return apArticleService.load2(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_MORE, true);
    }

    ...
}

// article-Service
public ResponseResult load2(ArticleHomeDto dto, Short type, boolean firstPage) {
    if(firstPage==true){
        String jsonString = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
        if(StringUtils.isNotBlank(jsonString)){
            List<HotArticleVo> hotArticleVoList = JSON.parseArray(jsonString, HotArticleVo.class);
            return ResponseResult.okResult(hotArticleVoList);
        }
    }
    return load(dto, type);
}

来源

黑马程序员. 黑马头条

Gitee

https://gitee.com/yu-ba-ba-ba/leadnews

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/964516.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】队列---C语言版(详解!!!)

文章目录 &#x1f438;一、队列的概念及结构&#x1f344;1、队列的概念定义&#x1f344;2、动图演示 &#x1f438;二、队列的实现&#x1f438;三、链表结构队列详解&#x1f34e;创建队列的结构⭕接口1&#xff1a;定义结构体&#xff08;QNode、Queue&#xff09;⭕接口2…

LeetCode 23 合并 K 个升序链表

LeetCode 23 合并 K 个升序链表 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/merge-k-sorted-lists/description/ 博主Github&#xff1a;https://github.com/GDUT-Rp/LeetCode 题目&#xff1a; 给你一个链表数组…

中心差分法-学习笔记《结构动力学-陈政清》

激励分段解析法仅仅对外载荷进行了离散&#xff0c;但对运动方程还是严格满足的&#xff0c;体系的运动在时间轴上依然是满足运动微分方程。然而&#xff0c;一般的时域逐步积分法进一步放松要求&#xff0c;不仅仅对外荷载进行离散化处理&#xff0c;也对体系的运动进行离散化…

前端Vue仿企查查天眼查高管信息列表组件

随着技术的不断发展&#xff0c;传统的开发方式使得系统的复杂度越来越高。在传统开发过程中&#xff0c;一个小小的改动或者一个小功能的增加可能会导致整体逻辑的修改&#xff0c;造成牵一发而动全身的情况。为了解决这个问题&#xff0c;我们采用了组件化的开发模式。通过组…

PCL 判断四点共面(三维空间)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 这里仍然沿用之前的方式来判断三维空间中四个顶点的共面性,三维空间中四个顶点可以构成三条线段(共用同一个顶点),这三条线段所代表的矢量可以组成一个立方空间,如下图所示: 这个立方体的体积其实就是由这三个…

Blender里复制对象动画

假设在Blender里有2个对象&#xff0c;其中一个添加了动画&#xff0c;另外一个没有添加动画&#xff0c;那么如何把已有的动画拷贝到没有动画的对象上呢&#xff1f; 分为2步&#xff1a; 先选中没有动画的对象&#xff0c;再按shift键选中有动画的对象&#xff0c;此时2个对…

【论文精读】Learning Transferable Visual Models From Natural Language Supervision

Learning Transferable Visual Models From Natural Language Supervision 前言Abstract1. Introduction and Motivating Work2. Approach2.1. Creating a Sufficiently Large Dataset2.2. Selecting an Efficient Pre-Training Method2.3. Choosing and Scaling a Model2.4. P…

给Hexo添加说说功能

首发博客地址 官网地址 效果 &#x1f440; 前言 GitHub 仓库&#xff1a;Artitalk.js &#x1f389; 特性 增删查改全方面支持 支持针对每条说说的评论 支持 Markdown/html 语法 支持图片上传 &#x1f680; 快速使用 下列主题已将本项目整合进去&#xff0c;可以直接使用。 感…

Linux——常用命令大汇总(带你快速入门Linux)

纵有疾风起&#xff0c;人生不言弃。本文篇幅较长&#xff0c;如有错误请不吝赐教&#xff0c;感谢支持。 &#x1f4ac;文章目录 一.终端和shell命令解析器终端和shell命令解析器概述终端提示符的格式常用快捷键 二.Linux命令格式帮助文档&#xff1a;man 三.目录基础知识Wind…

什么是RTC

参考&#xff1a; https://zhuanlan.zhihu.com/p/377100294 RTC&#xff08;Real time communication&#xff09;实时通信&#xff0c;是实时音视频的一个简称&#xff0c;我们常说的RTC技术一般指的是WebRTC技术&#xff0c;已经被 W3C 和 IETF 发布为正式标准。由于几乎所…

tableau基础学习2:时间序列数据预处理与绘图

文章目录 数据预处理1. 原始数据2. 合并数据集2. 创建计算字段 绘图分析1. 趋势分析2. 计算字段趋势分析 这一部分&#xff0c;我们记录一些分析时序趋势的分析步骤 数据预处理 1. 原始数据 原始数据是excel表格&#xff0c;其中包含三个Sheet页&#xff0c; 这里我们选择两…

老程序员教你如何笑对问题,轻松培养逻辑思考和解决问题的能力

原文链接 ​​​​​​​老程序员教你如何笑对问题&#xff0c;轻松培养逻辑思考和解决问题的能力 故事发生在一个阳光明媚的午后&#xff0c;我们的主人公&#xff0c;老李&#xff0c;一位拥有十年工作经验的 Python 老程序员&#xff0c;正悠哉地在喝着咖啡。 这时&#x…

VisualStudio配置pybind11-Python调用C++方法

个人测试下来Debug生成的dll改pyd&#xff0c;py中import会报错gilstate->autoInterpreterState 如果遇到同样问题使用Release吧 目录 1.安装pybind11 1.pip&#xff1a; 2.github&#xff1a; 2.配置VS工程 2.在VC目录中的包含目录添加&#xff1a; 3.在VC目录中的库目录…

Debezium的三种部署方式

Debezium如何部署 debezium 有下面三种部署方式,其中最常用的就是 kafka connect。 kafka connect 一般情况下,我们通过 kafka connect 来部署 debezium,kafka connect 是一个框架和运行时: source connectors:像 debezium 这样将记录发送到 kafka 的source connectors…

JavaScript基础语法04——输入输出语法

嗨&#xff0c;大家好&#xff0c;我是雷工。 今天学习JavaScript基础语法&#xff0c;输入输出语法&#xff0c;以下为学习笔记。 1、输出语法&#xff1a; 1.1、alert&#xff08;&#xff09; 作用&#xff1a;界面弹出警告对话框。 示例&#xff1a; <script>aler…

数据结构入门 — 队列

本文属于数据结构专栏文章&#xff0c;适合数据结构入门者学习&#xff0c;涵盖数据结构基础的知识和内容体系&#xff0c;文章在介绍数据结构时会配合上动图演示&#xff0c;方便初学者在学习数据结构时理解和学习&#xff0c;了解数据结构系列专栏点击下方链接。 博客主页&am…

Linux centos7 bash编程(循环与条件判断)

在编程训练中&#xff0c;循环结构与条件判断十分重要。 根据条件为真为假确定是否执行循环。 有时&#xff0c;根据条件的真假结果&#xff0c;决定执行哪些语句&#xff0c;这就是分支语句。 为了训练分支语句与循环语句&#xff0c;我们设计一个案例&#xff1a; 求一组…

Python库-coverage测试覆盖率

Coverage.py 是用于测量Python程序代码覆盖率的工具。它 监视程序&#xff0c;注意代码的哪些部分已执行&#xff0c;然后 分析源以识别可以执行但未执行的代码。 覆盖率测量通常用于衡量测试的有效性。它 可以显示测试正在执行代码的哪些部分&#xff0c;以及哪些部分是 不。…

CentOS配置Java环境报错-bash: /usr/local/jdk1.8.0_381/bin/java: 无法执行二进制文件

CentOS配置Java环境后执行java -version时报错&#xff1a; -bash: /usr/local/jdk1.8.0_381/bin/java: 无法执行二进制文件原因是所使用的jdk的版本和Linux内核架构匹配不上 使用以下命令查看Linux架构&#xff1a; [rootlocalhost ~]# cat /proc/version Linux version 3.1…

C语言:大小端字节序存储

一、大小端字节序存储介绍 大端字节序存储模式&#xff1a;把一个数据低位字节处的数据存放在高地址处&#xff0c;数据高位字节处的数据存放在低地址处 小端字节序存储模式&#xff1a;把一个数据低位字节处的数据存放在低地址处&#xff0c;数据高位字节处的数据存放在高地址…