heima头条学习笔记

一、knife4j接口文档管理

 见springcloud学习笔记里;

二、springboot整合freemarker入门案例

  freemarker即springmvc当中使用视图转发器返回的优化版本,具体的freemarker语法见springcloud学习笔记

1、导入依赖

   <dependencies>    

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

   下面的这个是freemarker核心注解
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- apache 对 java io 的封装工具库 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

2、yaml配置

server:
  port: 8881 #服务端口
spring:
  application:
    name: freemarker-demo #指定服务名
  freemarker:
    cache: false  #关闭模板缓存,方便测试
    settings:
      template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl               #指定Freemarker模板文件的后缀名,不指定的话就会是ftlh

    template-loader-path: classpath:/templates     #模板存放位置

3、在resources下创建templates,此目录为freemarker的默认模板存放目录。

 再里面创建第一个模板 01-basic.ftl 这里注意后缀不要写错

<!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>
</body>
</html>

 02-list.ftl 这个是用来测试基础语法

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>

<#-- list 数据的展示 -->
<b>展示list中的stu数据:</b>
<br>
<br>
<table>
    <tr>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#if stus??> <#--stus为空报错可以加上判断如下,因为为空就会报错,所以使用stus??可以让它为空时为空白-->
        <#list stus as stu>
            <tr>
                <td>0000</td>
                <td>${stu.name}</td>
                <td>${stu.age}</td>
                <td>${stu.money}</td>
            </tr>
        </#list>
    </#if>
    <#list stus as stu > <#--list遍历集合-->
        <#if stu.name='小红'>
            <tr style="color: red">
                <td>${stu_index+1}</td>
                <td>${stu.name}</td>
                <td>${stu.age}</td>
                <td>${stu.money}</td>
            </tr>
        <#else >
            <tr>
                <td>${stu_index+1}</td>
                <td>${stu.name}</td>
                <td>${stu.age}</td>
                <td>${stu.money}</td>
            </tr>
        </#if>
    </#list>
</table>
<hr>

<#-- Map 数据的展示 -->
<b>map数据的展示:</b>
<br/><br/>
<a href="###">方式一:通过map['keyname'].property</a><br/>
输出stu1的学生信息:<br/>
姓名:${stuMap['stu1'].name}<br/>
年龄:${stuMap['stu1'].age}<br/>
<br/>


<a href="###">方式二:通过map.keyname.property</a><br/>
输出stu2的学生信息:<br/>
姓名:${stuMap.stu2.name}<br/>
年龄:${stuMap.stu2.age}<br/>

<br/>
<a href="###">遍历map中两个学生信息:</a><br/>
<table>
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stuMap?keys as key>
        <tr>
            <td>${key_index +1}</td>
            <td>${stuMap[key].name}</td>
            <td>${stuMap[key].age}</td>
            <td>${stuMap[key].money}</td>
        </tr>
    </#list>
</table>
<hr>
当前的日期为:${today?datetime} <br>
自定义格式化:${today?string("yyyy年MM月")}

</body>
</html>

4、创建controller


@Controller
public class basicController {

    @GetMapping("/basic")
    public String basic(Model model){
        model.addAttribute("name","许石豪");
        Student stu = new Student();
        stu.setName("xushihao");
        stu.setAge(21);
        model.addAttribute("stu",stu);
        return "01-basic";
    }

    @GetMapping("/list")
    public String list(Model model){
        Student stu1 = new Student();
        stu1.setName("小强");
        stu1.setAge(18);
        stu1.setMoney(1000.86f);
        stu1.setBirthday(new Date());

        //小红对象模型数据
        Student stu2 = new Student();
        stu2.setName("小红");
        stu2.setMoney(200.1f);
        stu2.setAge(19);

        //将两个对象模型数据存放到List集合中
        List<Student> stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);

        //向model中存放List集合数据
        model.addAttribute("stus",stus);

        //创建Map数据
        HashMap<String,Student> stuMap = new HashMap<>();
        stuMap.put("stu1",stu1);
        stuMap.put("stu2",stu2);
        // 3.1 向model中存放Map数据
        model.addAttribute("stuMap", stuMap);

        //传递日期
        model.addAttribute("today",new Date());

        return "02-list";
    }

}

最后启动项目访问即可,需要注意的是,模板转发视图使用的是controller,而不是RestController

2、freemarker静态文件生成


