Elasticsearch使用——结合MybatisPlus使用ES es和MySQL数据一致性 结合RabbitMQ实现解耦

news2025/7/9 14:16:13

在这里插入图片描述

前言

本篇博客是一篇elasticsearch的使用案例,包括结合MybatisPlus使用ES,如何保证MySQL和es的数据一致性,另外使用了RabbitMQ进行解耦,自定义了发消息的方法。

其他相关的Elasticsearch的文章列表如下:

  • Elasticsearch的Docker版本的安装和参数设置 & 端口开放和浏览器访问

  • Elasticsearch的可视化Kibana工具安装 & IK分词器的安装和使用

  • Elasticsearch的springboot整合 & Kibana进行全查询和模糊查询

目录

  • 前言
  • 引出
  • 结合MybatisPlus使用ES
    • 1.引入依赖
    • 2.进行配置
    • 3.实体类上加入注解
    • 4.创建操作的 Repository
    • 5.初始化es中的数据
    • 6.进行全查询以及分页
      • 带条件分页查询
  • es和mysql的数据一致性
    • 延迟双删
    • 加锁的方式
  • 用rabbitmq进行解耦
    • 配置yml文件
    • rabbitmq的配置类
    • callback回调方法
    • 自定义发消息工具类
    • 进行消息的发送
    • 接收到消息,更新es
  • 总结

引出


1.elasticsearch的使用案例,包括结合MybatisPlus使用ES;
2.如何保证MySQL和es的数据一致性;
3.使用了RabbitMQ进行解耦,自定义了发消息的方法。

结合MybatisPlus使用ES

1.引入依赖

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

        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--        druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>

        <!--  springboot 整合mybaits plus   -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

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

2.进行配置

package com.tianju.es.config;


import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;

/**
 * 你也可以不继承 AbstractElasticsearchConfiguration 类,而将 ESConfig 写成一般的配置类的型式。
 * 不过继承 AbstractElasticsearchConfiguration 好处在于,它已经帮我们配置好了elasticsearchTemplate 直接使用。
 */
@Configuration
public class ESConfig extends AbstractElasticsearchConfiguration {
    @Override
    public RestHighLevelClient elasticsearchClient() {
        ClientConfiguration clientConfiguration =
                ClientConfiguration.builder()
                        .connectedTo("192.168.111.130:9200")
                        .build();
        return RestClients.create(clientConfiguration).rest();
    }
}

3.实体类上加入注解

在这里插入图片描述

package com.tianju.es.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.math.BigDecimal;

/**
 * 产品,包括库存,价格信息
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("finance_sku")
@Document(indexName = "finance_sku")
public class FinanceSkuES {
    @TableId(value = "ID",type = IdType.AUTO)
    private Long id;
    @TableField("finance_sku_describe")
    @Field(index = true,analyzer = "ik_smart",
            searchAnalyzer = "ik_smart",type = FieldType.Text)
    private String detail; // 详情
    @TableField("finance_sku_price")
    private BigDecimal price;
    @TableField("finance_sku_stock")
    private Long stock;
    @TableField("finance_state")
    private Integer status;
}

参数解释

@Document(indexName = "books", shards = 1, replicas = 0)
@Data
public class Book {
    @Id
    @Field(type = FieldType.Integer)
    private Integer id;
    
    @Field(type = FieldType.Keyword)
    private String title;
    
    @Field(type = FieldType.Text)
    private String press;
    
    @Field(type = FieldType.Keyword)
    private String author;
    
    @Field(type = FieldType.Keyword,index=false)
    private BigDecimal price;
    
    @Field(type = FieldType.Text)
    private String description;
}
  • @Document :注解会对实体中的所有属性建立索引;
    indexName = “books” :表示创建一个名称为 “books” 的索引;
    shards = 1 : 表示只使用一个分片;
    replicas = 0 : 表示不使用复制备份;
    index = false: 不能索引查询
  • @Field(type = FieldType.Keyword) : 用以指定字段的数据类型。

4.创建操作的 Repository

在这里插入图片描述

从它的祖先们那里继承了大量的现成的方法,除此之外,它还可以按 spring data 的规则定义特定的方法。

在这里插入图片描述

package com.tianju.es.mapper;

import com.tianju.es.entity.FinanceSkuES;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

/**
 * 操作es,类似于之前的mapper
 */
