SpringBoot 整合 Avro 与 Kafka

优质博文:IT-BLOG-CN

【需求】:生产者发送数据至 kafka 序列化使用 Avro,消费者通过 Avro 进行反序列化,并将数据通过 MyBatisPlus 存入数据库。

一、环境介绍

【1】Apache Avro 1.8;【2】Spring Kafka 1.2;【3】Spring Boot 1.5;【4】Maven 3.5;

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.codenotfound</groupId>
  <artifactId>spring-kafka-avro</artifactId>
  <version>0.0.1-SNAPSHOT</version>
 
  <name>spring-kafka-avro</name>
  <description>Spring Kafka - Apache Avro Serializer Deserializer Example</description>
  <url>https://www.codenotfound.com/spring-kafka-apache-avro-serializer-deserializer-example.html</url>
 
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.4.RELEASE</version>
  </parent>
 
  <properties>
    <java.version>1.8</java.version>
 
    <spring-kafka.version>1.2.2.RELEASE</spring-kafka.version>
    <avro.version>1.8.2</avro.version>
  </properties>
 
  <dependencies>
    <!-- spring-boot -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- spring-kafka -->
    <dependency>
      <groupId>org.springframework.kafka</groupId>
      <artifactId>spring-kafka</artifactId>
      <version>${spring-kafka.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.kafka</groupId>
      <artifactId>spring-kafka-test</artifactId>
      <version>${spring-kafka.version}</version>
      <scope>test</scope>
    </dependency>
    <!-- avro -->
    <dependency>
      <groupId>org.apache.avro</groupId>
      <artifactId>avro</artifactId>
      <version>${avro.version}</version>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <!-- spring-boot-maven-plugin -->
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!-- avro-maven-plugin -->
      <plugin>
        <groupId>org.apache.avro</groupId>
        <artifactId>avro-maven-plugin</artifactId>
        <version>${avro.version}</version>
        <executions>
          <execution>
            <phase>generate-sources</phase>
            <goals>
              <goal>schema</goal>
            </goals>
            <configuration>
              <sourceDirectory>${project.basedir}/src/main/resources/avro/</sourceDirectory>
              <outputDirectory>${project.build.directory}/generated/avro</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

二、Avro 文件

【1】Avro 依赖于由使用JSON定义的原始类型组成的架构。对于此示例,我们将使用Apache Avro入门指南中的“用户”模式,如下所示。该模式存储在src / main / resources / avro下的 user.avsc文件中。我这里使用的是 electronicsPackage.avsc。namespace 指定你生成 java 类时指定的 package 路径,name 表时生成的文件。

{"namespace": "com.yd.cyber.protocol.avro",
 "type": "record",
 "name": "ElectronicsPackage",
 "fields": [
     {"name":"package_number","type":["string","null"],"default": null},
     {"name":"frs_site_code","type":["string","null"],"default": null},
     {"name":"frs_site_code_type","type":["string","null"],"default":null},
     {"name":"end_allocate_code","type":["string","null"],"default": null},
     {"name":"code_1","type":["string","null"],"default": null},
     {"name":"aggregat_package_code","type":["string","null"],"default": null}
    ]
}

【2】Avro附带了代码生成功能,该代码生成功能使我们可以根据上面定义的“用户”模式自动创建Java类。一旦生成了相关的类,就无需直接在程序中使用架构。这些类可以使用 avro-tools.jar 或项目是Maven 项目,调用 Maven Projects 进行 compile 自动生成 electronicsPackage.java 文件:如下是通过 maven 的方式

【3】这将导致生成一个 electronicsPackage.java 类,该类包含架构和许多 Builder构造 electronicsPackage对象的方法。

三、为 Kafka 主题生成 Avro消息

Kafka Byte 在其主题中存储和传输数组。但是,当我们使用 Avro对象时,我们需要在这些 Byte数组之间进行转换。在0.9.0.0版之前,Kafka Java API使用 Encoder/ Decoder接口的实现来处理转换,但是在新API中,这些已经被 Serializer/ Deserializer接口实现代替。Kafka附带了许多 内置(反)序列化器,但不包括Avro。为了解决这个问题,我们将创建一个 AvroSerializer类,该类Serializer专门为 Avro对象实现接口。然后,我们实现将 serialize() 主题名称和数据对象作为输入的方法,在本例中,该对象是扩展的 Avro对象 SpecificRecordBase。该方法将Avro对象序列化为字节数组并返回结果。这个类属于通用类,一次配置多次使用。

package com.yd.cyber.web.avro;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
 
import org.apache.avro.io.BinaryEncoder;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.EncoderFactory;
import org.apache.avro.specific.SpecificDatumWriter;
import org.apache.avro.specific.SpecificRecordBase;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Serializer;
 
/**
 *  avro序列化类
 * @author zzx
 * @creat 2020-03-11-19:17
 */
public class AvroSerializer<T extends SpecificRecordBase> implements Serializer<T> {
    @Override
    public void close() {}
 
    @Override
    public void configure(Map<String, ?> arg0, boolean arg1) {}
 
    @Override
    public byte[] serialize(String topic, T data) {
        if(data == null) {
            return null;
        }
        DatumWriter<T> writer = new SpecificDatumWriter<>(data.getSchema());
        ByteArrayOutputStream byteArrayOutputStream  = new ByteArrayOutputStream();
        BinaryEncoder binaryEncoder  = EncoderFactory.get().directBinaryEncoder(byteArrayOutputStream , null);
        try {
            writer.write(data, binaryEncoder);
            binaryEncoder.flush();
            byteArrayOutputStream.close();
        }catch (IOException e) {
            throw new SerializationException(e.getMessage());
        }
        return byteArrayOutputStream.toByteArray();
    }
}

四、AvroConfig 配置类

Avro 配置信息在 AvroConfig 配置类中,现在,我们需要更改,AvroConfig 开始使用我们的自定义 Serializer实现。这是通过将“ VALUE_SERIALIZER_CLASS_CONFIG”属性设置为 AvroSerializer该类来完成的。此外,我们更改了ProducerFactory 和KafkaTemplate 通用类型,使其指定 ElectronicsPackage 而不是 String。当我们有多个序列化的时候,这个配置文件需要多次需求,添加自己需要序列化的对象。

package com.yd.cyber.web.avro;
 
/**
 * @author zzx
 * @creat 2020-03-11-20:23
 */
@Configuration
@EnableKafka
public class AvroConfig {
 
    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;
 
    @Value("${spring.kafka.producer.max-request-size}")
    private String maxRequestSize;
 
    @Bean
    public Map<String, Object> avroProducerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ProducerConfig.MAX_REQUEST_SIZE_CONFIG, maxRequestSize);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, AvroSerializer.class);
        return props;
    }
 
    @Bean
    public ProducerFactory<String, ElectronicsPackage> elProducerFactory() {
        return new DefaultKafkaProducerFactory<>(avroProducerConfigs());
    }
 
    @Bean
    public KafkaTemplate<String, ElectronicsPackage> elKafkaTemplate() {
        return new KafkaTemplate<>(elProducerFactory());
    }
}