@SpringBootTest(classes = FreemarkerApplication.class)
@RunWith(SpringRunner.class)
public class freemarkerTest {

    @Autowired
    private Configuration configuration;

    /*静态化页面测试*/
    @Test
    public void test() throws IOException, TemplateException {
        //freemarker的模板对象,获取模板
        Template template = configuration.getTemplate("02-list.ftl");
        Map params = getData();
        //合成
        //第一个参数 数据模型
        //第二个参数  输出流
        template.process(params, new FileWriter("d:/java/springCloud学习笔记/静态化页面/list.html"));
    }

    private Map getData() {
        Map<String, Object> map = new HashMap<>();
        Student stu1 = new Student();
        stu1.setName("小强");
        stu1.setAge(18);
        stu1.setMoney(1000.86f);
        stu1.setBirthday(new Date());

        //小红对象模型数据
        Student stu2 = new Student();
        stu2.setName("小红");
        stu2.setMoney(200.1f);
        stu2.setAge(19);

        //将两个对象模型数据存放到List集合中
        List<Student> stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);

        //向map中存放List集合数据
        map.put("stus", stus);


        //创建Map数据
        HashMap<String, Student> stuMap = new HashMap<>();
        stuMap.put("stu1", stu1);
        stuMap.put("stu2", stu2);
        //向map中存放Map数据
        map.put("stuMap", stuMap);
         //日期
        map.put("today",new Date());

        //返回Map
        return map;
    }
}

 三、MinIO

1、docker安装MinIO

  镜像拉取 docker pull minio/minio

2、拉取容器,

这里因为docker跌打快,所以直接拉取容器命令改变

运行容器:


docker run -d \
--name minio1 \
-p 9000:9000 \
-p 9001:9001 \
--privileged=true \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin123" \    用户名密码
-v /home/minio/data:/data \
-v /home/minio/config:/root/.minio \
minio/minio server \
--console-address ":9000" \             9000浏览器访问
--address ":9001" /data                    9001后端访问 

3、minio管理控制台 

 springboot下依赖文件

        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>7.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

将freemarker生成的静态文件上传文件到minio桶当中

     需要注意的是,我们minio设置的的后端访问端口为9001,所以我们这里获取链接信息使用的是9001端口,后面直接通过路径访问页面也是一样,需要使用9001端口

public class MinioTest { //创建测试类,上传html文件

    public static void main(String[] args) {

        try{
            //创建输入流,上传html文件
            FileInputStream fileInputStream = new FileInputStream("D:\\java\\springCloud学习笔记\\静态化页面\\list.html");

            //获取minio的链接信息  创建一个minio的客户端  用户名,密码,网址
            MinioClient minioClient = MinioClient.builder().credentials("admin", "admin123").endpoint("http://192.168.200.128:9001").build();

            //上传
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object("list.html") //文件名称
                    .contentType("text/html")//文件类型
                    .bucket("leadnews")//桶名称,minio图形界面创建的名称一致
                    .stream(fileInputStream,fileInputStream.available(),-1) //上传文件流
                    .build();
            minioClient.putObject(putObjectArgs);
            

            //访问路径
            System.out.println("http://192.168.200.128:9001/leadnews/list.html");

        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

4、封装minio的starter

   添加的minio配置文件就是我们minio的账号密码桶访问链接地址,和上传桶后访问的ip地址

5、新增文章详情的实现步骤(freemarker-minio)

 实现文章详情新增步骤 

 模拟新增文章详情,文章素材发布之后将文章详情静态页面生成出来路径存储到minio当中,下次才能直接访问!

@SpringBootTest(classes = ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired   //这个是静态文件生成所需的
    private Configuration configuration;

    //minio操作starter
    @Autowired
    private FileStorageService fileStorageService;


    /*
    * 模拟我们新增文章的时候生成我们的文章详情静态文章内容
    * 并将静态页面上传到minio当中
    * */
    @Test
    public void createStaticUrlTest() throws Exception {
        //1查询文章表,获取文章内容
        long apArticleId = 1383828014629179393L;
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId,apArticleId));

        //判断文章内容是否为空
        if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())){
            //2文章内容通过freemarker生成静态页面
            //2.1获取模板
            Template template = configuration.getTemplate("article.ftl");
            //2.2准备数据   存储数据因为我们模板当中遍历,这时候字符串不行,要转换为对象
            Map<String,Object> params = new HashMap<>();
            params.put("content", JSONArray.parseArray(apArticleContent.getContent()));

            //2.3输出流
            StringWriter out = new StringWriter();
            //2.4 合成,输出文件,
            template.process(params,out);

            //3把html文件上传到minio当中
            //3.1将静态页面转化为字节输入流
            InputStream is = new ByteArrayInputStream(out.toString().getBytes());

             //3.2上传到minio,调用上传命令,前缀,名称,静态页面,返回的就是存储到minio当中静态页面访问地址
            String path = fileStorageService.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", is);

            System.out.println("文件访问路径"+path);

            //修改ap_article当中文章详情的路径
            ApArticle apArticle = new ApArticle();
            apArticle.setId(apArticleContent.getArticleId());
            apArticle.setStaticUrl(path);
            articleMapper.updateById(apArticle);
        }


    }
}