@Repository
public interface SkuESMapper extends ElasticsearchRepository<FinanceSkuES, Long> {

    /**
     * 根据关键字进行 分词 分页查询 sku数据
     * @param detail 查询条件
     * @param pageable 分页
     * @return
     */
    Page<FinanceSkuES> findFinanceSkuESByDetail(String detail, Pageable pageable);

    /**
     * 根据id进行删除
     * @param id
     */
    void removeFinanceSkuESById(Long id);

}

5.初始化es中的数据

在这里插入图片描述

运行的后台信息

在这里插入图片描述

查看es页面的信息,index management

在这里插入图片描述

6.进行全查询以及分页

在这里插入图片描述

进行全查询

在这里插入图片描述

{
  "content": [
    {
      "id": 1,
      "detail": "HUAWEI MateBook X Pro 2023 微绒典藏版 13代酷睿i7 32GB 2TB 14.2英寸3.1K原色全面屏 墨蓝",
      "price": 13999.0,
      "stock": 50,
      "status": 1
    },
    {
      "id": 2,
      "detail": "HUAWEI Mate 60 Pro+ 16GB+1TB 宣白",
      "price": 9999.0,
      "stock": 60,
      "status": 1
    },
    {
      "id": 3,
      "detail": "iPhone 15 Pro Max 超视网膜 XDR 显示屏",
      "price": 9299.0,
      "stock": 46,
      "status": 1
    },
    {
      "id": 4,
      "detail": "MacBook Air Apple M2 芯片 8 核中央处理器 8 核图形处理器 8GB 统一内存 256GB 固态硬盘",
      "price": 8999.0,
      "stock": 60,
      "status": 1
    }
  ],
  "pageable": {
    "sort": {
      "empty": true,
      "sorted": false,
      "unsorted": true
    },
    "offset": 0,
    "pageSize": 4,
    "pageNumber": 0,
    "paged": true,
    "unpaged": false
  },
  "totalElements": 4,
  "last": true,
  "totalPages": 1,
  "number": 0,
  "size": 4,
  "sort": {
    "empty": true,
    "sorted": false,
    "unsorted": true
  },
  "numberOfElements": 4,
  "first": true,
  "empty": false
}

带条件分页查询

在这里插入图片描述

注意分页查询的page从0开始,尝试发现需要输入分词器分词后最小单元,比如hu不是最小单元,而HUAWEI是

在这里插入图片描述

分词器进行分词的结果

在这里插入图片描述

es和mysql的数据一致性

延迟双删

在这里插入图片描述

    @Override
    public FinanceSkuES updateByIddDoubleDelete(FinanceSkuES financeSkuES) {
        // 把es看做是缓存,如何保证es 和 mysql的 数据一致性?
        // 延迟双删的模式
        // 1.先删除缓存 es
        skuESMapper.deleteAll();
        // 2.更新数据库 mysql
        updateById(financeSkuES);
        // 3.延时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 4.再次删除缓存 es
        skuESMapper.deleteAll();

        // 5.最后更新缓存 es
        skuESMapper.saveAll(list());
        Optional<FinanceSkuES> byId = skuESMapper.findById(financeSkuES.getId());
        log.debug("byId: "+byId);
        return byId.get();
    }

上面代码有不妥的地方,我这里是修改,结果一开始直接从es中全部删除,应该是根据id把修改的数据删除,然后把修改好的数据set进es里面

加锁的方式

感觉好像没什么用的样子,就是用了一下加锁

在这里插入图片描述

用rabbitmq进行解耦

在这里插入图片描述

配置yml文件

在这里插入图片描述

spring:
  main:
    allow-circular-references: true
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    ### 本地的数据库
    url: jdbc:mysql://127.0.0.1:3306/consumer_finance_product?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowMultiQueries=true
    username: root
    password: 123

  # redis的相关配置
  redis:
    host: 119.3.162.127
    port: 6379
    database: 0
    password: Pet3927

  # rabbitmq相关
  rabbitmq:
    host: 192.168.111.130
    port: 5672
    username: admin
    password: 123
    virtual-host: /test

    # 生产者保证消息可靠性
    publisher-returns: true
    publisher-confirm-type: correlated

    # 设置手动确认
    listener:
      simple:
        acknowledge-mode: manual

