2.8日学习打卡----初学RabbitMQ(三)

2.8日学习打卡

一.springboot整合RabbitMQ

在这里插入图片描述
之前我们使用原生JAVA操作RabbitMQ较为繁琐,接下来我们使用
SpringBoot整合RabbitMQ,简化代码编写

创建SpringBoot项目,引入RabbitMQ起步依赖

<!-- RabbitMQ起步依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starteramqp</artifactId>
</dependency>

编写配置文件

spring:rabbitmq:host: 192.168.66.100port: 5672username: jjypassword: jjyvirtual-host: /
# 日志格式
logging:pattern:console: '%d{HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n'

SpringBoot整合RabbitMQ时,需要在配置类创建队列和交换机,

写法如下:

package com.jjy.springrabbitmqdemo;import org.springframework.amqp.core.*;import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitConfig {private final String EXCHANGE_NAME = "boot_topic_exchange";private final String QUEUE_NAME = "boot_queue";//创建交换机@Bean("bootExchange")public Exchange getExchange(){return ExchangeBuilder.topicExchange(EXCHANGE_NAME)//交换机类型.durable(true)//是否持久化.build();}//创建队列@Bean("bootQueue")public Queue getMessageQueue() {return new Queue(QUEUE_NAME); // 队列名}//交换机绑定队列@Beanpublic Binding bindMessageQueue(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue){return BindingBuilder.bind(queue).to(exchange).with("#.message.#").noargs();}
}

SpringBoot整合RabbitMQ_编写生产者

在这里插入图片描述
SpringBoot整合RabbitMQ时,提供了工具类RabbitTemplate发送
消息,编写生产者时只需要注入RabbitTemplate即可发送消息。

package com.jjy.springrabbitmqdemo;import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class TestProducer {//注入RabbitTemplate工具类@Autowiredprivate RabbitTemplate rabbitTemplate;@Test/*** 发送消息* 参数1:交换机* 参数2:路由key* 参数3:要发送的消息*/public void testSendMessage(){rabbitTemplate.convertAndSend("boot_topic_exchange","message","双十一开始了!");}
}

SpringBoot整合RabbitMQ_编写消费者

在这里插入图片描述

消费者

package com.jjy.rabbitmqcosspring.consumer;import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;//消费者
@Component
public class Consumer {//监听队列@RabbitListener(queues = "boot_queue")public void listenMessage(String message){System.out.println("监听的消息: "+message);}
}

整合后的代码,就是不用自己去实例化(创建连接工厂,连接,信道);让spring容器来控制实例的创建到销毁。
代码的实现有生产者和消费者、还有配置类(创建交换机跟队列及其绑定操作),都独立为一个类(共3个类),yml文件中配置rabbitmq的一些属性。

Direct类型(默认,匹配发送)

它会把消息路由到那些binding key与routing key完全匹配的Queue中。

它是一个一对一的模型,一条消息一定会被发到指定的一个队列(完全匹配)。