四、图片上传(自媒体端,也就是网页端)

1、图片上传

第一步:  再网关过滤器里过滤网址的时候,将用户信息存入header当中

第二步:网关通过后会将请求带入微服务,我们配置微服务的拦截器,将过来的所有i请求拦截,提前header当中的用户信息,使用工具类存入当前线程当中(工具类包括存,取,清除)等。

第三步:业务代码,需要注意的是 MultipartFile对象里面封装的是前端提供的图片存储位置,

2、mybatis-puls分页查询

  比如查询图片素材

再serviceimpl里面的话直接调用page方法就是分页查询,然后创建的LambdaQueryWrapper对象里面可以添加其他查询条件。

3、文章管理(修改,保存)

 请求参数形式

五、文章审核

1、集成阿里云接口,文章内容审核和图片审核。

     (依赖是skd-code,sdk-green)

需要使用的时候直接指定aliyun的keyid和secret就可以调用接口使用,文本审核直接传入文章内容,图片审核则是传入list集合的字节数组,即图片的字节数组。

2、分布式id--雪花算法

   (分布式id--雪花算法是mybatis-plus集成的,使用方法也比较方便)

第一:在实体类中的id上加入如下配置,指定类型为id_worker

   @TableId(value = "id",type = IdType.ID_WORKER)
    private Long id;

第二:在application.yml文件中配置数据中心id和机器id

mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml  指定映射文件位置
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.heima.model.article.pojos
  global-config:
    datacenter-id: 1           datacenter-id:数据中心id(取值范围:0-31)
    workerId: 1                  workerId:机器id(取值范围:0-31)

第三:具体实现

   后面使用mybatispuls完成新增操作的时候自动生成的主键就是根据雪花算法而来

3、feign远程调用服务降级处理

 第一步::在fegin接口类重 编写降级逻辑(实现的是字节写的feign接口)

/**
 * feign失败配置
 * @author itheima
 */
@Component
public class IArticleClientFallback implements IArticleClient {
    @Override
    public ResponseResult saveArticle(ArticleDto dto)  {
        return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR,"获取数据失败");
    }
}

第二步::在feign接口中,将降级处理类添加进去(fallback指定)

@FeignClient(value = "leadnews-article",fallback = IArticleClientFallback.class) //fallback指定feign调用失败的处理
public interface IArticleClient {

    //文章保存或者修改 自媒体端保存后app端要将文章内容保存起来
    @PostMapping("/api/v1/article/save")
    public ResponseResult saveArticle(@RequestBody ArticleDto dto);

}

第三步::哪里需要调用feign接口,就在那个服务下配置

feign:
  # 开启feign对hystrix熔断降级的支持
  hystrix:
    enabled: true
  # 修改调用超时时间
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 2000

第四步:: 因为新的服务扫描不到我们的feign服务降级处理类,所以我们需要在调用者下创建一个配置文件,扫描降级服务处理类

@Configuration
@ComponentScan("com.heima.apis.article.fallback")
public class InitConfig {
}

4、springboot使用异步调用完成自动审核(异步调用)

5、自管理敏感词(这里使用dfa算法实现) 

        方案                                                              说明
数据库模糊查询效率太低
String.indexOf("")查找数据库量大的话也是比较慢
全文检索分词再匹配
DFA算法确定有穷自动机(一种数据结构)

  这个项目使用的是DFA算法,没有使用es分词器进行敏感词管理 (黑马头条中将DFA算法放在utils的SensitiveWordUtil中,以后遇到也可以直接拷贝使用)

                DFA算法的实现原理

6、图文识别(使用tess4j)

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

  这里我们使用的是tess4j。

