前言

黑马头条项目笔记

 

一、大文本静态化方案

一个新闻资讯网站,用户查询新闻/文章详情的频率会很高,所有我们可以使用大文本静态化方案减少数据库查询;

借助Freemarker模板引擎把文章详情中的大文本内容,提前渲染成HTML文件存储到对象存储服务-Minio中

当用户查看新闻/文章详情时,直接使用URL从Minio-对象存储中get文章内容;

此举可以减少数据库查询,提升文章内容查询效率;

1.freemarker

模板渲染引擎, 

1.1.模板

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}
<hr>

<#--1.集合遍历-->
<#--2.if..else分支判断-->
<#list studentList as student>
    <#if (student.age < 20) || (student.name="Martin")>
        <p style="color: aquamarine"> ${student.name}<---->${student.age}<br/></p>
    <#else>
        <p style="color: aqua">${student.name}<---->${student.age+6}</p>
    </#if>
</#list>


<#--3.判空、size方法、集合遍历-->
<#if studentList??>
   <p>列表的总长度:${studentList?size} </p>
    <#list studentList as student>
        <#if (student.age < 20) || (student.name="Martin")>
        <p style="color: aquamarine"> ${student.name}<---->${student.age}<br/></p>
        <#else>
        <p style="color: aqua">${student.name}<---->${student.age+6}</p>
        </#if>
    </#list>
</#if>


<#--4.日期函数-->
<#--5.内建函数 -->
${today?date}
<br/>
${today?time}
<br/>
${today?datetime}
<br/>
${today?string("yyyy年MM月")}

</body>
</html>
01-basic.ftl

1.2.单元测试

package com.heima.freemarker.demo;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

//单元测试和SpringBoot入口程序保证在同1个包下
@SpringBootTest(classes = FreemarkerDemoApplication.class)
//@RunWith(SpringRunner.class)
public class freemarkerTest {
    @Autowired
    private Configuration configuration;

    @Test
    public void test() throws IOException, TemplateException {
        //1.将生成的HTML文件放到当前目录
        //2.导入configration依赖
        //3.指定模板
        Template template = configuration.getTemplate("01-basic.ftl");
        //4.分配数据
        Map context = getDate();
        //5.生成文件
        FileWriter fw = new FileWriter("test.html");
        template.process(context, fw);
    }

    private Map getDate() {
        //map1
        Map<String, Object> context = new HashMap<>();
        context.put("name","张根");
        Map<String, Object> stuMap1 = new HashMap<>();
        stuMap1.put("name", "Martin");
        stuMap1.put("age", 19);
        context.put("stu", stuMap1);
        //map2
        Map<String, Object> stuMap2 = new HashMap<>();
        stuMap2.put("name", "Tom");
        stuMap2.put("age", 20);
        List<Map<String, Object>> studentList = new ArrayList<>();
        studentList.add(stuMap1);
        studentList.add(stuMap2);
        context.put("studentList", studentList);
        // 新增日期字段
        context.put("today", new Date());
        return context;
    }
}
freemarkerTest.java

 

2.分库分表

数据库优化方案

2.1.水平分表

水平分表就是指以行为单位对数据进行拆分,一般意义上的分库分表指的就是水平分表
分表之后,所有表的结构都是一样的。

2.2.垂直分表

表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。

垂直分表就是把一张表按列分为多张表,多张表通过主键进行关联,从而组成完整的数据。

 二、图片上传

SpringMVC拦截器只能返回true和false;

SpringMVC拦截器向SpringMVC处理器传递用户信息,借助ThreadLocal做中间介质;

 

三、新闻发布功能

1.数据库表关系

2.执行流程

 

四、新闻内容审核

Day03完成的新闻发布功能没有对新闻内容进行含敏感信息审核;

自媒体端发布新闻之后,通过阿里云的审核功能,对新闻的文字、图片内容进行自动审核;

 新闻的文字、图片内容审核通过之后,通过Feign向文章微服务发起远程调用,把新闻保存为文章

远程调用之后文章微服务返回ArticleId,自媒体微服务获取ArticleId和wm_news的article_id自动进行新闻和文章的绑定;

 

五、自建议敏感词库

使用阿里云做新闻内容的审核,无法审核行业内一些专有敏感词;

需要自己维护1个敏感词库,再使用阿里云内容审核之前,先去自己的敏感词库匹配敏感词;

1.DFA算法

 DFA全称 Deterministic Finite Automaton 即确定有穷自动机:从一个状态通过一系列的事件转换到另一个状态,即 state -> event -> state。

1.1.敏感词库初始化

DAF算法在初始化的时候,会把敏感词库中的所有敏感词,例如病毒、病毒你妹、冰棒、冰箱空调、冰墩墩、冰墩子拆成以下树状结构存储;

 

如果链路的结尾是isEnd=1代表是敏感词,isEnd=0代表非敏感词;

 

1.2.关键词和敏感词匹配

把客户输入的关键词也拆分成1个个的字,和敏感词库中存在的每一条链路进行比对,直到链路的边界结束;

1.命中敏感词情况

如果客户端输入病毒哈哈哈哈你妹啊,会把这句话拆成1个个的字;

 

拿冰字和敏感词库中的冰字对比匹配成功;

判断是否是结尾?不是结尾

拿毒字和敏感词库中的毒字对比匹配成功 ;

判断是结尾isEnd=0病毒就是敏感词;

2.未命中敏感词情况

如果客户端输入病哈毒哈哈哈你妹啊,会把这句话拆成1个个的字;

 

拿关键词中冰字和敏感词库中的冰字对比匹配成功;

判断是否是结尾?不是结尾

拿哈字和敏感词库中的毒字对比匹配失败;

拿哈字和敏感词库中的棒字对比匹配失败;