五、通过 kafkaTemplate 发送消息

最后就是通过 Controller类调用 kafkaTemplate 的 send 方法接受一个Avro electronicsPackage对象作为输入。请注意,我们还更新了 kafkaTemplate 泛型类型。

package com.yd.cyber.web.controller.aggregation;
 
import com.yd.cyber.protocol.avro.ElectronicsPackage;
import com.yd.cyber.web.vo.ElectronicsPackageVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
 
/**
 * <p>
 * InnoDB free: 4096 kB 前端控制器
 * </p>
 *
 * @author zzx
 * @since 2020-04-19
 */
@RestController
@RequestMapping("/electronicsPackageTbl")
public class ElectronicsPackageController {
 
    //日誌
    private static final Logger log = LoggerFactory.getLogger(ElectronicsPackageController.class);
 
    @Resource
    private KafkaTemplate<String,ElectronicsPackage> kafkaTemplate;
 
    @GetMapping("/push")
    public void push(){
        ElectronicsPackageVO electronicsPackageVO = new ElectronicsPackageVO();
        electronicsPackageVO.setElectId(9);
        electronicsPackageVO.setAggregatPackageCode("9");
        electronicsPackageVO.setCode1("9");
        electronicsPackageVO.setEndAllocateCode("9");
        electronicsPackageVO.setFrsSiteCodeType("9");
        electronicsPackageVO.setFrsSiteCode("9");
        electronicsPackageVO.setPackageNumber("9");
        ElectronicsPackage electronicsPackage = new ElectronicsPackage();
        BeanUtils.copyProperties(electronicsPackageVO,electronicsPackage);
        //发送消息
        kafkaTemplate.send("Electronics_Package",electronicsPackage);
        log.info("Electronics_Package TOPIC 发送成功");
    }
}