第一步:: 导入依赖

<dependency>
    <groupId>net.sourceforge.tess4j</groupId>
    <artifactId>tess4j</artifactId>
    <version>4.1.1</version>
</dependency>

第二步: 创建Tesseract对象去完成图片文字识别

(DFA算法)

六、延迟队列消息(定时任务)

    本项目使用的是我们的redis的zset和list来完成,list存储立即完成的,而zset存储我们的定时完成的内容。

1、乐观锁

       给我们的sql任务表上锁,这里使用的是乐观锁

第一:;mybatis-plus支持数据库乐观锁的使用,所以在乐观锁哪里添加@Version注解

    

/**
* 版本号,用乐观锁
*/
@Version
private Integer version;

第二步::添加乐观锁的支持,添加乐观锁的拦截器

/**
     * mybatis-plus乐观锁支持
     * @return
     */
@Bean
public MybatisPlusInterceptor optimisticLockerInterceptor(){
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;

}

2、定时任务

 当zest当中的source小于当前时间的值,将zest中的集合转到list集合当中(这里设定的是每分钟执行一次)

第二步:在Scheduled服务当中添加@EnableScheduling 启动定时任务注解

3、redis分布式锁,

  redis的分布式锁是通过sexnx的特效来完成分布式锁的作用。(在CacheService 工具类下面有一个tryLock方法,就是完成加锁的方法,传递锁名称,过期时间,得到一个token,可以通过判断token是不是为空,来实现加锁功能) 

4、给文章发布添加延迟任务。

 总的来说就是文章发布后,将文章添加到延迟队列任务当中,每秒中到redis的list集合当中拉取一次任务,

七、kafka

特性ActiveMQRabbitMQRocketMQKafka
开发语言javaerlangjavascala
单机吞吐量万级万级10万级100万级
时效性msusmsms级以内
可用性高(主从)高(主从)非常高(分布式)非常高(分布式)
功能特性成熟的产品、较全的文档、各种协议支持好并发能力强、性能好、延迟低MQ功能比较完善,扩展性佳只支持主要的MQ功能,主要应用于大数据领域

消息中间件对比-选择建议

消息中间件建议
Kafka追求高吞吐量,适合产生大量数据的互联网服务的数据收集业务
RocketMQ可靠性要求很高的金融互联网领域,稳定性高,经历了多次阿里双11考验
RabbitMQ性能较好,社区活跃度高,数据量没有那么大,优先选择功能比较完备的RabbitMQ

produces消息的生产者,consumers消息的消费者,中间的kafka Cluster集群设置。

1、kafka的安装

    因为Kafka对于zookeeper是强依赖,保存kafka相关的节点数据,所以安装Kafka之前必须先安     装zookeeper

  • Docker安装zookeeper

下载镜像:

docker pull zookeeper:3.4.14

创建容器

docker run -d --name zookeeper -p 2181:2181 zookeeper:3.4.14
  • Docker安装kafka

下载镜像:

docker pull wurstmeister/kafka:2.12-2.3.1

创建容器

docker run -d --name kafka \
--env KAFKA_ADVERTISED_HOST_NAME=192.168.200.128 \  虚拟机服务器地址
--env KAFKA_ZOOKEEPER_CONNECT=192.168.200.128:2181 \   zookeeper连接地址
--env KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.200.128:9092 \   对外的监听
--env KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \
--env KAFKA_HEAP_OPTS="-Xmx256M -Xms256M" \
--net=host wurstmeister/kafka:2.12-2.3.1   使用--net=host直接使用容器宿主机的命名空间,使用宿主机的ip和端口

2、入门案例,使用kafka发送接收消息 

第一步:导入依赖

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
</dependency>

第二步::生产者

  这里注解,同步异步方法发送消息也在里面


/*生产者*/
public class ProducerQuickStart {