拿哈字和敏感词库中的箱字对比匹配失败;

 拿哈字和敏感词库中的蹲字对比匹配失败;

 ---------------------------------------------------------

3.DFA算法实现

package com.heima.utils;


import java.util.*;

public class SensitiveWordUtil {

    public static Map<String, Object> dictionaryMap = new HashMap<>();


    /**
     * 生成关键词字典库
     * @param words
     * @return
     */
    public static void initMap(Collection<String> words) {
        if (words == null) {
            System.out.println("敏感词列表不能为空");
            return ;
        }

        // map初始长度words.size(),整个字典库的入口字数(小于words.size(),因为不同的词可能会有相同的首字)
        Map<String, Object> map = new HashMap<>(words.size());
        // 遍历过程中当前层次的数据
        Map<String, Object> curMap = null;
        Iterator<String> iterator = words.iterator();

        while (iterator.hasNext()) {
            String word = iterator.next();
            curMap = map;
            int len = word.length();
            for (int i =0; i < len; i++) {
                // 遍历每个词的字
                String key = String.valueOf(word.charAt(i));
                // 当前字在当前层是否存在, 不存在则新建, 当前层数据指向下一个节点, 继续判断是否存在数据
                Map<String, Object> wordMap = (Map<String, Object>) curMap.get(key);
                if (wordMap == null) {
                    // 每个节点存在两个数据: 下一个节点和isEnd(是否结束标志)
                    wordMap = new HashMap<>(2);
                    wordMap.put("isEnd", "0");
                    curMap.put(key, wordMap);
                }
                curMap = wordMap;
                // 如果当前字是词的最后一个字,则将isEnd标志置1
                if (i == len -1) {
                    curMap.put("isEnd", "1");
                }
            }
        }

        dictionaryMap = map;
    }

    /**
     * 搜索文本中某个文字是否匹配关键词
     * @param text
     * @param beginIndex
     * @return
     */
    private static int checkWord(String text, int beginIndex) {
        if (dictionaryMap == null) {
            throw new RuntimeException("字典不能为空");
        }
        boolean isEnd = false;
        int wordLength = 0;
        Map<String, Object> curMap = dictionaryMap;
        int len = text.length();
        // 从文本的第beginIndex开始匹配
        for (int i = beginIndex; i < len; i++) {
            String key = String.valueOf(text.charAt(i));
            // 获取当前key的下一个节点
            curMap = (Map<String, Object>) curMap.get(key);
            if (curMap == null) {
                break;
            } else {
                wordLength ++;
                if ("1".equals(curMap.get("isEnd"))) {
                    isEnd = true;
                }
            }
        }
        if (!isEnd) {
            wordLength = 0;
        }
        return wordLength;
    }

    /**
     * 获取匹配的关键词和命中次数
     * @param text
     * @return
     */
    public static Map<String, Integer> matchWords(String text) {
        Map<String, Integer> wordMap = new HashMap<>();
        int len = text.length();
        for (int i = 0; i < len; i++) {
            int wordLength = checkWord(text, i);
            if (wordLength > 0) {
                String word = text.substring(i, i + wordLength);
                // 添加关键词匹配次数
                if (wordMap.containsKey(word)) {
                    wordMap.put(word, wordMap.get(word) + 1);
                } else {
                    wordMap.put(word, 1);
                }

                i += wordLength - 1;
            }
        }
        return wordMap;
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("是是");
        list.add("法是功");
        list.add("冰毒");
        initMap(list);
        String content="我是一个好人,并不会卖冰毒,我真的不卖冰毒";
        Map<String, Integer> map = matchWords(content);
        System.out.println(map);
    }
}
SensitiveWordUtil.java

4.调用

//调用自建敏感词库进行内容审核
    private Boolean dfaCheck(String sentence){
        if(StringUtils.isBlank(sentence)){
            return true;
        }
        //1.使用DFA算法进行自建敏感词处理
        //2.调用敏感词Mapper,查询所有敏感词
        LambdaQueryWrapper<WmSensitive> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(WmSensitive::getSensitives);
        List<WmSensitive> wmSensitives = wmSensitiveMapper.selectList(wrapper);
        if(CollectionUtils.isEmpty(wmSensitives)){
            return true;
        }
        List<String> words = wmSensitives.stream().filter(Objects::nonNull).map(WmSensitive::getSensitives).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(words)){
            return true;
        }
        //3.DFA初始化
        SensitiveWordUtil.initMap(words);
        //4.DFA敏感词对比
        Map<String, Integer> result = SensitiveWordUtil.matchWords(sentence);
        //5.判断结果
       return CollectionUtils.isEmpty(result);
    }
    //分析阿里云返回的扫描结果
    private Boolean analyseScanResult(Map scanMap, Long articleId, Integer newsId) {
        //判断审核结果是否为空
        if (CollectionUtils.isEmpty(scanMap)) {
            log.warn("审核结果为空");
            return true;
        }
        String suggestion = (String) scanMap.get("suggestion");
        if ("pass".equals(suggestion)) {
            return true;
        } else if ("block".equals(suggestion)) {
            //审核未通过----更新wmNews表的status字段,但是不更新wmNes表的articleId字段
            updateWmNews(2, "未通过", articleId, newsId);
            return false;
        } else if ("review".equals(suggestion)) {
            //审核未通过----更新wmNews表的status字段,但是不更新wmNes表的articleId字段
            updateWmNews(3, "转人工审核", articleId, newsId);
        }
        return true;
    }
AutoScanServiceImpl.java

 

六、图片文字审核

文章中包含的图片要识别文字,过滤掉图片文字的敏感词;

1.OCR

OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程;

方案说明
百度OCR 收费
Tesseract-OCR Google维护的开源OCR引擎,支持Java,Python等语言调用
Tess4J 封装了Tesseract-OCR ,支持Java调用

