RocketMQ笔记(七)SpringBoot整合RocketMQ发送事务消息

目录

    • 一、简介
      • 1.1、流程图
      • 1.2、事务消息流程介绍
    • 二、Maven依赖
    • 三、生产者
      • 3.1、application配置
      • 3.2、员工表
      • 3.3、实体
      • 3.4、持久层
      • 3.5、监听器
    • 四、测试
      • 4.1、普通消息
      • 4.2、事务消息
        • 4.2.1、消费者
        • 4.2.2、正常提交
        • 4.2.3、异常提交
    • 五、其他
      • 5.1、接口说明
      • 5.2、checkLocalTransaction不回调

一、简介

  在之前的文章中,我讲过了,同步发送单条消息,异步发送单条消息,发送单向消息,发送顺序消息,以及批量发送消息,延迟消息。今天说下发送事务消息。

1.1、流程图

  事务消息交互流程如下图所示。
在这里插入图片描述

1.2、事务消息流程介绍

  事务消息交互流程如下图所示。

  1. 生产者将消息发送至Apache RocketMQ服务端
  2. Apache RocketMQ服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息被标记为"暂不能投递",这种状态下的消息即为半事务消息
  3. 生产者开始执行本地事务逻辑
  4. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
  • 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者
  • 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者
  1. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。 说明 服务端回查的间隔时间和最大回查次数
  2. 生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果
  3. 生产者根据检查到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理

二、Maven依赖

pom.xml

<?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">
    <parent>
        <artifactId>rocketmq</artifactId>
        <groupId>com.alian</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>06-send-transactional-message</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alian</groupId>
            <artifactId>common-rocketmq-dto</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

</project>

  父工程已经在我上一篇文章里,通用公共包也在我上一篇文章里有说明,包括消费者。具体参考:RocketMQ笔记(一)SpringBoot整合RocketMQ发送同步消息

三、生产者

3.1、application配置

application.properties

server.port=8006

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=test
spring.datasource.password=Alian!@34
spring.datasource.url=jdbc:mysql://192.168.0.139:3306/test?characterEncoding=utf8&useUnicode=true&useSSL=false&zeroDateTimeBehavior=convertToNull&autoReconnect=true&allowMultiQueries=true&failOverReadOnly=false&connectTimeout=6000&maxReconnects=5
spring.datasource.initialSize=5
spring.datasource.minIdle= 5
spring.datasource.maxActive=20

# rocketmq地址
rocketmq.name-server=192.168.0.234:9876
# 默认的生产者组
rocketmq.producer.group=transactional_group
# 发送同步消息超时时间
rocketmq.producer.send-message-timeout=3000
# 用于设置在消息发送失败后,生产者是否尝试切换到下一个服务器。设置为 true 表示启用,在发送失败时尝试切换到下一个服务器
rocketmq.producer.retry-next-server=true
# 用于指定消息发送失败时的重试次数
rocketmq.producer.retry-times-when-send-failed=3
# 设置消息压缩的阈值,为0表示禁用消息体的压缩
rocketmq.producer.compress-message-body-threshold=0

  在 RocketMQ 中,RocketMQTemplatesyncSend方法,它允许你批量发送同步消息,主要参数:

  • topic:主题
  • Message:消息内容
  • timeout:发送超时时间
  • delayLevel:延迟级别

3.2、员工表

员工实体