    /*kafka入门案例*/
    public static void main(String[] args) throws Exception {
        //1.kafka配置
        Properties prop = new Properties();
        //连接地址
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.200.128:9092");
        //ack配置  消息确认机制
        prop.put(ProducerConfig.ACKS_CONFIG,"1");
        //发送失败,重新连接次数
        prop.put(ProducerConfig.RETRIES_CONFIG,5);
        //指定key,和value的序列化器,都是用的kafka包下的StringSerializer序列化器
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");

        //创建kafka生产对象
        KafkaProducer<String,String> producer = new KafkaProducer<String,String>(prop);

        String str = "许石豪真帅";
        //封装需要发送的消息(指定topic,key,value)
        ProducerRecord<String, String> record = new ProducerRecord<String, String>("topic_first", "key_one", str);

        //发送消息
/*        1.普通发送,直接send
        producer.send(record);*/
       /* 2.同步发送
        RecordMetadata recordMetadata = producer.send(record).get();
        System.out.println("发送消息成功,偏移量::"+recordMetadata.offset());*/

        //3.异步发送
        producer.send(record, new Callback() {
            @Override
            public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                System.out.println("发送消息成功,偏移量::"+recordMetadata.offset());
                if(e!=null){
                    System.out.println("一般出现异常的话会打印异常信息到日志当中,方便后续处理");
                }
            }
        });

        //关闭通道,不关闭的化消息会发送不成功
        producer.close();
    }
}

 第三步:消费者

    注意:其中的定义组,单个组有多个消费者的话只会发送消息给一个消费者,多个组,每个组下还有一个消费者,则会将消息发送给每个消费者

/*消费者*/
public class ConsumerQuickStart {
    public static void main(String[] args) {

        Properties prop = new Properties();
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.200.128:9092");
        //定义组,同个消费者组的话只会有一个消费者接收到消息,不同组的话就是多个组都接收到消息(所以单个组一对一,多个组,一对多)
        prop.put(ConsumerConfig.GROUP_ID_CONFIG,"group2");
        //设置key和value的反序列化器
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");

        //创建消费者对象
        KafkaConsumer<String,String> kafkaConsumer = new KafkaConsumer<String,String>(prop);
        //订阅主题(topic)
        kafkaConsumer.subscribe(Collections.singletonList("topic_first"));

        //当前线程一直处于监听状态
        while (true){
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofMillis(1000));

            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                String key = consumerRecord.key();
                String value = consumerRecord.value();
                System.out.println(key+":::"+value);
            }
        }

    }
}

3、kafka的高可用设计 

4、参数详解

//ack配置  消息确认机制(all父节点和ISR节点还有普通节点都受到消息后,生产者才会收到消息发送成功的响应,一般我们都是使用的默认值)
prop.put(ProducerConfig.ACKS_CONFIG,"all");

确认机制说明
acks=0生产者在成功写入消息之前不会等待任何来自服务器的响应,消息有丢失的风险,但是速度最快
acks=1(默认值)只要集群首领节点收到消息,生产者就会收到一个来自服务器的成功响应
acks=all只有当所有参与赋值的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应

 retries(重试次数,当发送消息失败,我们尝试几次发送)

//重试次数
prop.put(ProducerConfig.RETRIES_CONFIG,10);

 //数据压缩
prop.put(ProducerConfig.COMPRESSION_TYPE_CONFIG,"lz4");

压缩算法说明
snappy占用较少的 CPU, 却能提供较好的性能和相当可观的压缩比, 如果看重性能和网络带宽,建议采用
lz4占用较少的 CPU, 压缩和解压缩速度较快,压缩比也很客观
gzip占用较多的 CPU,但会提供更高的压缩比,网络带宽有限,可以使用这种算法

4、手动提交偏移量

      自动提交错误::1.如果提交偏移量小于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息就会被重复处理。

                                  2.如果提交的偏移量大于客户端的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失。

   所以我们可以采用手动提交的方式,记录偏移量(在消费者创建对象的时候,将自动提交管不

/设置偏移量手动提交
prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,false);)

(1)同步手动提交,

缺点:当发送的消息过多时,发起提交调用时应用会阻塞

优点:处理完所有记录后调用commitSync提交偏移量,解决了偏移量可能错误

while (true){
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
    for (ConsumerRecord<String, String> record : records) {
        System.out.println(record.value());
        System.out.println(record.key());
        try {
            consumer.commitSync();//同步提交当前最新的偏移量
        }catch (CommitFailedException e){
            System.out.println("记录提交失败的异常:"+e);
        }

    }
}  //这里可以对照前面的消费者看,进行了那些变化,都是在消息处理当中进行提交

(2)异步手动提交

 可以很好的解决堵塞的现象

 while (true){
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
    for (ConsumerRecord<String, String> record : records) {
        System.out.println(record.value());
        System.out.println(record.key());
    }
    consumer.commitAsync(new OffsetCommitCallback() {
        @Override
        public void onComplete(Map<TopicPartition, OffsetAndMetadata> map, Exception e) {
            if(e!=null){
                System.out.println("记录错误的提交偏移量:"+ map+",异常信息"+e);
            }
        }
    });
}