六、从 Kafka主题消费 Avro消息反序列化

收到的消息需要反序列化为 Avro格式。为此,我们创建一个 AvroDeserializer 实现该 Deserializer接口的类。该 deserialize()方法将主题名称和Byte数组作为输入,然后将其解码回Avro对象。从 targetType类参数中检索需要用于解码的模式,该类参数需要作为参数传递给 AvroDeserializer构造函数。

package com.yd.cyber.web.avro;
 
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
 
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.BinaryDecoder;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.specific.SpecificRecordBase;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Deserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import javax.xml.bind.DatatypeConverter;
 
/**
 *  avro反序列化
 * @author fuyx
 * @creat 2020-03-12-15:19
 */
public class AvroDeserializer<T extends SpecificRecordBase> implements Deserializer<T> {
    //日志系统
    private static final Logger LOGGER = LoggerFactory.getLogger(AvroDeserializer.class);
 
    protected final Class<T> targetType;
 
    public AvroDeserializer(Class<T> targetType) {
        this.targetType = targetType;
    }
    @Override
    public void close() {}
 
    @Override
    public void configure(Map<String, ?> arg0, boolean arg1) {}
 
    @Override
    public T deserialize(String topic, byte[] data) {
        try {
            T result = null;
            if(data == null) {
                return null;
            }
            LOGGER.debug("data='{}'", DatatypeConverter.printHexBinary(data));
            ByteArrayInputStream in = new ByteArrayInputStream(data);
            DatumReader<GenericRecord> userDatumReader = new SpecificDatumReader<>(targetType.newInstance().getSchema());
            BinaryDecoder decoder = DecoderFactory.get().directBinaryDecoder(in, null);
            result = (T) userDatumReader.read(null, decoder);
            LOGGER.debug("deserialized data='{}'", result);
            return result;
        } catch (Exception ex) {
            throw new SerializationException(
                    "Can't deserialize data '" + Arrays.toString(data) + "' from topic '" + topic + "'", ex);
        } finally {
 
        }
    }
}

七、反序列化的配置类

我将反序列化的配置和序列化的配置都放置在 AvroConfig 配置类中。在 AvroConfig 需要被这样更新了AvroDeserializer用作值“VALUE_DESERIALIZER_CLASS_CONFIG”属性。我们还更改了 ConsumerFactory 和 ConcurrentKafkaListenerContainerFactory通用类型,以使其指定 ElectronicsPackage 而不是 String。将 DefaultKafkaConsumerFactory 通过1个新的创造 AvroDeserializer 是需要 “User.class”作为构造函数的参数。需要使用Class<?> targetType,AvroDeserializer 以将消费 byte[]对象反序列化为适当的目标对象(在此示例中为 ElectronicsPackage 类)。

@Configuration
@EnableKafka
public class AvroConfig {
 
    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;
 
    @Value("${spring.kafka.producer.max-request-size}")
    private String maxRequestSize;
 
 
    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, AvroDeserializer.class);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "avro");
 
        return props;
    }
 
    @Bean
    public ConsumerFactory<String, ElectronicsPackage> consumerFactory() {
        return new DefaultKafkaConsumerFactory<>(consumerConfigs(), new StringDeserializer(),
                new AvroDeserializer<>(ElectronicsPackage.class));
    }
 
    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, ElectronicsPackage> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<String, ElectronicsPackage> factory =
                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
 
        return factory;
    }
 
}

八、消费者消费消息

消费者通过 @KafkaListener 监听对应的 Topic ,这里需要注意的是,网上直接获取对象的参数传的是对象,比如这里可能需要传入 ElectronicsPackage 类,但是我这样写的时候,error日志总说是返回序列化的问题,所以我使用 GenericRecord 对象接收,也就是我反序列化中定义的对象,是没有问题的。然后我将接收到的消息通过 mybatisplus 存入到数据库。

package com.zzx.cyber.web.controller.dataSource.intercompany;
 
import com.zzx.cyber.web.service.ElectronicsPackageService;
import com.zzx.cyber.web.vo.ElectronicsPackageVO;
import org.apache.avro.generic.GenericRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Controller;
 
import javax.annotation.Resource;
 
/**
 * @desc:
 * @author: zzx
 * @creatdate 2020/4/1912:21
 */