利用OCR技术提取图片中的文字内容,进行DFA和阿里云文字内容审核; 

2.引入OCR依赖

  <!--ocr依赖-->
        <dependency>
            <groupId>net.sourceforge.tess4j</groupId>
            <artifactId>tess4j</artifactId>
            <version>4.1.1</version>
        </dependency>

3.工具类

package com.heima.utils;

import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;

import java.awt.image.BufferedImage;

public class OcrUtil {
    private final static String DATA_PATH = "D:\\workspace\\cp386\\project\\lead-news\\leadnews-base\\";
    private final static String LANGUAGE = "chi_sim";

    public static String doOcr(BufferedImage image) throws TesseractException {
        //创建Tesseract对象
        ITesseract tesseract = new Tesseract();
        //设置字体库路径
        tesseract.setDatapath(DATA_PATH);
        //中文识别
        tesseract.setLanguage(LANGUAGE);
        //执行ocr识别
        String result = tesseract.doOCR(image);
        //替换回车和tal键  使结果为一行
        result = result.replaceAll("[\\r\\n]", "-").replaceAll(" ", "");
        return result;
    }
}
OcrUtil.java

4.添加中文字体库

导入中文字体库, 把资料中的 chi_sim.traineddata 文件拷贝到项目根目录下

5.调用工具类

 //调用ORC进行图片中包含文字的检测
    private String orcCheck(List<byte[]> images) throws IOException, TesseractException {
        //判空
        if (CollectionUtils.isEmpty(images)) {
            return "";
        }

        StringBuilder stringBuilder = new StringBuilder();
        //接收图片并遍历
        for (byte[] image : images) {
            //调用ORC工具类,提取图片中的内容
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(image);
            BufferedImage read = ImageIO.read(byteArrayInputStream);
            String textFromImage = OcrUtil.doOcr(read);
            //将文字内容组成1个字符串
            stringBuilder.append(textFromImage);
        }
        //返回提取到的文字
        return stringBuilder.toString();
    }
AutoScanServiceImpl.java

 

七、延迟发布文章

用户频繁的发布文章,MySQL就会频繁执行Insert操作,新增文章任务,为规避频繁的磁盘IO耗时,新发布的文章直接存MySQL不合适,MySQL负责持久化;

Redis实现队列功能的同时有新增了1个延迟队列实现文章的延迟发布;

 

 

 

1.什么是延迟任务

定时任务:有固定周期的,有明确的触发时间

2.延迟任务应用场景

场景一:订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单;如果期间下单成功,任务取消

场景二:定时发布新闻

 

3.延迟队列技术对比

有2种方式可以实现延迟队列;

3.1.RabbitMQ实现延迟任务

  • TTL:Time To Live (消息存活时间)

  • 死信队列:Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以重新发送另一个交换机(死信交换机)

 

3.2.redis实现

zset数据类型的去重有序(分数排序)特点进行延迟。例如:时间戳作为score进行排序;

4.Redis管道技术

管道(pipeline)可以一次性发送多条命令并在执行完后一次性将结果返回;

Pipeline通过减少客户端与Redis的通信次数来实现降低往返延时时间;

Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性;

Pipline管道技术比逐条执行要快,特别是客户端与服务端的网络延迟越大,体能越明显;

 

代码