(3)同步异步共同提交

  如果服务器返回提交失败,异步提交不会进行重试,所以我们才需要同步异步共同提交的方式

try {
    while (true){
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
        for (ConsumerRecord<String, String> record : records) {
            System.out.println(record.value());
            System.out.println(record.key());
        }
        consumer.commitAsync();  //异步
    }
}catch (Exception e){+
    e.printStackTrace();
    System.out.println("记录错误信息:"+e);
}finally {
    try {
        consumer.commitSync();  //同步
    }finally {
        consumer.close();
    }
}

 5、springboot集成kafka

第一步: 依赖配置

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- kafkfa -->
    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.apache.kafka</groupId>
                <artifactId>kafka-clients</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

第二步:yml配置

 spring:
    kafka:
      bootstrap-servers: 192.168.200.128:9092
      producer:   (retries重试次数,key-serializer 指定序列化器)
            retries: 5
            key-serializer: org.apache.kafka.common.serialization.StringSerializer
            value-serializer: org.apache.kafka.common.serialization.StringSerializer
      consumer:    (group-id:消费组的id,后面两个:反序列化器的指定)
            group-id: group-test
            key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
            value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

第三步:发送消息 


@RestController
public class HelloController {

    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;

    @GetMapping("/hello")
    public String hello(){
        kafkaTemplate.send("itcast-topic","许石豪真帅");
        return "ok";
    }
}

第四步:设置监听器接收消息

@Component
public class HelloListener {

    @KafkaListener(topics = "itcast-topic")
    public void onMessage(String message){
        if(!StringUtils.isEmpty(message)){
            System.out.println(message);
        }
    }
}

八、app端文章搜索

1、使用的是es搜索引擎来完成搜索案例的实现。

2、文章自动审核构建索引

    新增文章后需要增加es当中索引文档,所以文章审核完成,在生成静态页面详情上传minio当中时发送信息到kafka,在搜索微服务当中接收消息调用es,完成文档索引的构建。

3、app搜索记录

(1)springboot整合mongodb

 使用的是mongodb数据库,用户的搜索记录,需要给每一个用户都保存一份,数据量较大,要求加载速度快,通常这样的数据存储到mongodb更合适,不建议直接存储到关系型数据库中,因此选择mongodb

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

yml数据配置 

spring:
  data:
    mongodb:
      host: 192.168.200.130
      port: 27017
      database: leadnews-history

 3.。。pojo类(需要注意的是,@Document指定的是mongodb集合的名称,我们的主键id一般都是使用的string类型,因为会自动生成比较长的一串)

/**
 * <p>
 * 联想词表
 * </p>
 *
 * @author itheima
 */
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {

    private static final long serialVersionUID = 1L;

    private String id;

    /**
     * 联想词
     */
    private String associateWords;

    /**
     * 创建时间
     */
    private Date createdTime;

}

4.测试(注意,mongodb每次操作都要指名所使用的pojo类,因为其中有指定集合名称)

@SpringBootTest(classes = MongoApplication.class)
@RunWith(SpringRunner.class)
public class MongoTest {

    @Autowired
    private MongoTemplate mongoTemplate;


    @Test
    public void saveTest(){

        for (int i = 0; i < 10; i++) {
            ApAssociateWords apAssociateWords = new ApAssociateWords();
            apAssociateWords.setAssociateWords("许石豪");
            apAssociateWords.setCreatedTime(new Date());

            mongoTemplate.save(apAssociateWords);
        }

    }


    @Test
    public void selectTest(){
        ApAssociateWords byId = mongoTemplate.findById("654c74ee68e63d296a82926e", ApAssociateWords.class);
        System.out.println(byId);
    }


    //按条件查询
    @Test
    public void testQuery(){
        Query query =Query.query(Criteria.where("associateWords").is("许石豪"))
                .with(Sort.by(Sort.Direction.DESC,"createdTime"));
        List<ApAssociateWords> apAssociateWords = mongoTemplate.find(query, ApAssociateWords.class);
        System.out.println(apAssociateWords);
    }

    @Test
    public void deleteTest(){
        Query query = Query.query(Criteria.where("associateWords").is("许石豪"));
        mongoTemplate.remove(query, ApAssociateWords.class);
    }


}

(2)搜索记录的保存

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值