@Controller
public class ElectronicsPackageConsumerController {
 
    //日志
    private static final Logger log  = LoggerFactory.getLogger(ElectronicsPackageConsumerController.class);
 
    //服务层
    @Resource
    private ElectronicsPackageService electronicsPackageService;
    /**
     * 扫描数据测试
     * @param genericRecordne
     */
    @KafkaListener(topics = {"Electronics_Package"})
    public void receive(GenericRecord genericRecordne) throws Exception {
        log.info("数据接收:electronicsPackage + "+  genericRecordne.toString());
        //业务处理类,mybatispuls 自动生成的类
        ElectronicsPackageVO electronicsPackageVO = new ElectronicsPackageVO();
        //将收的数据复制过来
        BeanUtils.copyProperties(genericRecordne,electronicsPackageVO);
        try {
            //落库
            log.info("数据入库");
            electronicsPackageService.save(electronicsPackageVO);
        } catch (Exception e) {
            throw new Exception("插入异常"+e);
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Win10+Ubuntu20.04双系统重装Ubuntu22.04单系统

从去年 8 月美化 Ubuntu 系统后一直存在内核错误问题&#xff0c;但因为大部分功能还能正常使用&#xff0c;只是在 apt 时报错&#xff0c;所以一直逃避不想重装&#xff0c;直到最近 12 月新的开始&#xff0c;恰好设置的界面打不开得重装 gnome &#xff0c;所以下定决心重装…

Linux:进程间通信之system V

一、共享内存 进程间通信的本质是让不同的进程看到同一份代码。 1.1 原理 第一步&#xff1a;申请公共内存 为了让不同的进程看到同一份资源&#xff0c;首先我们需要由操作系统为我们提供一个公共的内存块。 第二步&#xff1a;挂接到要通信进程的地址空间中 &#xff…

Vue进阶之单组件开发与组件通信

书接上篇&#xff0c;我们了解了如何快速创建一个脚手架&#xff0c;现在我们来学习如何基于vite创建属于自己的脚手架。在创建一个新的组件时&#xff0c;要在新建文件夹中打开终端创建一个基本的脚手架&#xff0c;可在脚手架中原有的文件中修改或在相应路径重新创建&#xf…

【Linux网络编程】第四弹---构建UDP服务器与字典翻译系统:源码结构与关键组件解析

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】 目录 1、UdpServer.hpp 1.1、函数对象声明 1.2、Server类基本结构 1.3、构造函数 1.4、Start() 2、Dict.hpp…

数字IC后端设计实现之分段长clock tree经典案例

最近发现很多读者问到分段长clock tree的做法&#xff0c;小编今天给大家分享几个SoC芯片中复杂时钟结构设计的分段长clock tree的应用案例。希望对各位的学习和工作有所助益。 数字后端设计实现之时钟树综合实践篇 数字IC后端实现专家都具备哪些技能&#xff1f;&#xff08…

计算机毕业设计Spark+SpringBoot旅游推荐系统 旅游景点推荐 旅游可视化 旅游爬虫 景区客流量预测 旅游大数据 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

EasyMedia播放rtsprtmp视频流

学习链接 MisterZhang/EasyMedia - gitee地址 EasyMedia转码rtsp视频流flv格式&#xff0c;hls格式&#xff0c;H5页面播放flv流视频 文章目录 学习链接介绍步骤easydarwin启动rtsp服务&#xff0c;ffmpeg推送摄像头&#xff08;模拟rtsp视频流&#xff09;nginx添加rtmp支持…

【Linux】开启你的Linux之旅:初学者指令指南

Linux相关知识点可以通过点击以下链接进行学习一起加油&#xff01; 在 Linux 开发中&#xff0c;GDB 调试器和 Git 版本控制工具是开发者必备的利器。GDB 帮助快速定位代码问题&#xff0c;Git 则提供高效的版本管理与协作支持。本指南将简明介绍两者的核心功能与使用技巧&…

SpringBoot-问题排查 Controller全局打印入参,返回值,响应时间,异常日志

问题: 想要打印每次请求的入参,返回值,响应时间,异常日志,如果给每个方法挨个添加打印日志非常麻烦 解决方案: 使用切面的方式将所有的Controller每个方法加入切入点使用环绕通知的方式可以在切入点执行前后执行切面,符合我们的需求在方法执行前后打印相关日志忽略LogIgnore注解…

mysql数据库varchar截断问题

用了这么多年mysql数据库&#xff0c;才发现varchar是可以截断的&#xff0c;而且是在我们线上数据库。个人觉得dba的这个设置是非常有问题的&#xff0c;用户往数据库里存东西&#xff0c;就是为了以后用的&#xff0c;截断了存放&#xff0c;数据不完整&#xff0c;就用不了了…

C++草原三剑客之一:继承

为王的诞生献上礼炮吧&#xff01; 目录 1 继承的概念及其定义 1.1 继承的概念 1.2 继承的定义 1.2.1 定义格式 1.2.2 继承方式以及继承基类成员访问方式的变化 1.3 继承类模板 2 基类和派生类之间的转换 3 继承中的作用域 3.1 隐藏规则 3.2 两道考察继承作用的相关…

Crash-SQLiteDiskIOException

目录 相关问题 日志信息 可能原因 问题排查 相关问题 蓝牙wifi无法使用 日志信息 可能原因 磁盘空间不足&#xff1a;当设备上的可用存储空间不足时&#xff0c;SQLite无法完成磁盘I/O操作&#xff0c;从而导致SQLiteDiskIOException。 数据库文件损坏&#xff1a;如果数…

6.824/6.5840 Lab 1: Lab 3: Raft

漆昼中温柔的不像话 静守着他的遗憾啊 旧的摇椅吱吱呀呀停不下 风卷走了满院的落叶落花 ——暮色回响 完整代码见&#xff1a; https://github.com/SnowLegend-star/6.824 在完成Lab之前&#xff0c;务必把论文多读几遍&#xff0c;力求完全理解Leader选举、log日志等过程。 …

【C++动态规划 BFS 博弈】3283. 吃掉所有兵需要的最多移动次数|2473

本文涉及知识点 C动态规划 CBFS算法 数学 博弈 LeetCode3283. 吃掉所有兵需要的最多移动次数 给你一个 50 x 50 的国际象棋棋盘&#xff0c;棋盘上有 一个 马和一些兵。给你两个整数 kx 和 ky &#xff0c;其中 (kx, ky) 表示马所在的位置&#xff0c;同时还有一个二维数组 …

6.824/6.5840 Lab 2: Key/Value Server

故事里能毁坏的只有风景 谁也摧毁不了我们的梦境 弦月旁的流星划过了天际 我许下 的愿望 该向谁 去说明 ——我是如此相信 完整代码见&#xff1a; https://github.com/SnowLegend-star/6.824 还是那句话&#xff0c;尽量只是参考思路而不是照抄 先阅读几遍实验说明的Introd…

Linux-异步IO和存储映射IO

异步IO 在 I/O 多路复用中&#xff0c;进程通过系统调用 select()或 poll()来主动查询文件描述符上是否可以执行 I/O 操作。而在异步 I/O 中&#xff0c;当文件描述符上可以执行 I/O 操作时&#xff0c;进程可以请求内核为自己发送一个信号。之后进程就可以执行任何其它的任务…

R 语言科研绘图第 1 期 --- 折线图-基础

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…

企业中数据防泄漏如何防范?有哪些防泄密措施?

企业数据不仅是业务运营的核心&#xff0c;也是企业竞争力的关键所在。 然而&#xff0c;随着信息技术的快速发展&#xff0c;数据泄露的风险也随之增加。 数据一旦泄露&#xff0c;不仅可能导致企业经济损失&#xff0c;还可能损害企业声誉&#xff0c;甚至引发法律纠纷。 …

汽车控制软件下载移动管家手机控车一键启动app

移动管家手机控制汽车系统是一款实现车辆远程智能控制的应用程序‌。通过下载并安装特定的APP&#xff0c;用户可以轻松实现以下功能&#xff1a;‌远程启动与熄火‌&#xff1a;无论身处何地&#xff0c;只要有网络&#xff0c;即可远程启动或熄火车辆&#xff0c;提前预冷或预…

基于事件驱动构建 AI 原生应用

作者&#xff1a;寒斜 AI 应用在商业化服务的阶段会面临诸多挑战&#xff0c;比如更快的服务交付速度&#xff0c;更实时、精准的结果以及更人性化的体验等&#xff0c;传统架构限制于同步交互&#xff0c;无法满足上述需求&#xff0c;本篇文章给大家分享一下如何基于事件驱动…