heima头条学习笔记

一、knife4j接口文档管理

 见springcloud学习笔记里;

二、springboot整合freemarker入门案例

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

heima头条学习笔记_第1张图片

1、导入依赖

       

       
            org.springframework.boot
            spring-boot-starter-web
       

   下面的这个是freemarker核心注解
       
            org.springframework.boot
            spring-boot-starter-freemarker
       

       
            org.springframework.boot
            spring-boot-starter-test
       

       
       
            org.projectlombok
            lombok
       

       
       
            org.apache.commons
            commons-io
            1.3.2
       

   

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 这里注意后缀不要写错




    
    Hello World!


普通文本 String 展示:

Hello ${name}

对象Student中的数据展示:
姓名:${stu.name}
年龄:${stu.age}

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

heima头条学习笔记_第2张图片




    
    Hello World!



<#-- list 数据的展示 -->
展示list中的stu数据:


<#if stus??> <#--stus为空报错可以加上判断如下,因为为空就会报错,所以使用stus??可以让它为空时为空白--> <#list stus as stu> <#list stus as stu > <#--list遍历集合--> <#if stu.name='小红'> <#else >
姓名 年龄 钱包
0000 ${stu.name} ${stu.age} ${stu.money}
${stu_index+1} ${stu.name} ${stu.age} ${stu.money}
${stu_index+1} ${stu.name} ${stu.age} ${stu.money}

<#-- Map 数据的展示 --> map数据的展示:

方式一:通过map['keyname'].property
输出stu1的学生信息:
姓名:${stuMap['stu1'].name}
年龄:${stuMap['stu1'].age}

方式二:通过map.keyname.property
输出stu2的学生信息:
姓名:${stuMap.stu2.name}
年龄:${stuMap.stu2.age}

遍历map中两个学生信息:
<#list stuMap?keys as key>
序号 姓名 年龄 钱包
${key_index +1} ${stuMap[key].name} ${stuMap[key].age} ${stuMap[key].money}

当前的日期为:${today?datetime}
自定义格式化:${today?string("yyyy年MM月")}

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 stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);

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

        //创建Map数据
        HashMap 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 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 stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);

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


        //创建Map数据
        HashMap 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下依赖文件

       
            io.minio
            minio
            7.1.0
       

       
            org.springframework.boot
            spring-boot-starter-web
       

       
            org.springframework.boot
            spring-boot-starter-test
       

将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地址

heima头条学习笔记_第3张图片

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

heima头条学习笔记_第4张图片

 实现文章详情新增步骤 

heima头条学习笔记_第5张图片

 模拟新增文章详情,文章素材发布之后将文章详情静态页面生成出来路径存储到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.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 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、图片上传

heima头条学习笔记_第6张图片

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

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

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

2、mybatis-puls分页查询

  比如查询图片素材

heima头条学习笔记_第7张图片

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

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

heima头条学习笔记_第8张图片

 请求参数形式

heima头条学习笔记_第9张图片

五、文章审核

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

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

heima头条学习笔记_第10张图片

heima头条学习笔记_第11张图片需要使用的时候直接指定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远程调用服务降级处理

heima头条学习笔记_第12张图片

 第一步::在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使用异步调用完成自动审核(异步调用)

heima头条学习笔记_第13张图片

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

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

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

                DFA算法的实现原理

heima头条学习笔记_第14张图片

6、图文识别(使用tess4j)

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

  这里我们使用的是tess4j。

第一步:: 导入依赖


    net.sourceforge.tess4j
    tess4j
    4.1.1

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

heima头条学习笔记_第15张图片(DFA算法)

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

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

heima头条学习笔记_第16张图片

1、乐观锁

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

heima头条学习笔记_第17张图片

第一:;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集合当中(这里设定的是每分钟执行一次)

heima头条学习笔记_第18张图片

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

3、redis分布式锁,

heima头条学习笔记_第19张图片

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

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

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

heima头条学习笔记_第20张图片

七、kafka

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

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

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

heima头条学习笔记_第21张图片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发送接收消息 

第一步:导入依赖


    org.apache.kafka
    kafka-clients

第二步::生产者

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


/*生产者*/
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 producer = new KafkaProducer(prop);

        String str = "许石豪真帅";
        //封装需要发送的消息(指定topic,key,value)
        ProducerRecord record = new ProducerRecord("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 kafkaConsumer = new KafkaConsumer(prop);
        //订阅主题(topic)
        kafkaConsumer.subscribe(Collections.singletonList("topic_first"));

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

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

    }
}

3、kafka的高可用设计 

heima头条学习笔记_第22张图片

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

(3)同步异步共同提交

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

try {
    while (true){
        ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));
        for (ConsumerRecord 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

第一步: 依赖配置


   
        org.springframework.boot
        spring-boot-starter-web
   

   
   
        org.springframework.kafka
        spring-kafka
       
           
                org.apache.kafka
                kafka-clients
           

       

   

   
        org.apache.kafka
        kafka-clients
   

   
        com.alibaba
        fastjson
   

第二步: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 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,完成文档索引的构建。

heima头条学习笔记_第23张图片

3、app搜索记录

(1)springboot整合mongodb

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

依赖


    org.springframework.boot
    spring-boot-starter-data-mongodb

yml数据配置 

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

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

/**
 * 

* 联想词表 *

* * @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 = 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)搜索记录的保存

heima头条学习笔记_第24张图片

你可能感兴趣的:(学习,笔记,1024程序员节)