package com.heima.common.chache;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public class CacheService extends CachingConfigurerSupport {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public StringRedisTemplate getstringRedisTemplate() {
        return this.stringRedisTemplate;
    }

    /** -------------------key相关操作--------------------- */

    /**
     * 删除key
     *
     * @param key
     */
    public void delete(String key) {
        stringRedisTemplate.delete(key);
    }

    /**
     * 批量删除key
     *
     * @param keys
     */
    public void delete(Collection<String> keys) {
        stringRedisTemplate.delete(keys);
    }

    /**
     * 序列化key
     *
     * @param key
     * @return
     */
    public byte[] dump(String key) {
        return stringRedisTemplate.dump(key);
    }

    /**
     * 是否存在key
     *
     * @param key
     * @return
     */
    public Boolean exists(String key) {
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param timeout
     * @param unit
     * @return
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return stringRedisTemplate.expire(key, timeout, unit);
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param date
     * @return
     */
    public Boolean expireAt(String key, Date date) {
        return stringRedisTemplate.expireAt(key, date);
    }

    /**
     * 查找匹配的key
     *
     * @param pattern
     * @return
     */
    public Set<String> keys(String pattern) {
        return stringRedisTemplate.keys(pattern);
    }

    /**
     * 将当前数据库的 key 移动到给定的数据库 db 当中
     *
     * @param key
     * @param dbIndex
     * @return
     */
    public Boolean move(String key, int dbIndex) {
        return stringRedisTemplate.move(key, dbIndex);
    }

    /**
     * 移除 key 的过期时间,key 将持久保持
     *
     * @param key
     * @return
     */
    public Boolean persist(String key) {
        return stringRedisTemplate.persist(key);
    }

    /**
     * 返回 key 的剩余的过期时间
     *
     * @param key
     * @param unit
     * @return
     */
    public Long getExpire(String key, TimeUnit unit) {
        return stringRedisTemplate.getExpire(key, unit);
    }

    /**
     * 返回 key 的剩余的过期时间
     *
     * @param key
     * @return
     */
    public Long getExpire(String key) {
        return stringRedisTemplate.getExpire(key);
    }

    /**
     * 从当前数据库中随机返回一个 key
     *
     * @return
     */
    public String randomKey() {
        return stringRedisTemplate.randomKey();
    }

    /**
     * 修改 key 的名称
     *
     * @param oldKey
     * @param newKey
     */
    public void rename(String oldKey, String newKey) {
        stringRedisTemplate.rename(oldKey, newKey);
    }

    /**
     * 仅当 newkey 不存在时,将 oldKey 改名为 newkey
     *
     * @param oldKey
     * @param newKey
     * @return
     */
    public Boolean renameIfAbsent(String oldKey, String newKey) {
        return stringRedisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 返回 key 所储存的值的类型
     *
     * @param key
     * @return
     */
    public DataType type(String key) {
        return stringRedisTemplate.type(key);
    }

    /** -------------------string相关操作--------------------- */

    /**
     * 设置指定 key 的值
     *
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * 获取指定 key 的值
     *
     * @param key
     * @return
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 返回 key 中字符串值的子字符
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public String getRange(String key, long start, long end) {
        return stringRedisTemplate.opsForValue().get(key, start, end);
    }

    /**
     * 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
     *
     * @param key
     * @param value
     * @return
     */
    public String getAndSet(String key, String value) {
        return stringRedisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
     *
     * @param key
     * @param offset
     * @return
     */
    public Boolean getBit(String key, long offset) {
        return stringRedisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * 批量获取
     *
     * @param keys
     * @return
     */
    public List<String> multiGet(Collection<String> keys) {
        return stringRedisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
     *
     * @param key
     * @param
     * @param value 值,true为1, false为0
     * @return
     */
    public boolean setBit(String key, long offset, boolean value) {
        return stringRedisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
     *
     * @param key
     * @param value
     * @param timeout 过期时间
     * @param unit    时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES
     *                秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
     */
    public void setEx(String key, String value, long timeout, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 只有在 key 不存在时设置 key 的值
     *
     * @param key
     * @param value
     * @return 之前已经存在返回false, 不存在返回true
     */
    public boolean setIfAbsent(String key, String value) {
        return stringRedisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
     *
     * @param key
     * @param value
     * @param offset 从指定位置开始覆写
     */
    public void setRange(String key, String value, long offset) {
        stringRedisTemplate.opsForValue().set(key, value, offset);
    }

    /**
     * 获取字符串的长度
     *
     * @param key
     * @return
     */
    public Long size(String key) {
        return stringRedisTemplate.opsForValue().size(key);
    }

    /**
     * 批量添加
     *
     * @param maps
     */
    public void multiSet(Map<String, String> maps) {
        stringRedisTemplate.opsForValue().multiSet(maps);
    }

    /**
     * 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
     *
     * @param maps
     * @return 之前已经存在返回false, 不存在返回true
     */
    public boolean multiSetIfAbsent(Map<String, String> maps) {
        return stringRedisTemplate.opsForValue().multiSetIfAbsent(maps);
    }

    /**
     * 增加(自增长), 负数则为自减
     *
     * @param key
     * @param
     * @return
     */
    public Long incrBy(String key, long increment) {
        return stringRedisTemplate.opsForValue().increment(key, increment);
    }

    /**
     * @param key
     * @param
     * @return
     */
    public Double incrByFloat(String key, double increment) {
        return stringRedisTemplate.opsForValue().increment(key, increment);
    }

    /**
     * 追加到末尾
     *
     * @param key
     * @param value
     * @return
     */
    public Integer append(String key, String value) {
        return stringRedisTemplate.opsForValue().append(key, value);
    }

    /** -------------------hash相关操作------------------------- */

    /**
     * 获取存储在哈希表中指定字段的值
     *
     * @param key
     * @param field
     * @return
     */
    public Object hGet(String key, String field) {
        return stringRedisTemplate.opsForHash().get(key, field);
    }

    /**
     * 获取所有给定字段的值
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hGetAll(String key) {
        return stringRedisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取所有给定字段的值
     *
     * @param key
     * @param fields
     * @return
     */
    public List<Object> hMultiGet(String key, Collection<Object> fields) {
        return stringRedisTemplate.opsForHash().multiGet(key, fields);
    }

    public void hPut(String key, String hashKey, String value) {
        stringRedisTemplate.opsForHash().put(key, hashKey, value);
    }

    public void hPutAll(String key, Map<String, String> maps) {
        stringRedisTemplate.opsForHash().putAll(key, maps);
    }

    /**
     * 仅当hashKey不存在时才设置
     *
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public Boolean hPutIfAbsent(String key, String hashKey, String value) {
        return stringRedisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    /**
     * 删除一个或多个哈希表字段
     *
     * @param key
     * @param fields
     * @return
     */
    public Long hDelete(String key, Object... fields) {
        return stringRedisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 查看哈希表 key 中,指定的字段是否存在
     *
     * @param key
     * @param field
     * @return
     */
    public boolean hExists(String key, String field) {
        return stringRedisTemplate.opsForHash().hasKey(key, field);
    }

    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     *
     * @param key
     * @param field
     * @param increment
     * @return
     */
    public Long hIncrBy(String key, Object field, long increment) {
        return stringRedisTemplate.opsForHash().increment(key, field, increment);
    }

    /**
     * 为哈希表 key 中的指定字段的整数值加上增量 increment
     *
     * @param key
     * @param field
     * @param delta
     * @return
     */
    public Double hIncrByFloat(String key, Object field, double delta) {
        return stringRedisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * 获取所有哈希表中的字段
     *
     * @param key
     * @return
     */
    public Set<Object> hKeys(String key) {
        return stringRedisTemplate.opsForHash().keys(key);
    }

    /**
     * 获取哈希表中字段的数量
     *
     * @param key
     * @return
     */
    public Long hSize(String key) {
        return stringRedisTemplate.opsForHash().size(key);
    }

    /**
     * 获取哈希表中所有值
     *
     * @param key
     * @return
     */
    public List<Object> hValues(String key) {
        return stringRedisTemplate.opsForHash().values(key);
    }

    /**
     * 迭代哈希表中的键值对
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
        return stringRedisTemplate.opsForHash().scan(key, options);
    }

    /** ------------------------list相关操作---------------------------- */

    /**
     * 通过索引获取列表中的元素
     *
     * @param key
     * @param index
     * @return
     */
    public String lIndex(String key, long index) {
        return stringRedisTemplate.opsForList().index(key, index);
    }

    /**
     * 获取列表指定范围内的元素
     *
     * @param key
     * @param start 开始位置, 0是开始位置
     * @param end   结束位置, -1返回所有
     * @return
     */
    public List<String> lRange(String key, long start, long end) {
        return stringRedisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 存储在list头部
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPush(String key, String value) {
        return stringRedisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushAll(String key, String... value) {
        return stringRedisTemplate.opsForList().leftPushAll(key, value);
    }

    /**
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushAll(String key, Collection<String> value) {
        return stringRedisTemplate.opsForList().leftPushAll(key, value);
    }

    /**
     * 当list存在的时候才加入
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushIfPresent(String key, String value) {
        return stringRedisTemplate.opsForList().leftPushIfPresent(key, value);
    }

    /**
     * 如果pivot存在,再pivot前面添加
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public Long lLeftPush(String key, String pivot, String value) {
        return stringRedisTemplate.opsForList().leftPush(key, pivot, value);
    }

    /**
     * @param key
     * @param value
     * @return
     */
    public Long lRightPush(String key, String value) {
        return stringRedisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushAll(String key, String... value) {
        return stringRedisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushAll(String key, Collection<String> value) {
        return stringRedisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * 为已存在的列表添加值
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushIfPresent(String key, String value) {
        return stringRedisTemplate.opsForList().rightPushIfPresent(key, value);
    }

    /**
     * 在pivot元素的右边添加值
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public Long lRightPush(String key, String pivot, String value) {
        return stringRedisTemplate.opsForList().rightPush(key, pivot, value);
    }

    /**
     * 通过索引设置列表元素的值
     *
     * @param key
     * @param index 位置
     * @param value
     */
    public void lSet(String key, long index, String value) {
        stringRedisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 移出并获取列表的第一个元素
     *
     * @param key
     * @return 删除的元素
     */
    public String lLeftPop(String key) {
        return stringRedisTemplate.opsForList().leftPop(key);
    }

    /**
     * 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
     *
     * @param key
     * @param timeout 等待时间
     * @param unit    时间单位
     * @return
     */
    public String lBLeftPop(String key, long timeout, TimeUnit unit) {
        return stringRedisTemplate.opsForList().leftPop(key, timeout, unit);
    }

    /**
     * 移除并获取列表最后一个元素
     *
     * @param key
     * @return 删除的元素
     */
    public String lRightPop(String key) {
        return stringRedisTemplate.opsForList().rightPop(key);
    }

    /**
     * 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
     *
     * @param key
     * @param timeout 等待时间
     * @param unit    时间单位
     * @return
     */
    public String lBRightPop(String key, long timeout, TimeUnit unit) {
        return stringRedisTemplate.opsForList().rightPop(key, timeout, unit);
    }

    /**
     * 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
     *
     * @param sourceKey
     * @param destinationKey
     * @return
     */
    public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
        return stringRedisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                destinationKey);
    }

    /**
     * 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
     *
     * @param sourceKey
     * @param destinationKey
     * @param timeout
     * @param unit
     * @return
     */
    public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
                                        long timeout, TimeUnit unit) {
        return stringRedisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                destinationKey, timeout, unit);
    }

    /**
     * 删除集合中值等于value得元素
     *
     * @param key
     * @param index index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;
     *              index<0, 从尾部开始删除第一个值等于value的元素;
     * @param value
     * @return
     */
    public Long lRemove(String key, long index, String value) {
        return stringRedisTemplate.opsForList().remove(key, index, value);
    }

    /**
     * 裁剪list
     *
     * @param key
     * @param start
     * @param end
     */
    public void lTrim(String key, long start, long end) {
        stringRedisTemplate.opsForList().trim(key, start, end);
    }

    /**
     * 获取列表长度
     *
     * @param key
     * @return
     */
    public Long lLen(String key) {
        return stringRedisTemplate.opsForList().size(key);
    }


    /** --------------------set相关操作-------------------------- */

    /**
     * set添加元素
     *
     * @param key
     * @param values
     * @return
     */
    public Long sAdd(String key, String... values) {
        return stringRedisTemplate.opsForSet().add(key, values);
    }

    /**
     * set移除元素
     *
     * @param key
     * @param values
     * @return
     */
    public Long sRemove(String key, Object... values) {
        return stringRedisTemplate.opsForSet().remove(key, values);
    }

    /**
     * 移除并返回集合的一个随机元素
     *
     * @param key
     * @return
     */
    public String sPop(String key) {
        return stringRedisTemplate.opsForSet().pop(key);
    }

    /**
     * 将元素value从一个集合移到另一个集合
     *
     * @param key
     * @param value
     * @param destKey
     * @return
     */
    public Boolean sMove(String key, String value, String destKey) {
        return stringRedisTemplate.opsForSet().move(key, value, destKey);
    }

    /**
     * 获取集合的大小
     *
     * @param key
     * @return
     */
    public Long sSize(String key) {
        return stringRedisTemplate.opsForSet().size(key);
    }

    /**
     * 判断集合是否包含value
     *
     * @param key
     * @param value
     * @return
     */
    public Boolean sIsMember(String key, Object value) {
        return stringRedisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 获取两个集合的交集
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<String> sIntersect(String key, String otherKey) {
        return stringRedisTemplate.opsForSet().intersect(key, otherKey);
    }

    /**
     * 获取key集合与多个集合的交集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sIntersect(String key, Collection<String> otherKeys) {
        return stringRedisTemplate.opsForSet().intersect(key, otherKeys);
    }

    /**
     * key集合与otherKey集合的交集存储到destKey集合中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sIntersectAndStore(String key, String otherKey, String destKey) {
        return stringRedisTemplate.opsForSet().intersectAndStore(key, otherKey,
                destKey);
    }

    /**
     * key集合与多个集合的交集存储到destKey集合中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sIntersectAndStore(String key, Collection<String> otherKeys,
                                   String destKey) {
        return stringRedisTemplate.opsForSet().intersectAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 获取两个集合的并集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sUnion(String key, String otherKeys) {
        return stringRedisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * 获取key集合与多个集合的并集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sUnion(String key, Collection<String> otherKeys) {
        return stringRedisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * key集合与otherKey集合的并集存储到destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sUnionAndStore(String key, String otherKey, String destKey) {
        return stringRedisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     * key集合与多个集合的并集存储到destKey中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sUnionAndStore(String key, Collection<String> otherKeys,
                               String destKey) {
        return stringRedisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 获取两个集合的差集
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<String> sDifference(String key, String otherKey) {
        return stringRedisTemplate.opsForSet().difference(key, otherKey);
    }

    /**
     * 获取key集合与多个集合的差集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sDifference(String key, Collection<String> otherKeys) {
        return stringRedisTemplate.opsForSet().difference(key, otherKeys);
    }

    /**
     * key集合与otherKey集合的差集存储到destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sDifference(String key, String otherKey, String destKey) {
        return stringRedisTemplate.opsForSet().differenceAndStore(key, otherKey,
                destKey);
    }

    /**
     * key集合与多个集合的差集存储到destKey中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sDifference(String key, Collection<String> otherKeys,
                            String destKey) {
        return stringRedisTemplate.opsForSet().differenceAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 获取集合所有元素
     *
     * @param key
     * @param
     * @param
     * @return
     */
    public Set<String> setMembers(String key) {
        return stringRedisTemplate.opsForSet().members(key);
    }

    /**
     * 随机获取集合中的一个元素
     *
     * @param key
     * @return
     */
    public String sRandomMember(String key) {
        return stringRedisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 随机获取集合中count个元素
     *
     * @param key
     * @param count
     * @return
     */
    public List<String> sRandomMembers(String key, long count) {
        return stringRedisTemplate.opsForSet().randomMembers(key, count);
    }

    /**
     * 随机获取集合中count个元素并且去除重复的
     *
     * @param key
     * @param count
     * @return
     */
    public Set<String> sDistinctRandomMembers(String key, long count) {
        return stringRedisTemplate.opsForSet().distinctRandomMembers(key, count);
    }

    /**
     * @param key
     * @param options
     * @return
     */
    public Cursor<String> sScan(String key, ScanOptions options) {
        return stringRedisTemplate.opsForSet().scan(key, options);
    }

    /**------------------zSet相关操作--------------------------------*/

    /**
     * 添加元素,有序集合是按照元素的score值由小到大排列
     *
     * @param key
     * @param value
     * @param score
     * @return
     */
    public Boolean zAdd(String key, String value, double score) {
        return stringRedisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     * @param key
     * @param values
     * @return
     */
    public Long zAdd(String key, Set<TypedTuple<String>> values) {
        return stringRedisTemplate.opsForZSet().add(key, values);
    }

    /**
     * @param key
     * @param values
     * @return
     */
    public Long zRemove(String key, Object... values) {
        return stringRedisTemplate.opsForZSet().remove(key, values);
    }

    public Long zRemove(String key, Collection<String> values) {
        if (values != null && !values.isEmpty()) {
            Object[] objs = values.toArray(new Object[values.size()]);
            return stringRedisTemplate.opsForZSet().remove(key, objs);
        }
        return 0L;
    }

    /**
     * 增加元素的score值,并返回增加后的值
     *
     * @param key
     * @param value
     * @param delta
     * @return
     */
    public Double zIncrementScore(String key, String value, double delta) {
        return stringRedisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    /**
     * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
     *
     * @param key
     * @param value
     * @return 0表示第一位
     */
    public Long zRank(String key, Object value) {
        return stringRedisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 返回元素在集合的排名,按元素的score值由大到小排列
     *
     * @param key
     * @param value
     * @return
     */
    public Long zReverseRank(String key, Object value) {
        return stringRedisTemplate.opsForZSet().reverseRank(key, value);
    }

    /**
     * 获取集合的元素, 从小到大排序
     *
     * @param key
     * @param start 开始位置
     * @param end   结束位置, -1查询所有
     * @return
     */
    public Set<String> zRange(String key, long start, long end) {
        return stringRedisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 获取zset集合的所有元素, 从小到大排序
     */
    public Set<String> zRangeAll(String key) {
        return zRange(key, 0, -1);
    }

    /**
     * 获取集合元素, 并且把score值也获取
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zRangeWithScores(String key, long start,
                                                    long end) {
        return stringRedisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * 根据Score值查询集合元素
     *
     * @param key
     * @param min 最小值
     * @param max 最大值
     * @return
     */
    public Set<String> zRangeByScore(String key, double min, double max) {
        return stringRedisTemplate.opsForZSet().rangeByScore(key, min, max);
    }


    /**
     * 根据Score值查询集合元素, 从小到大排序
     *
     * @param key
     * @param min 最小值
     * @param max 最大值
     * @return
     */
    public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
                                                           double min, double max) {
        return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
    }

    /**
     * @param key
     * @param min
     * @param max
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
                                                           double min, double max, long start, long end) {
        return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
                start, end);
    }

    /**
     * 获取集合的元素, 从大到小排序
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> zReverseRange(String key, long start, long end) {
        return stringRedisTemplate.opsForZSet().reverseRange(key, start, end);

    }

    public Set<String> zReverseRangeByScore(String key, long min, long max) {
        return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max);

    }

    /**
     * 获取集合的元素, 从大到小排序, 并返回score值
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zReverseRangeWithScores(String key,
                                                           long start, long end) {
        return stringRedisTemplate.opsForZSet().reverseRangeWithScores(key, start,
                end);
    }

    /**
     * 根据Score值查询集合元素, 从大到小排序
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<String> zReverseRangeByScore(String key, double min,
                                            double max) {
        return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    }

    /**
     * 根据Score值查询集合元素, 从大到小排序
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<TypedTuple<String>> zReverseRangeByScoreWithScores(
            String key, double min, double max) {
        return stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
                min, max);
    }

    /**
     * @param key
     * @param min
     * @param max
     * @param start
     * @param end
     * @return
     */
    public Set<String> zReverseRangeByScore(String key, double min,
                                            double max, long start, long end) {
        return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
                start, end);
    }

    /**
     * 根据score值获取集合元素数量
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long zCount(String key, double min, double max) {
        return stringRedisTemplate.opsForZSet().count(key, min, max);
    }

    /**
     * 获取集合大小
     *
     * @param key
     * @return
     */
    public Long zSize(String key) {
        return stringRedisTemplate.opsForZSet().size(key);
    }

    /**
     * 获取集合大小
     *
     * @param key
     * @return
     */
    public Long zZCard(String key) {
        return stringRedisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 获取集合中value元素的score值
     *
     * @param key
     * @param value
     * @return
     */
    public Double zScore(String key, Object value) {
        return stringRedisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 移除指定索引位置的成员
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Long zRemoveRange(String key, long start, long end) {
        return stringRedisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * 根据指定的score值的范围来移除成员
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long zRemoveRangeByScore(String key, double min, double max) {
        return stringRedisTemplate.opsForZSet().removeRangeByScore(key, min, max);
    }

    /**
     * 获取key和otherKey的并集并存储在destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long zUnionAndStore(String key, String otherKey, String destKey) {
        return stringRedisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long zUnionAndStore(String key, Collection<String> otherKeys,
                               String destKey) {
        return stringRedisTemplate.opsForZSet()
                .unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 交集
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long zIntersectAndStore(String key, String otherKey,
                                   String destKey) {
        return stringRedisTemplate.opsForZSet().intersectAndStore(key, otherKey,
                destKey);
    }

    /**
     * 交集
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long zIntersectAndStore(String key, Collection<String> otherKeys,
                                   String destKey) {
        return stringRedisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
                destKey);
    }

    /**
     * @param key
     * @param options
     * @return
     */
    public Cursor<TypedTuple<String>> zScan(String key, ScanOptions options) {
        return stringRedisTemplate.opsForZSet().scan(key, options);
    }

    /**
     * 扫描主键,建议使用
     *
     * @param patten
     * @return
     */
    public Set<String> scan(String patten) {
        Set<String> keys = stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> result = new HashSet<>();
            try (Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder()
                    .match(patten).count(10000).build())) {
                while (cursor.hasNext()) {
                    result.add(new String(cursor.next()));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return result;
        });
        return keys;
    }

    /**
     * 管道技术,提高性能
     *
     * @param type
     * @param values
     * @return
     */
    public List<Object> lRightPushPipeline(String type, Collection<String> values) {
        List<Object> results = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
                //集合转换数组
                String[] strings = values.toArray(new String[values.size()]);
                //直接批量发送
                stringRedisConn.rPush(type, strings);
                return null;
            }
        });
        return results;
    }

    public List<Object> refreshWithPipeline(String future_key, String topic_key, Collection<String> values) {

        List<Object> objects = stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Nullable
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                StringRedisConnection stringRedisConnection = (StringRedisConnection) redisConnection;
                String[] strings = values.toArray(new String[values.size()]);
                stringRedisConnection.rPush(topic_key, strings);
                stringRedisConnection.zRem(future_key, strings);
                return null;
            }
        });
        return objects;
    }
    //分布式锁
    public String tryLock(String name, long expire) {
        name = name + "_lock";
        String token = UUID.randomUUID().toString();
        Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(name, token, expire, TimeUnit.MILLISECONDS);
        if (locked == null) {
            return null;
        }

        return locked ? token : null;
    }
}
CacheService.java

 

5.Spring定时器

Spring中的定时器可以帮助我们定时执行1些周期性任务;

5.1.程序入口

package com.zhanggen.schedule;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;


@SpringBootApplication
@MapperScan("com.zhanggen.schedule.mapper")
//开启定时任务注解
@EnableScheduling
public class ScheduleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ScheduleApplication.class,args);
    }
}

5.2.定时器

//使用PipLine管道技术从Resis的FUTURE队列定时刷新超时任务信息至TOPIC队列
    @Override
    //秒、分钟、小时、天、月
    //@Scheduled(cron ="5/15 * * * * ?"):从第5秒开始,每个15秒执行1次
    @Scheduled(fixedRate = 1000 * 60 * 5) //间隔1秒执行1次
    public void refresh() {
        log.warn("正在从FUTURE队列中转移超时任务到TOPIC队列中");
        //加上分布式锁
        String lockToken = cacheService.tryLock("reflsh_lock", 5000);
        if (StringUtils.isEmpty(lockToken)) {
            return;
        }
        //间隔5分钟,从未来队列中获取超时任务(超时时间大于当前时间)
        Set<String> timeOutTasks = cacheService.zRange("FUTURE", 0, System.currentTimeMillis());
        if (CollectionUtils.isEmpty(timeOutTasks)) {
            log.warn("未来队列中没有可以转移的任务");
            return;
        }
        cacheService.refreshWithPipeline("FUTURE", "TOPIC", timeOutTasks);
    }

 

6.ProtostuffUtil

基于protobuff改造的protostuff,它拥有良好性能的同时,又免去了生成描述文件的烦恼;

快速序列化和反序列化对象;

 

八、实现文章延迟发布

 

1.延迟任务队列加入之前逻辑

自媒体微服务用于发布新闻,

新闻发布之后通过feign调用AutoScan进行文章审核

AutoScan调用文章微服务的saveArticle接口进行文章的保存

saveArticle接口调用MinIoTemplate把文章内容上传到Minio服务器

2.延迟任务队列加入之后逻辑

新增schedul微服务

在自媒体微服务的新闻发布接口进行延迟任务添加

在自媒体微服务新增Scheduled定时任务消费者,定时从Redis中获取任务进行消费

Scheduled定时任务消费者,再调用AutoScan进行内容审核

---------------------------------------------------------------------------------------

AutoScan调用文章微服务的saveArticle接口进行文章的保存

saveArticle接口调用MinIoTemplate把文章内容上传到Minio服务器

  //自媒体新闻发布接口
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public ResponseResult submit(WmNewsDto wmNewsDto) {
        /*
        wm_news(新闻表)和wm_material(素材表)两张表是多对多关系
        通过wm_news_wm_material关系表相连
        */
        // 1. 从入参中,提取新闻表需要的内容,保存到wmNews对象
        WmNews wmNews = wmNewsDto2WmNews(wmNewsDto);
        if (wmNews == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE);
        }

        // 2. 把wmNews对象写入新闻表
        int insertResult = wmNewsMapper.insert(wmNews);
        if (insertResult < 1) {
            return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR);
        }

        // 3. 判断是不是草稿(是草稿->返回)
        Short status = wmNewsDto.getStatus();
        if (status == 0) {
            return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
        }

        // 4. 提取新闻内容中所有图片素材的URL
        //遍历一条一条的添加wm_material(素材表)和wm_news_wm_material关系表
        String content = wmNewsDto.getContent();
        List<String> contentImages = getContentImages(content);

        // 5.从所有图片素材的URL中提取到封面图--
        // 如果是自动布局,要到内容图中,去提取图片作为封面图
        // 如果type=0,无图布局
        // 如果type=1,单图布局
        // 如果type=3,多图
        // 如果type=-1,自动
        List<String> images = wmNewsDto.getImages();
        Short type = wmNewsDto.getType();
        List<String> coverImages = getCoverImages(type, images, contentImages);

        // 6. 素材表和新闻表的关系表添加记录,wm_news_material关系表中type=0代表是内容图片
        Boolean contentImageResult = addMaterial(wmNews.getId(), contentImages, 0);
        if (!contentImageResult) {
            throw new RuntimeException("内容图添加失败");
        }

        // 7. 素材表和新闻表的关系表添加记录,wm_news_material关系表中type=1代表是封面图片
        Boolean coverImageResult = addMaterial(wmNews.getId(), coverImages, 1);
        if (!coverImageResult) {
            throw new RuntimeException("封面图添加失败");
        }

        //8.新闻发布成功之后,调用审核代码进行新闻内容审核,转换成文章
//        try {
//            autoScanService.autoScanWmNews(wmNews.getId().longValue());
//        } catch (Exception e) {
//            log.warn("----------新闻内容审核失败--------");
//            e.printStackTrace();
//        }
        //8.往延时队列中发送1个任务,标明这个任务的执行时间
        Boolean isAddTask = addNewsToTask(wmNews.getId(), wmNews.getPublishTime().getTime());
        if (!isAddTask) {
            throw new RuntimeException("自媒体微服务远程调用schedule微服务添加任务失败");
        }
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }
    //发布新闻任务到Redis
    public Boolean addNewsToTask(Integer newsId, Long executeTime) {
        log.info("添加任务到延迟服务中----begin");
        Task task = new Task();
        task.setExecuteTime(executeTime);
        WmNews wmNews = new WmNews();
        wmNews.setId(newsId);
        task.setParameters(ProtostuffUtil.serialize(wmNews));
        ResponseResult responseResult = scheduleClien.addTask(task);
        if (responseResult != null && responseResult.getCode() == 200) {
            log.info("添加任务到延迟服务中成功----end");
            return true;
        }
        log.info("添加任务到延迟服务中失败----end");
        return false;

    }

从延迟队列中消费任务,再次调用autoScanWmNews进行内容审核;

 //发布任务到Redis的接口在WmNessService,从Redis队列中获取任务进行消费,调用autoScanWmNews进行内容审核;
    @Scheduled(fixedRate = 1000)
    public void getTask() {
        ResponseResult pollResult = scheduleClient.poll();
        //从Redis队列获取任务进行消费失败
        if (pollResult == null || pollResult.getCode() != 200) {
            log.warn("从Redis队列获取任务进行消费失败");
            return;
        }
        String data = (String) pollResult.getData();
        if (StringUtils.isEmpty(data)) {
            log.warn("从Redis队列中没有可以消费的任务");
            return;
        }
        Task task = JSON.parseObject(data, Task.class);
        if(task==null){
            log.warn("消费任务时序列化失败");
        }

        byte[] parameters = task.getParameters();
        WmNews wmNews= ProtostuffUtil.deserialize(parameters,WmNews.class);
        if(wmNews==null){
            log.warn("参数缺失");
            return;
        }
        try {
            autoScanWmNews(wmNews.getId().longValue());
        } catch (Exception e) {
            log.warn("Redis队列中获取Task调用autoScanWmNews进行新闻内容扫描失败");
            e.printStackTrace();
        }

    }

 

九、文章上/下架

 

 

 

用户行为

用户行为数据的记录包括了关注、点赞、不喜欢、收藏、阅读等行为

埋点是数据采集领域(尤其是用户行为数据采集领域)的术语,指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个icon点击次数、阅读文章的时长,观看视频的时长等等。

 

 

 

 

参考

posted on 2022-07-15 21:22  Martin8866  阅读(1165)  评论(0编辑  收藏  举报