rabbitmq的配置类

在这里插入图片描述

将Java对象转换成json字符串传输

在这里插入图片描述

package com.tianju.es.rabbit;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    public static final String ES_EXCHANGE = "es_exchange";
    public static final String ES_QUEUE = "es_queue";
    public static final String ES_KEY = "es_key";

    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange(ES_EXCHANGE);
    }

    @Bean
    public Queue esQueue(){
        return new Queue(ES_QUEUE);
    }

    @Bean
    public Binding esQueueToDirectExchange(){
        return BindingBuilder.bind(esQueue())
                .to(directExchange())
                .with(ES_KEY);
    }

    /**
     * 将对象转换为json字符串
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){
        return  new Jackson2JsonMessageConverter();
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(messageConverter());// 修改转换器
        return rabbitTemplate;
    }

}

在这里插入图片描述

callback回调方法

package com.tianju.es.rabbit;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * 生产者消息可靠性
 */
// RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback
@Configuration
@Slf4j
public class CallbackConfig
        implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 初始化
    @PostConstruct
    public void init(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
        rabbitTemplate.setMandatory(true);
    }

    /**
     * 不管成功或者失败都会执行
     * @param correlationData correlation对象需要在 发送消息时候 给
     * @param ack true表示成功,false表示发送失败
     * @param cause 如果失败的话,会写失败原因;如果成功,返回为null
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.debug("ack是否成功:"+ack);
        log.debug("cause信息:"+cause);

        if (correlationData!=null){
            JSONObject jsonObject = JSON.parseObject(correlationData.getReturnedMessage().getBody());
            String exchange = correlationData.getReturnedMessage().getMessageProperties().getReceivedExchange();
            String routingKey = correlationData.getReturnedMessage().getMessageProperties().getReceivedRoutingKey();
            log.debug("消息体:"+jsonObject);
            log.debug("交换机:"+exchange);
            log.debug("路由key:"+routingKey);
        }


        if (ack){
            return;
        }

        // 失败了

        // 1、重试重试上限次数(默认值5)每重试一次时间间隔会增加
        // 2、把消息、交换机名称、路由键等相关的消息保存到数据库,有一个程序定时扫描相关的消息,然后重新发送消息。
        // 重发上限次数(默认值5)超过阈值会转人工处理

        // 2、把消息体、交换机名称、路由键等相关的消息保存到数据库,有一个程序定时扫描相关的消息,然后重新发送消息。
        // 重发上限次数(默认值5)超过阈值会转人工处理
        // 2.1需要把相关的信息存放到数据中,表字段:消息体、交换机名称、路由键、状态、次数
        // 2.2定时任务(单体:spring定时任务  分布式:XxL-job),发送消息




    }


    /**
     * 只有失败了才会执行
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        // 2、把消息、交换机名称、路由键等相关的消息保存到数据库,有一个程序定时扫描相关的消息,然后重新发送消息。

    }
}

自定义发消息工具类

在这里插入图片描述

package com.tianju.common.util;


import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;


@Slf4j
public class RabbitUtil {

    /**
     * 延迟队列,发送消息,到达时间后进入死信队列中
     * @param rabbitTemplate 调用的rabbitTemplate
     * @param redisTemplate 用来在redis里面存token
     * @param msg 发送的消息
     * @param token 发送的token,用于保证幂等性
     * @param ttl 如果是延迟消费,则消息的过期时间,到达改时间后进入死信交换机,到死信队列中
     * @param exchange 交换机名字
     * @param routingKey 路由键名字
     * @param <T> 发送消息的实体类
     */
    public static  <T> void sendMsg(RabbitTemplate rabbitTemplate,
                                    StringRedisTemplate redisTemplate,
                                    T msg,String token,Integer ttl,
                                    String exchange,String routingKey) {

        log.debug("给交换机[{}]通过路由键[{}]发送消息 {},token为{}",exchange,routingKey,msg,token);

        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                redisTemplate.opsForValue().set(token, token,5*60000);
                message.getMessageProperties().setMessageId(token);
                if (ttl!=null){
                    message.getMessageProperties().setExpiration(ttl.toString());
                }
                return message;
            }
        };

        CorrelationData correlationData = new CorrelationData();
        // 消息体
        Message message = new Message(JSON.toJSONBytes(msg));
        // 交换机名称
        message.getMessageProperties().setReceivedExchange(exchange);
        // 路由键
        message.getMessageProperties().setReceivedRoutingKey(routingKey);
        correlationData.setReturnedMessage(message);

        // 发送MQ消息
        rabbitTemplate.convertAndSend(exchange, // 发给交换机
                routingKey, // 根据这个routingKey就会给到TTL队列,到时间成死信,发给死信交换机,到死信队列
                msg,
                messagePostProcessor,
                correlationData
        );
    }
}