CREATE TABLE `employee` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `code` varchar(8) NOT NULL DEFAULT '' COMMENT '编号',
  `emp_name` varchar(20) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(2) NOT NULL DEFAULT '0' COMMENT '年龄',
  `salary` double(8,2) NOT NULL DEFAULT '0.00' COMMENT '工资',
  `department` varchar(20) NOT NULL DEFAULT '' COMMENT '部门',
  `hire_date` date NOT NULL DEFAULT '1970-07-01' COMMENT '入职时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `code_UNIQUE` (`code`),
  KEY `idx_code` (`code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4

3.3、实体

实体类

@Data
@Entity
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 员工编号
     */
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    /**
     * 员工编号
     */
    @Column(name = "code")
    private String code;

    /**
     * 员工姓名
     */
    @Column(name = "emp_name")
    private String name;

    /**
     * 员工年龄
     */
    @Column(name = "age")
    private int age;

    /**
     * 工资
     */
    @Column(name = "salary")
    private double salary = 0.00;

    /**
     * 部门
     */
    @Column(name = "department")
    private String department;

    /**
     * 入职时间
     */
    @Column(name = "hire_date")
    private LocalDate hireDate;

}

3.4、持久层

持久层

public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Integer> {

    Employee findByCode(String code);
}

3.5、监听器

  首先我们需要配置自定义的 RocketMQTemplate,最重要的是设置 TransactionMQProducer 的生产者组名称,这里是custom_transactional_group

  配置扩展的 RocketMQTemplate的类,并指定了 RocketMQ 生产者组的名称为custom_transactional_group。这个扩展的 RocketMQTemplate类可以用于发送事务消息以及其他类型的消息。

package com.alian.transactional.listener;

import com.alian.transactional.domain.Employee;
import com.alian.transactional.repository.EmployeeRepository;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;

@Slf4j
@RocketMQTransactionListener(rocketMQTemplateBeanName = "rocketMQTemplate")
public class EmployeeTransactionListener implements RocketMQLocalTransactionListener {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        try {
            log.info("事务消息Headers为:{}", message.getHeaders());
            String payload = new String((byte[]) message.getPayload());
            log.info("事务消息为:{}", payload);
            Employee employee = JSON.parseObject(payload, Employee.class);
            employee.setId(null);
            Employee save = employeeRepository.save(employee);
            if (save.getId() != null) {
                log.info("保存员工成功:{}", save.getId());
                return RocketMQLocalTransactionState.COMMIT;
            }
            log.info("保存员工失败:{}", save.getId());
        } catch (Exception e) {
            log.error("发送事务消息异常:{}", e.getMessage());
            return RocketMQLocalTransactionState.UNKNOWN;
        }
        return RocketMQLocalTransactionState.ROLLBACK;

    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        String id = message.getHeaders().get("rocketmq_KEYS").toString();
        log.info("事务消息key为:{}", id);
        Employee employee = employeeRepository.findByCode(id);
        if (employee == null) {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return RocketMQLocalTransactionState.COMMIT;
    }

}

四、测试

4.1、普通消息

  

@Slf4j
@SpringBootTest
public class SendTransactionalMessageTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void syncSendStringMessage() {
        String topic = "string_message_topic";
        String message = "我是一条同步文本消息:syncSendStringMessage";
        SendResult sendResult = rocketMQTemplate.syncSend(topic, message);
        log.info("同步发送返回的结果:{}", sendResult);
    }

    @AfterEach
    public void waiting() {
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

运行结果:

同步发送返回的结果:SendResult [sendStatus=SEND_OK, msgId=7F000001109818B4AAC24B519EC40000, offsetMsgId=C0A800EA00002A9F000000000000371F, messageQueue=MessageQueue [topic=string_message_topic, brokerName=rocketmq, queueId=2], queueOffset=1]

字符串消费者接收到的消息: 我是一条同步文本消息:syncSendStringMessage

此处我们还是能正常发送普通消息。

4.2、事务消息

4.2.1、消费者

  首先我们要在之前的消费者,增加一个消费监听

@Slf4j
@Component
@RocketMQMessageListener(topic = "transaction_message_topic", consumerGroup = "CONCURRENT_GROUP_TRANSACTION")
public class TransactionMessageConsumer implements RocketMQListener<JSONObject> {

    @Override
    public void onMessage(JSONObject json) {
        log.info("接收到事务消息:{}", json);
    }
}
4.2.2、正常提交

  接着,我们先测试正常发送消息(发送半事务消息,本地保存记录,成功提交事务)。

@Slf4j
@SpringBootTest
public class SendTransactionalMessageTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void sendMessageInTransaction() {
        // 计算过期时间戳
        String uuid = UUID.randomUUID().toString().replace("-", "");
        String topic = "transaction_message_topic";

        String code = "BAT10015";
        JSONObject json = new JSONObject();
        json.put("code", code);
        json.put("name", "张若尘");
        json.put("age", "25");
        json.put("salary", "15000");
        json.put("department", "测试部");
        json.put("hireDate", "2020-05-21");
        Message<JSONObject> rocketMessage = MessageBuilder.withPayload(json)
                // 根据需要设置
                .setHeader(RocketMQHeaders.TRANSACTION_ID, uuid)
                // 设置一个业务的key,以便事务回查时使用
                .setHeader(RocketMQHeaders.KEYS, code)
                .build();
        // 发送事务消息
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(topic, rocketMessage, null);
        log.info("【发送状态】:{}", transactionSendResult.getLocalTransactionState());
    }

    @AfterEach
    public void waiting() {
        try {
            // 休眠时间3分钟
            Thread.sleep(180000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

生产者运行结果:

事务消息Headers为:{rocketmq_TOPIC=transaction_message_topic, rocketmq_FLAG=0, __transactionId__=7F000001326018B4AAC24B72107D0000, rocketmq_TRANSACTION_ID=7F000001326018B4AAC24B72107D0000, rocketmq_KEYS=BAT10015, id=c98dca25-50b7-9c9a-fc5f-b7ab7bea18eb, TRANSACTION_ID=0055a562d5af4db0a19f2939124a82f0, contentType=application/json, timestamp=1710488166582}
事务消息为:{"hireDate":"2020-05-21","code":"BAT10015","name":"张若尘","salary":"15000","department":"测试部","age":"25"}
保存员工成功:25
【发送状态】:COMMIT_MESSAGE

消费者运行结果:

接收到事务消息:{"hireDate":"2020-05-21","code":"BAT10015","name":"张若尘","salary":"15000","department":"测试部","age":"25"}

  从上面的监听器保存成功记录后,返回了 RocketMQLocalTransactionState.COMMIT,所以我们的事务是正常提交的,半事务消息也被推送到消息者队列了。

4.2.3、异常提交

  因为,我们数据库表设计时,code字段的长度是8位,我们就插入一个大于8位的值,然后抛出一个异常,然后rocketmq 会进行回查

@Slf4j
@SpringBootTest
public class SendTransactionalMessageTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Test
    public void sendMessageInTransaction() {
        // 计算过期时间戳
        String uuid = UUID.randomUUID().toString().replace("-", "");
        String topic = "transaction_message_topic";

        String code = "BAT8888888";
        JSONObject json = new JSONObject();
        json.put("code", code);
        json.put("name", "张若尘");
        json.put("age", "25");
        json.put("salary", "15000");
        json.put("department", "测试部");
        json.put("hireDate", "2020-05-21");
        Message<JSONObject> rocketMessage = MessageBuilder.withPayload(json)
                // 根据需要设置
                .setHeader(RocketMQHeaders.TRANSACTION_ID, uuid)
                // 设置一个业务的key,以便事务回查时使用
                .setHeader(RocketMQHeaders.KEYS, code)
                .build();
        // 发送事务消息
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(topic, rocketMessage, null);
        log.info("【发送状态】:{}", transactionSendResult.getLocalTransactionState());
    }

    @AfterEach
    public void waiting() {
        try {
            // 休眠时间3分钟
            Thread.sleep(180000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

运行结果:

事务消息Headers为:{rocketmq_TOPIC=transaction_message_topic, rocketmq_FLAG=0, __transactionId__=7F00000107B018B4AAC24B6E46F00000, rocketmq_TRANSACTION_ID=7F00000107B018B4AAC24B6E46F00000, rocketmq_KEYS=BAT888888, id=9d148765-9f66-2242-af17-0591c8378c7a, TRANSACTION_ID=26930ad65e7c40738aa24c6cb5c3da61, contentType=application/json, timestamp=1710487918377}
事务消息为:{"hireDate":"2020-05-25","code":"BAT888888","name":"Alian","salary":"35000","department":"研发部","age":"28"}
SQL Error: 1406, SQLState: 22001
Data truncation: Data too long for column 'code' at row 1
发送事务消息异常:could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement
【发送状态】:UNKNOW

事务消息key为:BAT888888
本地事务失败,删除消息:BAT888888

  从上面的监听器保存异常(Data too long for column ‘code’),返回了 RocketMQLocalTransactionState.UNKNOWN(只是模拟返回),就不知道本地事务到底是成功还是失败,所以需要进行事务回查,也就是要调用:checkLocalTransaction放,检查本地是否正常,因为不存在记录,我们返回RocketMQLocalTransactionState.ROLLBACK,半事务消息就被删除了。

五、其他

5.1、接口说明

  在springBoot整合中,实现的接口是RocketMQLocalTransactionListener接口,而不是TransactionListener。其中executeLocalTransaction 是半事务消息发送成功后,执行本地事务的方法,具体执行完本地事务后,可以在该方法中返回以下三种状态:

  • RocketMQLocalTransactionState.COMMIT:提交事务,允许消费者消费该消息
  • RocketMQLocalTransactionState.ROLLBACK:回滚事务,消息将被丢弃不允许消费。
  • RocketMQLocalTransactionState.UNKNOWN:暂时无法判断状态,等待固定时间以后Broker端根据回查规则向生产者进行消息回查。

  checkLocalTransaction是由于二次确认消息没有收到,Broker端回查事务状态的方法。回查规则:本地事务执行完成后,若Broker端收到的本地事务返回状态为RocketMQLocalTransactionState.UNKNOWN,或生产者应用退出导致本地事务未提交任何状态。则Broker端会向消息生产者发起事务回查,第一次回查后仍未获取到事务状态,则之后每隔一段时间会再次回查。

5.2、checkLocalTransaction不回调

  一般来说的原因:

  • 在springboot整合rocketmq,和直接使用rocketmq实现的接口是不一样的,具体看上一小点
  • 编码错误,不会模拟情景,rocketmq未收到半事务提交的结果(Commit或是Rollback),才会进行回查
  • 最大的一个可能是,你使用springboot整合rocketmq时的版本和服务端的版本不一致(我遇到过),比如我这里整合版本是2.2.3,对应的rocketmq的版本是5.0.0,所以我的服务端,肯定也安装一个5.0的版本。如果服务端是4.x,就会出现消息转化报错,从而无法回调,具体可以看broker的日志
  • 参数配置,比如(transactionCheckEnable=truetransactionCheckInterval=15000),是不是配置关闭,或者检查时间过长
  • 测试不用心,比如写了单元测试,测试完,程序就结束了,导致服务端无法检查,所以我的测试程序,都休眠了一会

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

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

相关文章

智能传真机触摸屏中应用的触摸感应芯片

智能传真机是应用扫描和光电变换技术&#xff0c;把文件、图表、照片等静止图像转换成电信号&#xff0c;传送到接收端&#xff0c;以记录形式进行复制的通信设备。智能传真机将需发送的原件按照规定的顺序&#xff0c;通过光学扫描系统分解成许多微小单元&#xff08;称为像素…

AXS4110 单节锂电池保护芯片 爱协生 兼容XB6042/CM1124 低成本

概述 AXS4110系列产品是一种针对锂离子或聚合物电池保护的高集成解决方案芯片。AXS4110系列包含先进的功率MOSFET、高精度电压检测电路和延时电路。AXS4110系列采用DFN1x1x0.37_4封装&#xff0c;使其成为小体积电池保护的理想解决方案。 AXS4110具有过充、过放、过流、0V充电…

SpirngBoot开发常用知识

springboot开发常用知识 命令行打包SpringBoot打包插件window端口命令临时属性设置热部署启动热部署热部署范围 常用计量单位数据校验加载测试的专用属性Web环境模拟测试如何发送虚拟请求业务层测试回滚随机产生测试用例内置数据源 命令行打包 对SpringBoot项目进行打包命令行…

液冷是大模型对算力需求的必然选择?|英伟达 GTC 2024六大亮点

在这个以高性能计算和大模型推动未来通用人工智能时代&#xff0c;算力已成为科技发展的隐形支柱。本文将重点探讨算力的演进&#xff0c;深入分析在不同领域中算力如何成为推动进步的基石&#xff1b;着眼于液冷如何突破算力瓶颈成为引领未来的先锋&#xff0c;对液冷散热的三…

智慧城市中的物联网革命——青创智通

工业物联网解决方案-工业IOT-青创智通 得益于物联网 (IoT)的变革力量&#xff0c;智慧城市的概念正在迅速成为现实。物联网正在从根本上改变城市的运作方式&#xff0c;为城市居民带来更高的效率、可持续性和生活质量。在本文中&#xff0c;我们将探讨物联网在智慧城市中的作用…

小程序商城免费搭建之java商城 电子商务Spring Cloud+Spring Boot+二次开发+mybatis+MQ+VR全景

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…

面对DDOS攻击,有哪些解决办法

随着互联网带宽的持续增长以及DDOS黑客技术的发展&#xff0c;DDOS拒绝服务攻击的实施变得愈发容易。商业竞争、打击报复、网络敲诈等多种因素&#xff0c;各行各业的用户都曾受到DDOS攻击的威胁。 一旦遭受到DDOS攻击&#xff0c;随之而来的就是业务宕机&#xff0c;用户无法…

【必看】网络安全从业者书单推荐

推荐几本网络安全从业者必读的书籍 一、计算机基础 《网络硬件设备完全技术宝典》&#xff08;第3版&#xff09; 本书共768页&#xff0c;包括交换机、路由器、安全设备、网络设备等重要和常用的网络设备&#xff0c;图文并茂&#xff0c;语言流畅&#xff0c;内容及其丰富…

NL2SQL基础系列(2):主流大模型与微调方法精选集,Text2SQL经典算法技术回顾七年发展脉络梳理

NL2SQL基础系列(2)&#xff1a;主流大模型与微调方法精选集&#xff0c;Text2SQL经典算法技术回顾七年发展脉络梳理 Text-to-SQL&#xff08;或者Text2SQL&#xff09;&#xff0c;顾名思义就是把文本转化为SQL语言&#xff0c;更学术一点的定义是&#xff1a;把数据库领域下的…

MQ之————如何保证消息的可靠性

MQ之保证消息的可靠性 1.消费端消息可靠性保证&#xff1a; 1.1 消息确认&#xff08;Acknowledgements&#xff09;&#xff1a; 消费者在接收到消息后&#xff0c;默认情况下RabbitMQ会自动确认消息&#xff08;autoAcktrue&#xff09;。为保证消息可靠性&#xff0c;可以…

如何用Python编写简单的网络爬虫(页面代码简单分析过程)

一、什么是网络爬虫 在当今信息爆炸的时代&#xff0c;网络上蕴藏着大量宝贵的信息&#xff0c;如何高效地从中获取所需信息成为了一个重要课题。网络爬虫&#xff08;Web crawler&#xff09;作为一种自动化工具&#xff0c;可以帮助我们实现这一目标&#xff0c;用于数据分析…

发挥自定义表单开源优势,助力实现流程化办公!

在数字化发展进程中&#xff0c;利用低代码技术平台、自定义表单开源的优势特点&#xff0c;可以让企业实现流程化办公&#xff0c;从而实现提质增效的办公目的。作为一种新兴的应用开发模式&#xff0c;低代码技术平台获得了很多新老客户朋友的青睐和喜爱&#xff0c;正以它自…

静力水准仪如何进行数据获取及转换?

静力水准仪作为现代测量技术中的一项重要工具&#xff0c;通过利用磁致伸缩效应实现了对被测物体沉降量的高精度测量。本文将介绍磁致式静力水准仪数据获取的原理与方法&#xff0c;以及数据转换的过程&#xff0c;探讨其在工程安全监测领域的应用价值。 数据获取原理 静力水准…

【QT入门】 Qt自定义控件与样式设计之QPushButton点击按钮弹出菜单

往期回顾&#xff1a; 【QT入门】 Qt自定义控件与样式设计之QPushButton实现鼠标悬浮按钮弹出对话框-CSDN博客 【QT入门】 Qt自定义控件与样式设计之QComboBox样式表介绍-CSDN博客 【QT入门】 Qt自定义控件与样式设计之QCheckBox qss实现按钮开关-CSDN博客 【QT入门】 Qt自定义…

ai智能问答免费API接口

智能对话API接口&#xff0c;可以为网站或其他产品提供强大的智能交互功能&#xff0c;无需自行开发复杂的语义分析和自然语言处理算法。这使得开发者能够更专注于产品的核心功能和用户体验&#xff0c;加速产品上线速度并降低开发成本。 智能对话API接口的功能还包括对话内容…

德勤:《中国AI智算产业2024年四大趋势》

2023年《数字中国建设整体布局规划》的发布&#xff0c;明确了数字中国是构建数字时代竞争优势的关键支撑&#xff0c;是继移动互联网时代以来经济增长新引擎。当我们谈论数字中国的构建&#xff0c;不仅仅是在讨论一个国家级的技术升级&#xff0c;而是关乎如何利用数字技术来…

AI时代,搜索引擎的巨头地位恐怕不保了

兄弟们&#xff0c;你们使用搜索网站的频率有降低吗&#xff1f; ChatGPT 已经流行了一年多了&#xff0c;这期间数个大模型都发展了起来。 搜索引擎本质上也属于问答系统&#xff0c;所以&#xff0c;在大模型成熟之后&#xff0c;我使用搜索的频率越来越低了。 主要是因为…

苍穹外卖Day12——总结12

前期文章 文章标题地址苍穹外卖Day01——总结1https://lushimeng.blog.csdn.net/article/details/135466359苍穹外卖Day02——总结2https://lushimeng.blog.csdn.net/article/details/135484126苍穹外卖Day03——总结3https://blog.csdn.net/qq_43751200/article/details/1363…

C语言 文件函数

目录 1. 文件的打开和关闭 2. 文件的顺序读写 2.1 顺序读写函数介绍 2.2读文件&#xff08;读文件只能读一次&#xff09; 2.3写文件 3. 文件的随机读写 3.1 fseek 3.2 ftell 3.3 rewind 4.文件读取结束的判定 4.1 被错误使误的 feof 我对读写的理解&#xff1a;(从…

【机器学习】决策树(Decision Tree,DT)算法介绍:原理与案例实现

前言 决策树算法是机器学习领域中的一种重要分类方法&#xff0c;它通过树状结构来进行决策分析。决策树凭借其直观易懂、易于解释的特点&#xff0c;在分类问题中得到了广泛的应用。本文将介绍决策树的基本原理&#xff0c;包括熵和信息熵的相关概念&#xff0c;以及几种经典的…