RocketMq详解:二、SpringBoot集成RocketMq
在上一章中我们对Rocket的基础知识、特性以及四大核心组件进行了详细的介绍,本章带着大家一起去在项目中具体的进行应用,并设计将其作为一个工具包只提供消息的分发服务和业务模块进行解耦
在进行本章的学习之前,需要确保你的可以正常启动和访问RocketMq服务,还未安装的可以移步至此:《docker安装rocketMq》
1.添加maven依赖
<dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId><version>2.2.3</version></dependency>
2.项目结构
3.配置管理
rocketmq:name-server: 127.0.0.1:9876# 生产者producer:group: boot_group_1# 消息发送超时时间send-message-timeout: 3000# 消息最大长度4Mmax-message-size: 4096# 消息发送失败重试次数retry-times-when-send-failed: 3# 异步消息发送失败重试次数retry-times-when-send-async-failed: 2# 消费者consumer:group: boot_group_1# 每次提取的最大消息数pull-batch-size: 5
上面的配置如果是在分布式环境下也可以配置在Apollo或nacos等配置中心里进行动态配置
4.配置类
在配置类中主要定义两个Bean的加载,即RocketMQTemplate和DefaultMQProducer,主要是提供消息发送的能力,即生产消息;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author ninesun* @ClassName RocketMqConfig* @description: 消息中间件配置类* @date 2024年05月19日* @version: 1.0*/
@Configuration
public class RocketMqConfig {@Value("${rocketmq.name-server}")private String nameServer;@Value("${rocketmq.producer.group}")private String producerGroup;@Value("${rocketmq.producer.send-message-timeout}")private Integer sendMsgTimeout;@Value("${rocketmq.producer.max-message-size}")private Integer maxMessageSize;@Value("${rocketmq.producer.retry-times-when-send-failed}")private Integer retryTimesWhenSendFailed;@Value("${rocketmq.producer.retry-times-when-send-async-failed}")private Integer retryTimesWhenSendAsyncFailed;@Beanpublic RocketMQTemplate rocketMqTemplate() {RocketMQTemplate rocketMqTemplate = new RocketMQTemplate();rocketMqTemplate.setProducer(defaultMqProducer());return rocketMqTemplate;}@Beanpublic DefaultMQProducer defaultMqProducer() {DefaultMQProducer producer = new DefaultMQProducer();producer.setNamesrvAddr(this.nameServer);producer.setProducerGroup(this.producerGroup);producer.setSendMsgTimeout(this.sendMsgTimeout);producer.setMaxMessageSize(this.maxMessageSize);producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed);producer.setRetryTimesWhenSendAsyncFailed(this.retryTimesWhenSendAsyncFailed);return producer;}
}
5.基础用法
5.1 消息生产
编写一个生产者接口类,分别使用RocketMQTemplate和DefaultMQProducer实现消息发送的功能,然后可以通过Dashboard控制面板查看消息详情
- 编写Controller进行消息的发送
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;@RestController
@Slf4j
public class TestController01 {@Resourceprivate RocketMQTemplate rocketMqTemplate;@Resourceprivate DefaultMQProducer defaultMqProducer;/*** 利用rocketMqTemplate发送消息** @return*/@GetMapping("/send/msg1")public String sendMsg1() {try {// 构建消息主体Map<String, String> msgBody = new HashMap<>();msgBody.put("data", "利用rocketMqTemplate发送消息");// 发送消息rocketMqTemplate.convertAndSend("boot-mq-topic", JSON.toJSONString(msgBody));} catch (Exception e) {e.printStackTrace();}return "OK";}/*** 利用DefaultMQProducer发送消息* @return*/@GetMapping("/send/msg2")public String sendMsg2() {try {// 构建消息主体,此处可以用对象代替,为了方便演示,使用mapMap<String, String> msgBody = new HashMap<>();msgBody.put("data", "利用DefaultMQProducer发送消息");// 构建消息对象Message message = new Message();message.setTopic("boot-mq-topic");message.setTags("boot-mq-tag");message.setKeys("boot-mq-key");message.setBody(JSON.toJSONString(msgBody).getBytes());// 发送消息,打印日志SendResult sendResult = defaultMqProducer.send(message);log.info("msgId:{},sendStatus:{}", sendResult.getMsgId(), sendResult.getSendStatus());} catch (Exception e) {e.printStackTrace();}return "OK";}
}
自己自行测试,访问这两个接口后,我们可以在Dashboard控制面板查看到:
5.2 消息消费者
接下来,我们创建一个消息消费者。在Spring Boot项目中,我们可以使用@RocketMQMessageListener注解来定义一个消息消费者。
import com.alibaba.fastjson.JSON;
import com.example.demo.po.MessageData;
import com.example.demo.po.User;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;/*** @author hb24795* @ClassName BootMqConsumer* @description: 消费者* @date 2024年05月26日* @version: 1.0*/
@Service
@RocketMQMessageListener(topic = "boot-mq-topic", consumerGroup = "boot_group_1")
public class BootMqConsumer implements RocketMQListener<String> {@Overridepublic void onMessage(String message) {System.out.printf("------- StringConsumer received:");System.out.println(message);}
}
当然你可以设置更多的消费前置条件:
@Component
@RocketMQMessageListener(topic = "your_topic_name", consumerGroup = "your_consumer_group_name",selectorExpression = "your_tag", selectorType = ExpressionType.TAG)
public class MyConsumer implements RocketMQListener<String> {@Overridepublic void onMessage(String message) {// 处理消息的逻辑System.out.println("Received message: " + message);}
}
注意:此处的consumerGroup要和我们配置的group对应
上面我们已经完成了一个基本的从消息发送到消息消费的逻辑,大家可以在自己的项目中结合设计模式,aop等来定制化自己的消息中间件,但是消息的类型远不止这几个,在实现之前我们先把消息的发送代码和我们的业务代码进行解耦
@Component
public class MessageProduct {@Resourceprivate RocketMQTemplate rocketMqTemplate;@Resourceprivate DefaultMQProducer defaultMqProducer;public SendResult SendMessage(String topic, Object data, List<String> keys, String tags) throws MQBrokerException, RemotingException, InterruptedException, MQClientException {Message message = new Message();if (StringUtils.isBlank(topic)) {return null;} else {message.setTopic(topic);}if (data != null) {message.setBody(JSON.toJSONString(data).getBytes());}if (!CollectionUtils.isEmpty(keys)) {message.setKeys(keys);}if (StringUtils.isBlank(tags)) {message.setTags(tags);}message.setBody(JSON.toJSONString(data).getBytes());// 发送消息,打印日志return defaultMqProducer.send(message);}
}
5.3 延迟消息
RocketMQ 支持延迟消息发送,但并非任意时间,而是有特定的延迟等级。在较旧的版本中,延迟等级从1到18,每个等级对应一个固定的延迟时间范围。然而,随着RocketMQ的更新发展,其对于延迟消息的支持变得更加灵活。
根据最新的资料,RocketMQ 5.x版本支持的最大延迟时间可以非常长,可达一年之久,这意味着在一定意义上,它能够满足大部分应用场景对于延迟消息的需要,尽管这并不等同于可以任意指定任意时间精度的延迟。
我们先看看必须通过指定的等级进行延迟的实现:
RocketMQ不支持任意时间的延迟,只有18个等级的延迟时间,默认是:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h。从头到尾共18个等级时间,s、m、h、d,分别表示秒、分、时、天。
默认的18个等级对应的时间可以修改,在broker.conf中增加如下配置,根据自身需求修改时间,然后重启broker。
1、延迟消息的原理
如果让你来设计RocketMQ的延迟消息,你会怎么设计?
1.延迟消息也是个消息,只是多了延迟时间,既然是消息,不管是不是要立刻处理,先找个临时Topic存储起来。
2.Topic里面实际上是一个个队列,那所有的延迟消息要存在一个队列里吗?不要放在同一个队列里,因为消息各自都有不同的延迟时间,如果放在一个队列里,会牵扯到其余问题:比如排序、比如记录消费位置等。所以是按延迟时间分开存。
3.消息已经存起来了,那怎么处理呢?既然涉及到了延迟时间,那自然启动线程去定时获取消息,判断消息的延迟时间是否已经到达,到达之后则取出来投放到目的Topic。
讲到这里,延迟消息的架构图基本浮现出来了:
实际上RocketMQ在设计延迟消息时,跟上面的思路基本类似,不在赘述,额外补充几点:
1.消息进入Broker后,会被存储在TopicSCHEDULE_TOPIC_XXXX中,只是在Dashboard中看不到。
TopicSCHEDULE_TOPIC_XXXX中有18个消息队列,分别存储18个延迟等级对应的消息。
2.RocketMQ 在启动时,会从broker.conf中获取18个等级对应的时间,延迟等级和时间的关系会存在放到DelayLevelTable中。
3.RocketMQ会开启18个定时任务每隔100ms,从TopicSCHEDULE_TOPIC_XXXX判断18个队列里的第一个消息是否可以被投放,如果可以投放,则在投放到原本的目的Topic。
判断逻辑:存入时间+延迟时间 > 当前时间。
说到这里,估计你也能猜到,为什么不支持自定义延迟时间了,核心原因还是性能问题。
试想一下,如果设计成任意时间,那么就不可能使用18个队列了,更不可能使用无限个队列了,只可能使用单个队列。
但是如果使用单个队列,按照先进先出的存放的话,那出现需要后进先出的消息怎么办?那只能对整个队列进行排序,如果消息量很大,每次有消息进来都需要排序,那CPU肯定会被玩爆。
而且队列里的消息被消费后,都会记录偏移量,如果每次有消息进来都要排序,那偏移量则失去意义,增加了消息丢失的风险。
所以,RocketMQ的这种18个延迟时间等级的设计,虽然在延迟时间的自由度上作出了妥协,但是基本满足业务,性能也很优秀。
2.具体实现
我们对上面的通用发送进行修改:
public SendResult SendMessage(String topic, Object data, List<String> keys, String tags, Integer delayLevel) throws MQBrokerException, RemotingException, InterruptedException, MQClientException {Message message = new Message();if (StringUtils.isBlank(topic)) {return null;} else {message.setTopic(topic);}if (data != null) {message.setBody(JSON.toJSONString(data).getBytes());}if (!CollectionUtils.isEmpty(keys)) {message.setKeys(keys);}if (StringUtils.isBlank(tags)) {message.setTags(tags);}if (delayLevel != null) {message.setDelayTimeLevel(delayLevel);}message.setBody(JSON.toJSONString(data).getBytes());// 发送消息,打印日志return defaultMqProducer.send(message);}
此处设置的等级5,可能对应1分钟的延迟,但具体等级和时间的映射关系可以根据RocketMQ服务器的配置有所不同
我们测试一下:
@GetMapping("/send/msg2")public String sendMsg2() {try {// 构建消息主体,此处可以用对象代替,为了方便演示,使用map![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/3799dce071c44aec923eb35d5a418f2e.png#pic_center)User user = User.builder().id(1).name("ninesun").build();SendResult sendResult = messageProduct.SendMessage("boot-mq-topic", user, Collections.singletonList(user.getId().toString()), null, null);log.info("msgId:{},sendStatus:{}", sendResult.getMsgId(), sendResult.getSendStatus());} catch (Exception e) {e.printStackTrace();}return "OK";}
如果我们想实现任意时间的延迟,可以利用上面的延迟等级进行实现
我只需要根据自定义的延迟时间获取延迟等级
首先自定义一个消息体,用于存储必要的信息
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DelayMessageDTO<T> {private T data;private Integer delayTime;private String topic;private List<String> keys;private String tags;
}
设计一个简单的算法用于获取延迟等级
private static final Integer[] delayTimes = {1, 5, 10, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 1200, 1800, 3600, 7200};private Integer getDelayLevel(Integer delayTime) {if (delayTime == null || delayTime == 0) {return null;}for (int i = 0; i < delayTimes.length; i++) {int level = i + 1;if (delayTime.equals(delayTimes[i])) {return level;}if (delayTime > delayTimes[i] && delayTime < delayTimes[i + 1]) {return level;}}return null;}
新增一个消费者专门用于处理该消息的延迟
@Service
@RocketMQMessageListener(topic = "delay-consumer-topic", consumerGroup = "boot_group_1")
@Slf4j
public class DelayMqConsumer implements RocketMQListener<String> {@ResourceMessageProduct messageProduct;@Overridepublic void onMessage(String message) {log.info("收到延迟消费消息,消息:{}", message);System.out.println(message);DelayMessageDTO delayMessageDTO = JSON.parseObject(message, DelayMessageDTO.class);log.info("剩余:{}s", delayMessageDTO.getDelayTime());try {messageProduct.SendDelayMessage(delayMessageDTO);} catch (MQBrokerException e) {throw new RuntimeException(e);} catch (RemotingException e) {throw new RuntimeException(e);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (MQClientException e) {throw new RuntimeException(e);}}
}
消息的发送
public SendResult SendDelayMessage(DelayMessageDTO data) throws MQBrokerException, RemotingException, InterruptedException, MQClientException {// 发送消息,打印日志if (data == null) {return null;}Integer delayLevel = this.getDelayLevel(data.getDelayTime());if (delayLevel == null) {return this.SendMessage(data.getTopic(), data, data.getKeys(), data.getTags(), delayLevel);}Integer delayTime = data.getDelayTime() - delayTimes[delayLevel - 1];data.setDelayTime(delayTime);return this.SendMessage("delay-consumer-topic", data, data.getKeys(), data.getTags(), delayLevel);}
至此我们就可以简单的实现一个消息的任意时间延迟,但是实际的延迟实现中,我们并不推荐该延迟方式,因为这种延迟完全依赖于mq的性能,如果遇到消息的积压等,我们的延迟将变得十分不可靠,很多开源社区中推荐:
- 外部存储与调度:将消息和期望的发送时间存储到数据库或缓存中(如Redis),然后使用一个定时任务(如Quartz、Spring Scheduler)定期检查这些存储的消息,当达到预定时间时,再通过DefaultMQProducer发送出去。
- 利用死信队列与TTL:虽然这不是直接实现任意时间延迟的方式,但可以通过设置消息的TTL(生存时间)和死信队列机制间接实现。消息到期后成为死信,触发特定逻辑进行处理或重定向到另一个队列进行实际发送。
在RocketMQ5.0版本,支持任意时段的延迟消息。在Github中最新的版本中已经解决了这个问题,[ISSUE #6203] Allow to publish delay message with arbitrary timestamp #6204
我们可以直接使用以下方式去实现:
@Resourceprivate RocketMQTemplate rocketMqTemplate;public SendResult SendMessage(DelayMessageDTO data) {return rocketMqTemplate.syncSendDelayTimeSeconds(data.getTopic(), JSON.toJSONString(data), data.getDelayTime());}
相关文章:
RocketMq详解:二、SpringBoot集成RocketMq
在上一章中我们对Rocket的基础知识、特性以及四大核心组件进行了详细的介绍,本章带着大家一起去在项目中具体的进行应用,并设计将其作为一个工具包只提供消息的分发服务和业务模块进行解耦 在进行本章的学习之前,需要确保你的可以正常启动和…...
【源码】二开版微盘交易系统/贵金属交易平台/微交易系统
二开版微盘交易系统/贵金属交易平台/微交易系统 一套二开前端UI得贵金属微交易系统,前端产品后台可任意更换 此系统框架不是以往的至尊的框架,系统完美运行,K线采用nodejs方式运行 K线结算都正常,附带教程 资源来源:https://www.…...
React@16.x(26)useContext
目录 1,上下文的使用2,useContext 1,上下文的使用 之前的文章中介绍过 context上下文。 使用举例: import React, { useState } from "react";const ctx React.createContext();function Child() {return <ctx.C…...
Vue2学习(04)
目录 一、组件的三大组成部分 二、组件的样式冲突scoped 三、scoped原理 编辑 四、data是一个函数 五、组件通信 1.概念:是指组件与组件之间的数据传递,组件的数据是独立的,无法直接访问其他组件的数据,想用其他组件的数…...
Python中columns()函数
1. columns的概念 在数据分析和处理中,columns是指数据表中的列,也称为字段。每一列代表了特定类型的数据,在一个数据表中,每一行代表了一个数据实例,而每一列则代表了一个特定的特征或属性。 可以直接定义和更改列标题,也可以直接读取某列的数据,或者对某列进行运算。…...
Vue3 使用 vue-clipboard3 实现一键复制
安装依赖 npm install --save vue-clipboard3示例 <template><el-input v-model"data"></el-input><button click"touchCopy">复制链接</button> </template><script setup lang"ts"> // 导入插件 …...
人机环境生态系统智能的流动性
一般来说,流动性可以理解为事物在空间或时间上的转移、变化或运动。在人机环境生态系统中,流动性可以涉及以下几个方面: 信息流动:数据、消息、知识等在系统中的传递和交换。这可能包括传感器收集的数据传输到处理中心,…...
实现开源可商用的 ChatPDF RAG:密集向量检索(R)+上下文学习(AG)
实现 ChatPDF & RAG:密集向量检索(R)上下文学习(AG) RAG 是啥?实现 ChatPDF怎么优化 RAG? RAG 是啥? RAG 是检索增强生成的缩写,是一种结合了信息检索技术与语言生成…...
对待谷歌百度等搜索引擎的正确方式
对待百度、谷歌等搜索引擎的方式是,你要站在搜索引擎之上,保持自己的独立思想和意见。 当谷歌宣布他们将会根据一个名为“Alphabet”的新控股公司来进行业务调整时,在科技界引起了一片恐慌之声。 永远不要说这是一个公司一直在做的事情。不…...
pikachu靶场通关全流程
目录 暴力破解: 1.基于表单的暴力破解: 2.验证码绕过(on server): 3.验证码绕过(on client): token防爆破: XSS: 1.反射型xss(get): 2.反射性xss(post): 3.存储型xss&#…...
实现k8s网络互通
前言 不管是docker还是k8s都会在物理机组件虚拟局域网,只不过是它们实现的目标不同。 docker:针对同一个物理机(宿主机) k8s:针对的是多台物理机(宿主机) Docker 虚拟局域网 K8S虚拟局域网 …...
diffusers 使用脚本导入自定义数据集
在训练扩散模型时,如果附加额外的条件图片数据,则需要我们准备相应的数据集。此时我们可以使用官网提供的脚本模板来控制导入我们需要的数据。 您可以参考官方的教程来实现具体的功能需求,为了更加简洁,我将简单描述一下整个流程…...
【Android面试八股文】请讲一讲synchronized和ReentrantLock的区别
文章目录 请讲一讲synchronized和ReentrantLock的区别这道题想考察什么 ?考察的知识点应该如何回答?Synchronized 的原理ReentrantLock 的原理Synchronized 和 ReentrantLock 的区别总结请讲一讲synchronized和ReentrantLock的区别 这道题想考察什么 ? 是否了解并发相关的理…...
springmvc 全局异常处理器配置的三种方式深入底层源码分析原理
文章目录 springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理配置全局异常处理器的三种方式实现接口HandlerExceptionResolver并配置到WebMvcConfigurer注解式配置ExceptionHandlercontroller里方法上定义ExceptionHandler 深入源码分析进入DispatcherServlet执…...
MySQL 8.0 安装、配置、启动、登录、连接、卸载教程
目录 前言1. 安装 MySQL 8.01.1 下载 MySQL 8.01.2 安装 MySQL 8.0 2. 配置 MySQL 8.02.1打开环境变量2.2新建变量 MYSQL_HOME2.3编辑 Path 变量 3. 启动MySQL 8.03.1验证安装与配置是否成功3.2初始化并注册MYSQL3.3 启动MYSQL服务 4.登录MySQL4.1修改账户默认密码4.2登录MYSQL…...
Pythone 程序打包成 exe
1.安装pyinstaller # 安装 pip install pyinstaller # 查看版本 pyinstaller -v2.更新pyinstaller 版本 # 更新 pip install --upgrade pyinstaller # 查看版本 pyinstaller -v3.切换到 py文件所在目录 #切换到.py所在的目录 E: cd cd E:\x-svn_x-local\04PythoneProjects\A…...
“卫星-无人机-地面”遥感数据快速使用及地物含量计算
随着我国高分系列、欧比特系列、自然资源卫星系列等卫星数据的获取,以及美国Headwall、芬兰SPECIM、挪威HySpex、我国双利合谱、智科远达、中科谱光等无人机数据的兴起,遥感数据越来越易得。这些多源数据,在与典型地面点结合后,将…...
设计模式学习(二)工厂模式——简单工厂模式
设计模式学习(二)工厂模式——简单工厂模式 前言简单工厂模式简介示例优点缺点使用场景 前言 工厂模式是一种常用的设计模式,属于创建型模式之一。它的主要目的是为了解耦组件之间的依赖关系。通过使用工厂模式,系统中的具体类的…...
贷款业务——LPR、APR、IRR
文章目录 LPR(Loan Prime Rate)贷款市场报价利率APR(Annual Percentage Rate)年化百分比利率IRR(Internal Rate of Return)内部收益率 LPR、APR 和 IRR 是三个不同的金融术语,LPR 是一种市场利率…...
Simscape Multibody与RigidBodyTree:机器人建模
RigidBodyTree:主要用于表示机器人刚体结构的动力学模型,重点关注机器人的几何结构、质量和力矩,以及它们如何随时间变化。它通常用于计算机器人的运动和受力情况。Simscape Multibody:作为Simscape的一个子模块,专门用…...
数据结构刷题-链表
数据结构刷题-链表 总结:1 链表的解法总结: 1 链表的知识点:1 LC链表合集:1.1 lc206反转链表: 双指针:lc25: K个一组翻转链表:栈1.2 lc203移除链表元素:1.3 设计链表:1.4…...
Java应届第一年规划
👽System.out.println(“👋🏼嗨,大家好,我是代码不会敲的小符,目前工作于上海某电商服务公司…”); 📚System.out.println(“🎈如果文章中有错误的地方,恳请大家指正&…...
js之简单轮播图
今天给大家封装一个简单的轮播图,可以点击下一张上一张以及自动轮播 <!DOCTYPE html> <html><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>走马…...
GitLab教程(二):快手上手Git
文章目录 1.将远端代码克隆到本地2.修改本地代码并提交到远程仓库3.Git命令总结git clonegit statusgit addgit commitgit pushgit log 首先,我在Gitlab上创建了一个远程仓库,用于演示使用Gitlab进行版本管理的完整流程: 1.将远端代码克隆到本…...
前端渲染大量数据思路【虚拟列表】【异步机制】
当浏览器遇到性能瓶颈导致页面卡顿时,你会怎么处理?如何查找问题的原因? 浏览器本身自带性能检测工具,通常我们分析由脚本导致的页面卡顿会选择 性能(performance) 选项卡,在其中我们可以找到导…...
Ubuntu24.04记录网易邮箱大师的安装
邮箱大师下载 官网自行下载,下载后文件名“mail.deb" https://dashi.163.com/ 安装发现缺少依赖 #mermaid-svg-8wqpqFSBVOPD7NGP {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-8wqpqFSBVOPD7NGP …...
PDF编辑与转换的终极工具智能PDF处理Acrobat Pro DC
Acrobat Pro DC 2023是一款功能全面的PDF编辑管理软件,支持创建、编辑、转换、签署和共享PDF文件。它具备OCR技术,可将扫描文档转换为可编辑文本,同时提供智能PDF处理技术,确保文件完整性和可读性。此外,软件还支持电子…...
Django UpdateView视图
UpdateView是Django中的一个通用视图,用于处理对象的更新操作。它允许用户更新一个已经存在的对象。UpdateView通常与一个模型表单一起使用,这样用户就可以看到当前对象的值,并可以修改它们。 1,添加视图 Test/app3/views.py fr…...
【CS.SE】2024年,你应该选择计算机专业吗?详细分析与未来展望
文章目录 1. 引言1.1 背景介绍 2. 计算机相关专业的现状与挑战2. 计算机相关专业的现状与挑战2.1 行业内的就业趋势2.1.1 现有就业数据2.1.2 行业需求变化 2.2 市场饱和度与竞争2.2.1 毕业生数量增长2.2.2 薪资与职业发展 2.3 技术创新与行业发展2.3.1 新兴技术的发展2.3.2 全球…...
后端开发面经系列 -- 华为OD -- C++面经(全)
华为OD – C面经(全) 公众号:阿Q技术站 文章目录 华为OD -- C面经(全)一面1、C结构体和类的区别,类默认的访问权限,结构体可以定义成员函数吗?2、多态的意义?多态的意义…...
网站首页布局设计工具/最近一周的新闻热点事件
VSCode便捷操作一、扩展包二、快捷键三、结语一、扩展包 汉化包:将软件设为中文模式,插件安装后,软件重启即生效; 扩展栏输入Chinese,全称为Chinese (Simplified) (简体中文) Language Pack for Visual Studio Code双…...
ui设计的网站有哪些/什么关键词能搜到资源
项目最初给的意见是,pc一套前端代码,wap一套代码,并没有说手机端在电脑上打开正常显示的需求。因此,我最后移动端单位用了vw/vh进行布局,没想到的是,需求变了,噩梦开始了! 遇到的问…...
做视频网站怎么挣钱吗/网站优化 秦皇岛
今天開始学习Libevent 。Libevent 是开源社区的一款高性能I/O框架库。主要特点有: 1 跨平台。 2 统一事件源 3 线程安全 4 基于Reactor 今天主要进行了Libevent的安装,以及利用libevent框架编写一个间隔1s打印 Hello Libevent!信息的程序…...
精选南昌网站建设公司/数据分析师培训机构推荐
主要是使用了angular的指令。 学习地址:http://www.runoob.com/angularjs/angularjs-tutorial.html 1. 效果: 输入数据剩余字数会相应减少,点击保存弹出已保存提示信息,点击清空输入数据为空。 <!DOCTYPE html> <html lang"en&…...
沐风+wordpress+主题/seo外链优化策略
夏天就是不好,穷的时候我连西北风都没得喝...
网站建设需要的费用/如何优化关键词的方法
Linux开发板 - 01 - 远程通讯/控制(SSH/VNC/FTP) 前言:最近向学校实验室借了一块Linux开发板(我一开始还以为是树莓派,本来想借树莓派的),开始接下来一段时间会记录学习过程。我借的是韦东山的J…...