进行消息的发送

在这里插入图片描述

接口

package com.tianju.es.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.tianju.es.entity.FinanceSkuES;

public interface SkuService extends IService<FinanceSkuES> {

    /**
     * 延迟双删的方式,保证es 缓存 和 mysql数据库的数据一致性
     * @param financeSkuES 修改的数据
     * @return
     */
    FinanceSkuES updateByIddDoubleDelete(FinanceSkuES financeSkuES);

    /**
     * 加锁的方式,不过感觉没啥用的样子
     * @param financeSkuES
     * @return
     */
    FinanceSkuES updateByIdRedisLock(FinanceSkuES financeSkuES);

    /**
     * 通过rabbitmq进行解耦
     * @param financeSkuES
     * @return
     */
    String updateByIdRabbitMQ(FinanceSkuES financeSkuES);
}

实现类

package com.tianju.es.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianju.common.util.RabbitUtil;
import com.tianju.es.entity.FinanceSkuES;
import com.tianju.es.mapper.SkuESMapper;
import com.tianju.es.mapper.SkuMybatisPlusMapper;
import com.tianju.es.rabbit.RabbitConfig;
import com.tianju.es.service.SkuService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Optional;
import java.util.UUID;

@Service
public class SkuServiceImpl extends ServiceImpl<SkuMybatisPlusMapper,FinanceSkuES>
        implements SkuService {

    @Autowired
    private SkuESMapper skuESMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public FinanceSkuES updateByIddDoubleDelete(FinanceSkuES financeSkuES) {
        // 把es看做是缓存,如何保证es 和 mysql的 数据一致性?
        // 延迟双删的模式
        // 1.先删除缓存 es
        skuESMapper.deleteAll();
        // 2.更新数据库 mysql
        updateById(financeSkuES);
        // 3.延时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        // 4.再次删除缓存 es
        skuESMapper.deleteAll();

        // 5.最后更新缓存 es
        skuESMapper.saveAll(list());
        Optional<FinanceSkuES> byId = skuESMapper.findById(financeSkuES.getId());
        log.debug("byId: "+byId);
        return byId.get();
    }

    @Override
    public FinanceSkuES updateByIdRedisLock(FinanceSkuES financeSkuES) {
        // 第二种方式加锁
        String uuid = UUID.randomUUID().toString();
        // 相当于setnx指令
        Boolean skuLock = stringRedisTemplate.opsForValue().setIfAbsent("skuLock", uuid);
        try {
            if (skuLock){ // 抢到了锁
                skuESMapper.deleteAll();
                updateById(financeSkuES);
            }
        }finally {
            if (uuid.equals(stringRedisTemplate.opsForValue().get("skuLock"))){
                stringRedisTemplate.delete("skuLock");
            }
        }
        skuESMapper.saveAll(list());
        Optional<FinanceSkuES> byId = skuESMapper.findById(financeSkuES.getId());
        log.debug("byId: "+byId);
        return byId.get();
    }

    @Override
    public String updateByIdRabbitMQ(FinanceSkuES financeSkuES) {
        // 采用rabbitmq进行解耦

        updateById(financeSkuES);
        FinanceSkuES skuES = getById(financeSkuES.getId());
        String uuid = IdUtil.fastUUID();

        RabbitUtil.sendMsg(
                rabbitTemplate,stringRedisTemplate,skuES,uuid,null,
                RabbitConfig.ES_EXCHANGE,RabbitConfig.ES_KEY
        );

        return "已经发送消息:"+skuES;
    }
}

在这里插入图片描述

接收到消息,更新es

接收到消息进行es的更新,把原来的删除,把最新的set进去

在这里插入图片描述

package com.tianju.es.rabbit;