配置代码

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitDirectConfig {@Beanpublic Queue directQueue(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("directQueue-One",false,false,false,null);}@Beanpublic Queue directQueue2(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("directQueue-Two",false,false,false,null);}@Beanpublic DirectExchange directExchange(){//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new DirectExchange("MqSendService-One",false,false,null);}@Beanpublic Binding bingExchange(){return BindingBuilder.bind(directQueue())   //绑定队列.to(directExchange())       //队列绑定到哪个交换器.with("One");         //绑定路由key,必须指定}@Beanpublic Binding bingExchange2(){return BindingBuilder.bind(directQueue2())   //绑定队列.to(directExchange())       //队列绑定到哪个交换器.with("Two");         //绑定路由key,必须指定}
}

Topic类型(拓展匹配发送)

它是Direct类型的一种扩展,提供灵活的匹配规则。

  • routing key为一个句点号 " . " 分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如"One.Two"
  • binding key与routing key一样也是句点号 " . " 分隔的字符串
  • binding key中可以存在两种特殊字符 " * " 与 " # " ,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitTopicConfig {@Beanpublic Queue topicQueue(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("topicQueue-One",false,false,false,null);}@Beanpublic Queue topicQueue2(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("topicQueue-Two",false,false,false,null);}@Beanpublic TopicExchange topicExchange(){//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new TopicExchange("Topic-Ex",false,false,null);}@Beanpublic Binding bingExchange(){return BindingBuilder.bind(topicQueue())   //绑定队列.to(topicExchange())       //队列绑定到哪个交换器.with("*.Two.*");        //路由key,必须指定}@Beanpublic Binding bingExchange2(){return BindingBuilder.bind(topicQueue2())   //绑定队列.to(topicExchange())       //队列绑定到哪个交换器.with("#");         //路由key,必须指定}
}

Fanout 类型(广播发送)

它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。

它是一种一对多的类型,无法指定Binding Key,发送的一条消息会被发到绑定的所有队列。

配置代码


import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitFanoutConfig {@Beanpublic Queue fanoutQueue(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("fanoutQueue-One",false,false,false,null);}@Beanpublic Queue fanoutQueue2(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("fanoutQueue-Two",false,false,false,null);}@Beanpublic FanoutExchange fanoutExchange(){//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new FanoutExchange("Fanout-Ex",false,false,null);}@Beanpublic Binding bingExchange(){return BindingBuilder.bind(fanoutQueue())   //绑定队列.to(fanoutExchange());       //队列绑定到哪个交换器}@Beanpublic Binding bingExchange2(){return BindingBuilder.bind(fanoutQueue())   //绑定队列.to(fanoutExchange());       //队列绑定到哪个交换器}}

Headers(键值对匹配,不常用)

headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。

在绑定Queue与Exchange时指定一组键值对;当消息发送到ExchangeRabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。

该类型不常用,暂不提供代码。

Message(消息)

当执行诸如 basicPublish() 之类的操作时,内容作为字节数组参数传递,而其他属性作为单独的参数传入。

public class Message {private final MessageProperties messageProperties;private final byte[] body;public Message(byte[] body, MessageProperties messageProperties) {this.body = body;this.messageProperties = messageProperties;}public byte[] getBody() {return this.body;}public MessageProperties getMessageProperties() {return this.messageProperties;}...
}

MessageProperties 接口定义了几个常见的属性,例如“messageId”“timestamp”、“contentType”等等。 还可以通过调用 setHeader(String key, Object value) 方法扩展这些属性

二. 消息的可靠性投递

在这里插入图片描述
RabbitMQ消息投递的路径为:
生产者 —> 交换机 —> 队列 —> 消费者
在RabbitMQ工作的过程中,每个环节消息都可能传递失败,那么RabbitMQ是如何监听消息是否成功投递的呢?

  • 确认模式(confirm)可以监听消息是否从生产者成功传递到交换机。
  • 退回模式(return)可以监听消息是否从交换机成功传递到队列。
  • 消费者消息确认(Consumer Ack)可以监听消费者是否成功处理消息。

三种模式刚好监听完RabbitMQ的一整套流程。即我们能够由这三种模式得到消息的传递及处理的结果。

确认模式(confirm)

在这里插入图片描述
确认模式(confirm)可以监听消息是否从生产者成功传递到交换机

生产者配置文件开启确认模式

 rabbitmq:host: 192.168.66.100port: 5672username: jjypassword: jjyvirtual-host: /# 开启确认模式publisher-confirm-type: correlated
package com.jjy.rabbitproducer;import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitmqConfig {private final String EXCHNAGE_NAME="my_topic_exchange";private final String QUEUE_NAME="my_queue";@Bean("bootExchange")public Exchange getExchange(){return ExchangeBuilder.topicExchange(EXCHNAGE_NAME)//交换机类型.durable(true).build();}// 2.创建队列@Bean("bootQueue")public Queue getMessageQueue(){return QueueBuilder.durable(QUEUE_NAME) // 队列持久化.build();}@Beanpublic Binding bindMessageQueue(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue){return BindingBuilder.bind(queue).to(exchange).with("my_routing").noargs();}
}
@SpringBootTest
public class ProduceTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void sendMessage(){// 定义确认模式的回调方法,消息向交换机发送后会调用confirm方法rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {/*** 被调用的回调方法* @param correlationData 相关配置信息* @param ack 交换机是否成功收到了消息* @param cause 失败原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {if (ack){System.out.println("confirm接受成功!");}else{System.out.println("confirm接受失败,原因为:"+cause);// 做一些处理。}}});rabbitTemplate.convertAndSend("my_topic_exchange","my_routing","send message...");}
}

退回模式(return)

在这里插入图片描述
退回模式(return)可以监听消息是否从交换机成功传递到队列,
使用方法如下:

生产者配置文件开启退回模式

spring:rabbitmq:host: 192.168.66.100port: 5672username: jjypassword: jjyvirtual-host: /# 开启确认模式publisher-confirm-type: correlated# 开启回退模式publisher-returns: true
package com.jjy.rabbitproducer;import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitmqConfig {private final String EXCHNAGE_NAME="my_topic_exchange";private final String QUEUE_NAME="my_queue";@Bean("bootExchange")public Exchange getExchange(){return ExchangeBuilder.topicExchange(EXCHNAGE_NAME)//交换机类型.durable(true).build();}// 2.创建队列@Bean("bootQueue")public Queue getMessageQueue(){return QueueBuilder.durable(QUEUE_NAME) // 队列持久化.build();}@Beanpublic Binding bindMessageQueue(@Qualifier("bootExchange") Exchange exchange, @Qualifier("bootQueue") Queue queue){return BindingBuilder.bind(queue).to(exchange).with("my_routing").noargs();}
}
@Testpublic void testReturn(){// 定义退回模式的回调方法。交换机发送到队列失败后才会执行returnedMessage方法rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback(){/*** @param returned 失败后将失败信息封装到参数中*/@Overridepublic void returnedMessage(ReturnedMessage returned) {System.out.println("消息对象:"+returned.getMessage());System.out.println("错误码:"+returned.getReplyCode());System.out.println("错误信息:"+returned.getReplyText());System.out.println("交换机:"+returned.getExchange());System.out.println("路由键:"+returned.getRoutingKey());}});rabbitTemplate.convertAndSend("my_topic_exchange","my_routing1","send message...");}

消费者消息确认(Ack)

在这里插入图片描述
在RabbitMQ中,消费者接收到消息后会向队列发送确认签收的消息,只有确认签收的消息才会被移除队列。这种机制称为消费者消息确认(Consumer Acknowledge,简称Ack)。类似快递员派送快递也需要我们签收,否则一直存在于快递公司的系统中。

消息分为自动确认和手动确认。自动确认指消息只要被消费者接收到,无论是否成功处理消息,则自动签收,并将消息从队列中移除。但是在实际开发中,收到消息后可能业务处理出现异常,那么消息就会丢失。此时需要设置手动签收,即在业务处理成功再通知签收消息,如果出现异常,则拒签消息,让消息依然保留在队列当
中。

  • 自动确认:spring.rabbitmq.listener.simple.acknowledge=“none”
  • 手动确认:spring.rabbitmq.listener.simple.acknowledge=“manual”

消费者配置开启手动签收

spring:rabbitmq:host: 192.168.0.162port: 5672username: itbaizhanpassword: itbaizhanvirtual-host: /# 开启手动签收listener:simple:acknowledge-mode: manual
package com.jjy.rabbitconsumer;import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.io.IOException;@Component
public class AckConsumer {
//    @RabbitListener(queues = "my_queue")
//    public void listenMessage(String Mesage){
//        int i=1/0;
//        System.out.println("成功接收到消息:"+Mesage);
//    }
//@RabbitListener(queues = "my_queue")
public void listenMessage(Message message, Channel channel) throws InterruptedException, IOException {//消息投递序号,消息每次投递该值都会+1long deliveryTag = message.getMessageProperties().getDeliveryTag();try{int i=1/0;System.out.println("成功接收到消息:"+message);// 签收消息/*** 参数1:消息投递序号* 参数2:是否一次可以签收多条消息*/channel.basicAck(deliveryTag,true);} catch (Exception e){System.out.println("消息消费失败!");Thread.sleep(2000);// 拒签消息/*** 参数1:消息投递序号* 参数2:是否一次可以拒签多条消息* 参数3:拒签后消息是否重回队列*/channel.basicNack(deliveryTag,true,true);}}}

三.RabbitMQ高级特性

消费端限流

在这里插入图片描述
之前我们讲过MQ可以对请求进行“削峰填谷”,即通过消费端限流的方式限制消息的拉取速度,达到保护消费端的目的。
消费端限流的写法如下:
1 生产者批量发送消息

@Test
public void testSendBatch() {// 发送十条消息for (int i = 0; i < 10; i++) {rabbitTemplate.convertAndSend("my_topic_e
xchange", "my_routing", "send
message..."+i);}
}

2 消费端配置限流机制

spring:rabbitmq:host: 192.168.66.100port: 5672username: jjypassword: jjyvirtual-host: /listener:simple:# 限流机制必须开启手动签收acknowledge-mode: manual# 消费端最多拉取5条消息消费,签收后不满5
条才会继续拉取消息。prefetch: 5

3.消费者监听队列

@Component
public class OosConsimer {//@RabbitListener(queues ="my_queue")public void listenMessage(Message message, Channel channel) throws IOException, InterruptedException {// 1.获取消息System.out.println(new String(message.getBody()));// 2.业务处理Thread.sleep(3000);//3.签收long deliveryTag = message.getMessageProperties().getDeliveryTag();channel.basicAck(deliveryTag,true);}
}

就是说从生产端发送过来的消息,在队列等待消费端接收,如果消费端处理消息业务的速度相对较慢,积累的消息过多从而处理不过来(资源耗尽),会导致系统性能降低或瘫痪。
因为消费端每秒处理消息的条数有限,所以我们需要在消费端进行一个限流,故而限制了队列消息的投递。
即消费端限流也就是限制队列投递到消费端的流,也可以说是在队列与消费端之间进行一个限流。

利用限流实现不公平分发

在这里插入图片描述
在RabbitMQ中,多个消费者监听同一条队列,则队列默认采用的轮询分发。但在某种场景下这种策略并不是很好,例如消费者1处理任务的速度非常快,而其他消费者处理速度却很慢。此时如果采用公平分发,则消费者1有很大一部分时间处于空闲状态。此时可以采用不公平分发,即谁处理的快,谁处理的消息多。

公平分发则不能在yml文件中设置限流(prefetch),公平分发即给多个消费者平分消息进行消费。这样会导致处理快的消费者在等待,故而浪费资源,降低性能。

不公平分发则需要在yml文件中设置限流(prefetch),并且prefetch: 1(即设置为1);不公平分发即每次拉取一条消息,谁处理得快就继续处理,这样可以极大的节约资源,从而提高性能。

1 生产者批量发送消息

@Test
public void testSendBatch() {// 发送十条消息for (int i = 0; i < 10; i++) {rabbitTemplate.convertAndSend("my_topic_e
xchange", "my_routing", "send
message..."+i);}
}

2 消费端配置不公平分发

spring:rabbitmq:host: 192.168.66.100port: 5672username: jjypassword: jjyvirtual-host: /listener:simple:# 限流机制必须开启手动签收acknowledge-mode: manual# 消费端最多拉取1条消息消费,这样谁处理
的快谁拉取下一条消息,实现了不公平分发prefetch: 1

3 编写两个消费者

@Component
public class UnfairConsumer {// 消费者1@RabbitListener(queues = "my_queue")public void listenMessage1(Message message, Channel channel) throws Exception{//1.获取消息System.out.println("消费者1:"+newString(message.getBody(),"UTF-8"));//2. 处理业务逻辑Thread.sleep(500); // 消费者1处理快//3. 手动签收channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);// 消费者2@RabbitListener(queues = "my_queue")public void listenMessage2(Messagemessage, Channel channel) throws Exception{//1.获取消息System.out.println("消费者2:"+newString(message.getBody(),"UTF-8"));//2. 处理业务逻辑Thread.sleep(3000);// 消费者2处理慢//3. 手动签收channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);}}

消息存活时间

在这里插入图片描述
RabbitMQ可以设置消息的存活时间(Time To Live,简称TTL),当消息到达存活时间后还没有被消费,会被移出队列。RabbitMQ可以对队列的所有消息设置存活时间,也可以对某条消息设置存活时间

设置队列所有消息存活时间
就是说需要在配置类(RabbitConfig)中设置队列所有消息的存活时间;

return QueueBuilder.durable(QUEUE_NAME)//队列持久化.ttl(10000)//设置队列的所有消息存活10s.build();

即在创建bean队列时,就要设置队列所有消息的存活时间。

**设置某条消息的存活时间 **

就是说只需要在发送的时候指定它的存活时间即可。
实现比较稍微麻烦一点,创建消息属性并设置存活时间,然后创建消息对象,消息对象 将消息属性作为参数,并且传入发送的消息,最后再将消息对象作为参数传给交换机,即可实现对单条消息设置存活时间。

//发送消息,并设置该消息的存活时间@Testpublic void testSendMessage(){//1.创建消息属性MessageProperties messageProperties = new MessageProperties();//2.设置存活时间messageProperties.setExpiration("10000");//3.创建消息对象Message message = new Message("sendMessage...".getBytes(),messageProperties);//4.发送消息rabbitTemplate.convertAndSend("my_topic_exchange1","my_routing",message);}

若设置中间的消息的存活时间,当过期时,该消息不会被移除,但是该消息已经不会被消费了,需要等到该消息到队里顶端才会被移除。
因为队列是头出,尾进,故而要移除它需要等到它在顶端时才可以。

在队列设置存活时间,也在单条消息设置存活时间,则以时间短的为准。

消息过期后,并不会马上移除消息,只有消息消费到队列顶
端时,才会移除该消息。

 @Testpublic void testSendMessage2() {for (int i = 0; i < 10; i++) {if (i == 5) {// 1.创建消息属性MessageProperties messageProperties = new MessageProperties();// 2.设置存活时间messageProperties.setExpiration("10000 ");// 3.创建消息对象Message message = new Message(("send message..." + i).getBytes(), messageProperties);// 4.发送消息rabbitTemplate.convertAndSend("my_topi c_exchange", "my_routing", message);} else {rabbitTemplate.convertAndSend("my_topi c_exchange", "my_routing", "send message..." + i);}}}

在以上案例中,i=5的消息才有过期时间,10s后消息并没有马上被移除,但该消息已经不会被消费了,当它到达队列顶端时会被移除。

优先级队列

假设在电商系统中有一个订单催付的场景,即客户在一段时间内未付款会给用户推送一条短信提醒,但是系统中分为大型商家和小型商家。比如像苹果,小米这样大商家一年能给我们创造很大的利润,所以在订单量大时,他们的订单必须得到优先处理,此时就需要为不同的消息设置不同的优先级,此时我们要使用优先级队列

优先级队列用法如下:
1 创建队列和交换机


@Configuration
public class RabbitmqConfig3 {private final String EXCHANGE_NAME="priority_exchange";private final String QUEUE_NAME="priority_queue";@Bean(EXCHANGE_NAME)public Exchange priorityExchange(){return  ExchangeBuilder.topicExchange(EXCHANGE_NAME)//交换机类型.durable(true)//是否持久化.build();}@Bean(QUEUE_NAME)public Queue producerQueue(){return QueueBuilder.durable(QUEUE_NAME)//队列持久化//设置队列的最大优先级,最大可以设置到255,官网推荐不要超过10,,如果设置太高比较浪费资源.maxPriority(10).build();}@Beanpublic Binding bindPriority(@Qualifier(EXCHANGE_NAME) Exchange exchange, @Qualifier(QUEUE_NAME) Queue queue){return BindingBuilder.bind(queue).to(exchange).with("my_routing").noargs();}
}

2 编写生产者

 @Testpublic void testPriority() {for (int i = 0; i < 10; i++) {if (i == 5) {// i为5时消息的优先级较高MessageProperties messageProperties = new MessageProperties();messageProperties.setPriority(9);Message message = new Message(("send message..." + i).getBytes(StandardCharsets.UTF_8), messageProperties);rabbitTemplate.convertAndSend("priority_exchange", "my_routing", message);} else {rabbitTemplate.convertAndSend("priority_exchange", "my_routing", "send message..." + i);}}}

3 编写消费者


@Component
public class PriorityConsumer {@RabbitListener(queues = "priority_queue")public void listenMessage(Message message, Channel channel) throws IOException {System.out.println(new String(message.getBody(),"utf-8"));//手动签收channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);}
}

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

在这里插入图片描述

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

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

相关文章

二、OpenAI开发者快速入门

启动并运行OpenAI API OpenAI API 为开发者提供一个简单的接口&#xff0c;使其能够在他们的应用中创建一个智能层&#xff0c;由OpenAI最先进的模型提供支持。聊天补全端点为ChatGPT提示支持&#xff0c;一种简单的方法是&#xff1a;输入文本&#xff0c;使用GPT-4模型输出。…

Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…

项目02《游戏-12-开发》Unity3D

基于 项目02《游戏-11-开发》Unity3D &#xff0c; 任务&#xff1a;实现场景怪物自动巡航 &#xff0c; 首先在场景中创建小球命名为路径点WayPoint0&#xff0c; 取消小球的碰撞器Collider&#xff0c; 再复制两个改名为WayPoint1 和 WayPoint2 &#xff0c; 在…

Android矩阵Matrix动画缩放Bitmap移动手指触点到ImageView中心位置,Kotlin

Android矩阵Matrix动画缩放Bitmap移动手指触点到ImageView中心位置&#xff0c;Kotlin 借鉴 Android双指缩放ScaleGestureDetector检测放大因子大图移动到双指中心点ImageView区域中心&#xff0c;Kotlin&#xff08;2&#xff09;-CSDN博客 在此基础上实现手指在屏幕上点击后&…

微服务OAuth 2.1扩展额外信息到JWT并解析(Spring Security 6)

文章目录 一、简介二、重写UserDetailsService三、Controller解析JWT获取用户信息四、后记 一、简介 VersionJava17SpringCloud2023.0.0SpringBoot3.2.1Spring Authorization Server1.2.1Spring Security6.2.1mysql8.2.0 Spring Authorization Server 使用JWT时&#xff0c;前…

生成式人工智能攻击的一年:2024

趋势科技最近公布了其关于预期最危险威胁的年度研究数据。生成人工智能的广泛可用性和质量将是网络钓鱼攻击和策略发生巨大变化的主要原因。 趋势科技宣布推出“关键可扩展性”&#xff0c;这是著名年度研究的新版本&#xff0c;该研究分析了安全形势并提出了全年将肆虐的网络…

用EXCEL从地址(上海)中提取各区(浦东新区等区)信息

背景&#xff1a; 朋友工作需要经常用EXCEL把各上海用户收货地址中的区提取出来&#xff0c;之前一直手动处理&#xff0c;希望我帮忙用EXCEL公式直接提取处理。 数据样式&#xff1a; 中国上海市浦东新区A小区 上海徐汇区B小区 中国&#xff0c;上海&#xff0c;浦东新区&a…

代码审计-CVE-2023-6654-PHPEMS-加密-解密分析

路由&#xff1a; 入口方法&#xff1a; 鉴权分析&#xff1a; 由此可以得出 鉴权是由session类负责获取参数后&#xff0c;由各个类的魔术方法负责&#xff1a;&#xff08;在此还有一个方法 全局搜索登录关键词&#xff09; 1、断点分析&#xff1a; 寻找鉴权点分析&#…

[神奇代码岛】皮肤功能使用

前言 最近有很多人在制作地图的时候&#xff0c;因该会用到皮肤的功能&#xff0c;但是皮肤操作只知道UI操作&#xff0c;但缺点是&#xff0c;只能设置地图默认皮肤&#xff0c;根本都做不到想要的什么皮肤购买功能&#xff0c;自主穿戴功能&#xff0c;而API官方又放在非常隐…

《剑指 Offer》专项突破版 - 面试题 30 和 31:详解如何设计哈希表以及利用哈希表设计更加高级、复杂的数据结构

目录 一、哈希表的基础知识 二、哈希表的设计 2.1 - 插入、删除和随机访问都是 O(1) 的容器 2.2 - 最近最少使用缓存 一、哈希表的基础知识 哈希表是一种常见的数据结构&#xff0c;在解决算法面试题的时候经常需要用到哈希表。哈希表最大的优点是高效&#xff0c;在哈希表…

1、学习 Eureka 注册中心

学习 Eureka 注册中心 一、创建 Eureka 微服务0、SpringBoot 和 SpringCloud 版本1、引入 Eureka 服务端依赖2、启动类加 EnableEurekaServer 注解3、配置 yaml 文件&#xff0c;把 Eureka 服务注册到 Eureka 注册中心4、访问 Eureka 服务端&#xff0c;查看注册中心的服务列表…

自然语言学习nlp 六

https://www.bilibili.com/video/BV1UG411p7zv?p118 Delta Tuning&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;和机器学习领域中&#xff0c;通常指的是对预训练模型进行微调的一种策略。这种策略不是直接更新整个预训练模型的权重&#xff0c;而是仅针对模型…

Linux 软件管理(YUM RPM)

1 YUM yum&#xff08;全称为 Yellow dog Updater, Modified&#xff09;是一个在Fedora和RedHat以及CentOS中的Shell前端软件包管理器。基于RPM包管理&#xff0c;能够从指定的服务器自动处理依赖性关系&#xff0c;并且一次安装所有依赖的软件包&#xff0c;无须繁琐地一次次…

Aethir和Well-Link Tech携手革新云游戏,释放人工智能(AI)潜力

​Aethir将为Well-Link Tech的2亿用户提供先进的GPU计算能力&#xff0c;大幅提升他们的游戏体验。 新加坡&#xff0c;2024年2月7日 - 先驱性的去中心化GPU网络Aethir与实时云渲染技术领导者Well-Link Tech携手共创云游戏和元宇宙发展的新时代。 借助Well-Link Tech对领先游戏…

[office] Excel2019函数MAXIFS怎么使用?Excel2019函数MAXIFS使用教程 #知识分享#微信#经验分享

Excel2019函数MAXIFS怎么使用&#xff1f;Excel2019函数MAXIFS使用教程 Excel2019函数MAXIFS怎么使用&#xff1f;这篇文章主要介绍了Excel2019函数MAXIFS使用教程,需要的朋友可以参考下 在今年&#xff0c;Excel除了新版本Excel2019&#xff0c;其中有一个新功能MAXIFS函数&am…

初识文件包含漏洞

目录 什么是文件包含漏洞&#xff1f; 文件包含的环境要求 常见的文件包含函数 PHP伪协议 file://协议 php://协议 php://filter php://input zip://、bzip2://、zlib://协议 zip:// bzip2:// zlib:// data://协议 文件包含漏洞演示 案例1&#xff1a;php://inp…

Docker-现代化应用部署的利器

一、容器部署的发展 今天我们来说说容器部署。我们知道容器部署的发展大致分三个阶段&#xff0c;下面来介绍一下不同阶段的部署方式的优缺点 物理机部署 优点是可以提供更高的性能、资源控制&#xff0c;也可以提供更好的数据隔离和安全性&#xff0c;因为不同的应用程序运行在…

从0开始图形学(光栅化)

前言 说起图形学&#xff0c;很多人就会提到OpenGL&#xff0c;但其实两者并不是同一个东西。引入了OpenGL加重了学习的难度和成本&#xff0c;使得一些原理并不直观。可能你知道向量&#xff0c;矩阵&#xff0c;纹理&#xff0c;重心坐标等概念&#xff0c;但就是不知道这些概…

移动端设置position: fixed;固定定位,底部出现一条缝隙,不知原因,欢迎探讨!!!

1、问题 在父盒子中有一个子盒子&#xff0c;父盒子加了固定定位&#xff0c;需要子盒子上下都有要边距&#xff0c;用margin或者padding挤开时&#xff0c;会出现缝隙是子盒子背景颜色的。 测试过了&#xff0c;有些手机型号有&#xff0c;有些没有&#xff0c;微信小程序同移…

vscode +git +gitee 文件管理

文章目录 前言一、gitee是什么&#xff1f;2. Gitee与VScode连接大概步骤 二、在vscode中安装git1.安装git2.安装过程3.安装完后记得重启 三、使用1.新建文件夹first2.vscode 使用 四、连接git1.初始化仓库2.设置git 提交用户和邮箱3.登陆gitee账号新建仓库没有的自己注册一个4…