import com.rabbitmq.client.Channel;
import com.tianju.es.entity.FinanceSkuES;
import com.tianju.es.mapper.SkuESMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@Component
public class ESListener {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private SkuESMapper skuESMapper;

    @RabbitListener(queues = RabbitConfig.ES_QUEUE)
    public void esUpdate(FinanceSkuES financeSkuES, Message message, Channel channel) {
        String messageId = message.getMessageProperties().getMessageId();
        log.debug("进行业务----> 监听到队列{}的消息,messageId为{}",financeSkuES,messageId);

        try {
            // 幂等性
            if (redisTemplate.delete(messageId)){
                // 根据id删除原有的 es 数据
                // 然后把新的数据set进来
                log.debug("处理es的业务,删除原有的,替换最新的");
                skuESMapper.removeFinanceSkuESById(financeSkuES.getId());
                skuESMapper.save(financeSkuES);
            }

            // 手动签收消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

        }catch (Exception e){
            // 幂等性
            redisTemplate.opsForValue().set(messageId,messageId,5*60000);

            // 1、重试重试上限次数(默认值5) 每重试一次时间间隔会增加
            // 2、把消息、交换机名称、路由键等相关的消息保存到数据库,有一个程序定时扫描相关的消息,然后重新发送消息。
            // 重发上限次数(默认值5)超过阈值会转人工处理

            // 已知的消息,交换机,路由器,消息 message.getBody()  消息发送给的是监听的队列

            try {
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
}

在这里插入图片描述

后台打印的日志

在这里插入图片描述


总结

1.elasticsearch的使用案例,包括结合MybatisPlus使用ES;
2.如何保证MySQL和es的数据一致性;
3.使用了RabbitMQ进行解耦,自定义了发消息的方法。

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

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

相关文章

ES6 Symbol 数据结构

1. Symbol概念以及引入原因 ES6 引入了的一种新的原始数据类型Symbol&#xff0c;表示独一无二的值。它是 JavaScript 语言的第七种数据类型&#xff0c;前六种是&#xff1a;undefined、null、布尔值&#xff08;Boolean&#xff09;、字符串&#xff08;String&#xff09;、…

政务OA协同办公系统:数字化工具助力政府服务升级

在当今数字化时代&#xff0c;政务OA协同办公系统已经成为政府服务升级的重要工具。通过引入数字化技术&#xff0c;政务OA协同办公系统可以实现高效的信息传递、流程优化以及资源整合&#xff0c;进一步提升政府服务的质量和效率。本文将探讨政务OA协同办公系统的优势及其在数…

【UE5 Cesium】19-Cesium for Unreal 建立飞行跟踪器(4)

遗留问题 在上一篇博客中&#xff08;【UE5 Cesium】18-Cesium for Unreal 建立飞行跟踪器&#xff08;3&#xff09;&#xff09;&#xff0c;我们实现了飞机变速飞行的功能&#xff0c;但是还存在两个问题&#xff0c;分别是&#xff1a; &#xff08;1&#xff09;由于UE的…

三维地图开发三维地图服务器

三维地图开发三维地图服务器 发布时间&#xff1a;2020-03-03 版权&#xff1a; 搭建离线地图服务主要是两个步骤&#xff1a;一是&#xff1a;下载离线地图服务需要的地图数据&#xff1b;二是&#xff1a;将下载的离线地图数据发布成地图服务&#xff1b;只有做好这两步&…

【java零基础入门到就业】第二天:jdk的下载安装和第一个HelloWorld程序

1、java内容概述 java前半部分学习内容主要如下&#xff1a; 1、Java基础语法2、面向对象3、API4、字符串5、集合6、拼图游戏 1.1、 java基础语法 java基础语法主要包括以下内容&#xff1a; Java入门小概念Idea和运算符判断和循环方法数组课后练习题 Java是什么&#xf…

Qt基础 QPieSeries饼状图

目录 1.简单例子 2. 稍微复杂点 QPieSeries Class&#xff1a;饼状图数据 QChart 管理图表系列、图例和轴的图形表示 QChartView 可以显示图表的独立小部件 QPieSeries 在饼图中显示数据 QPieSlice 表示饼图系列中的单个切片&#xff08;其实就是标签&#xff09; 1…

鸿蒙应用开发之HTTP数据请求

一、概述 日常生活中我们使用应用程序看新闻、发送消息等&#xff0c;都需要连接到互联网&#xff0c;从服务端获取数据。例如&#xff0c;新闻应用可以从新闻服务器中获取最新的热点新闻&#xff0c;从而给用户打造更加丰富、更加实用的体验。 那么要实现这样一种能实时从服务…

Python突破浏览器TLS/JA3 指纹

JA3 是一种创建 SSL/TLS 客户端指纹的方法&#xff0c;一般一个网站的证书是不变的&#xff0c;所以浏览器指纹也是稳定的&#xff0c;能区分不同的客户端。 requests库 Python requests库请求一个带JA3指纹网站的结果&#xff1a; import requestsheaders {authority: tls…

P2251 质量检测

题目&#xff1a; P2251 质量检测 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 代码&#xff1a; #include<iostream> #include<cstdio> #include<deque> #include<vector> typedef long long ll; const ll N 1e7; using namespace std;int main…

nocos配置中心使用教程(NACOS 1.X版本)

1.下载和安装 进入到官网下载就好了 解压 启动 2.新建cloudalibaba-config-nacos-client3377 2.1 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://w…

【LeetCode刷题(数据结构与算法)】:链表中的两数相加

给你两个非空的链表&#xff0c;表示两个非负的整数 它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储一位数字 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 开头 示例1&…

数据库如何储存和管理数据的?

文章目录 磁盘IO数据的存储特别的 前言:众所周知,数据库就是一个将各类数据,以表格的形式存储的,但是看似如此简单的功能它是真的简单吗?我们和直接使用简单的Excel建立的表格有区别吗?如果有在哪里? PS&#xff1a;本文以常用的MySQL为例 磁盘IO 在不考虑缓存等机制&#…

【Tent-SSA-BP】基于Tent混沌映射改进的麻雀算法优化BP神经网络回归预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

站内全文搜索,怎样能更好找到自己想要的信息?

对于博客或者文档&#xff0c;站内全文搜索是非常重要的功能。现在有许多网站生成器工具可供选择&#xff0c;无论是静态的、带有完整后台的&#xff0c;还是基于低代码或无代码的。但是如今网站往往拥有大量的内容和信息。为了能够快速有效找到所需的信息&#xff0c;无论是通…

黑马JVM总结(三十七)

&#xff08;1&#xff09;synchronized-轻量级锁-无竞争 &#xff08;2&#xff09;synchronized-轻量级锁-锁膨胀 重量级锁就是我们前面介绍过的Monitor enter &#xff08;3&#xff09;synchronized-重量级锁-自旋 &#xff08;4&#xff09;synchronized-偏向锁 轻量级锁…

基于ssm+vue的线上点餐系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

ACU-01B 3HNA024871-001/03 机器人将如何改变世界

ACU-01B 3HNA024871-001/03 机器人将如何改变世界 由于改进的传感器技术以及机器学习和人工智能方面更显著的进步&#xff0c;机器人将继续从单纯的机械机器转变为具有认知功能的合作者。这些进步&#xff0c;以及其他相关领域&#xff0c;正在享受一个上升的轨迹&#xff0c;…

分类选择,最多五级

效果图&#xff0c;这种竖向的分类选择&#xff0c;每一列可以用不同的背景颜色 组件代码 <template><view class"toolTypeBox" :style"max-height:${maxHeight}"><block v-for"(item,index) in datalist"><block v-if&…

hanniman 1v1 咨询

‍ 一共4种可选方案&#xff0c;3个To C&#xff08;面向AI产品经理的职业规划诊断、求职内推套餐、模拟面试&#xff09;&#xff0c;1个To B&#xff08;面向AI企业/投资机构/券商等&#xff09;。 方案A&#xff1a;职业规划诊断 适合人群&#xff1a;AI产品经理 or 想转型A…

深入理解强化学习——学习(Learning)、规划(Planning)、探索(Exploration)和利用(Exploitation)

分类目录&#xff1a;《深入理解强化学习》总目录 学习 学习&#xff08;Learning&#xff09;和规划&#xff08;Planning&#xff09;是序列决策的两个基本问题。 如下图所示&#xff0c;在强化学习中&#xff0c;环境初始时是未知的&#xff0c;智能体不知道环境如何工作&a…