互联网分布式应用之RabbitMQ

RabbitMQ

Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机,Java 仍是企业和开发人员的首选开发平台。
  

课程内容的介绍

1. RabbitMQ介绍安装
2. RabbitMQ交换器
3. RabbitMQ高级
  

一、RabbitMQ介绍安装

1. AMQP 简介
AMQP (Advanced Message Queuing Protocol ,高级消息队列协议)是 个线路层的协议规范,而不是 API 规范(例如 JMS )。由于AMQP 是一个线路层协议规范,因此它天然就是跨平台的,就像 SMTP HTTP 等协议 样,只要开发者按照规范的格式发送数据,任何平台都可以通过 AMQP进行消息交互。像目前流行的 StormMQ RabbitMQ 等都实现了 AMQP。
   
2. RabbitMQ简介
RabbitMQ 一个实现了 AMQP 的开源消息中间件,使用高性能的 Erlang 编写。 RabbitMQ有可靠性、支持多种协议、高可用、支持消息集群以及多语言客户端等特点,在分布式系统中存储转发消息,具有不错的性能表现。
  
为什么要使用 RabbitMQ?他解决了什么问题?
现在的市面上有很多MQ可以选择,比如ActiveMQ、ZeroMQ、Appche Qpid,那问题来了为什么要选择RabbitMQ?
除了Qpid,RabbitMQ是唯一一个实现了AMQP标准的消息服务器;
可靠性,RabbitMQ的持久化支持,保证了消息的稳定性;
高并发,RabbitMQ使用了Erlang开发语言,Erlang是为电话交换机开发的语言,天生自带高并发光环,和高可用特性;
集群部署简单,正是应为Erlang使得RabbitMQ集群部署变的超级简单;
社区活跃度高,根据网上资料来看,RabbitMQ也是首选;

  

  

  

    

    
3. 消息队列基础知识
3.1 Provider
消息生产者,就是投递消息的程序。
  
3.2 Consumer
消息消费者,就是接受消息的程序。
 
3.3 非消息队列

  
3.4 使用消息队列

  
3.5 什么是队列?
队列就像存放了商品的仓库或者商店,是生产商品的工厂和购买商品的用户之间的中转站。
   
3.6 队列里存储了什么?
在 rabbitMQ 中,信息流从你的应用程序出发,来到 Rabbitmq 的队列,所有信息可以只存储在一个队列中。队列可以存储很多信息,因为它基本上是一个无限制的缓冲区,前提是你的机器有足够的存储空间。
  
3.7 队列和应用程序的关系?
多个生产者可以将消息发送到同一个队列中,多个消息者也可以只从同一个队列接收数据。
  
4. RabbitMQ安装
因为在Linux上面直接安装RabbitMQ比较复杂,而且容易出错,所以我们通过Docker来快速的安装我们的RabbitMQ。
 
4.1 查找镜像
docker search rabbitmq:management
  

  
4.2 拉取镜像
docker pull macintoshplus/rabbitmq-management
   

  
4.3 查看镜像
docker images
  

  
4.4 创建容器
docker run -d --hostname bobo01 --name rabbitmq -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest -p 15672:15672 -p 5672:5672 c20
  

  
4.5 查看容器
docker ps -a
  
4.6 访问测试
http://192.168.100.120:15672/

  

  
这就表示RabbitMQ安装成功并且访问成功了!
  
5.入门案例
5.1 创建项目
创建一个SpringBoot项目,并引入相关的依赖即可。
    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency></dependencies>
  
5.2 配置文件
我们需要在application.properties中添加RabbitMQ的相关的配置信息。
spring.application.name=rabbitmq-demo01
spring.rabbitmq.host=192.168.100.120
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest# 自定义一个属性 设置 队列的名称
mq.queue.name=hello-queue
 
5.3 队列配置文件
package com.bobo.config;import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class QueueConfig {@Value("${mq.queue.name}")private String queueName;@Beanpublic Queue createQueue(){return new Queue(queueName);}
}
   
5.4 消费者
package com.bobo.consumer;import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;/*** 消费者*/
@Component
public class Receiver {/*** 接收消息,然后处理消息*/@RabbitListener(queues = {"${mq.queue.name}"})public void process(String msg){// 处理消息System.out.println("recevier: " + msg);}
}
  
5.5 生产者
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** 消息的提供者**/
@Component
public class Sender {@Autowiredprivate AmqpTemplate template;@Value("${mq.queue.name}")private String queueName;/*** 发送消息的方法*/public void send(String msg){// 队列名称  消息内容template.convertAndSend(queueName,msg);}}
  
5.6 测试
首先启动服务,消费者处于监听状态。

   
启动后可以在RabbitMQ的服务中看到对应的队列和消费者信息。

    
通过单元测试来发送消息。
package com.bobo;import com.bobo.provider.Sender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class RabbitmqDemo01ApplicationTests {@Autowiredprivate Sender sender;@Testvoid contextLoads() {sender.send("你好啊....");}}
  

  
6. RabbitMQ原理介绍
6.1 原理图

  

  
6.2 概念介绍
1.Message
消息。消息是不具名的,它由消息头消息体组成。消息体是不透明的,而消息头则由一系列可选属性组成,这些属性包括:routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出消息可能持久性存储)等。
  
2.Publisher
消息的生产者。也是一个向交换器发布消息的客户端应用程序。
  
3.Consumer
消息的消费者。表示一个从消息队列中取得消息的客户端应用程序。
  
4.Exchange
交换器。用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
三种常用的交换器类型
1. direct(发布与订阅 完全匹配)
2. fanout(广播)
3. topic(主题,规则匹配)
  
5.Binding
绑定。用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
  
6.Queue
消息队列。用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者链接到这个队列将其取走。
  
7.Routing-key
路由键。RabbitMQ 决定消息该投递到哪个队列的规则。队列通过路由键绑定到交换器。消息发送到 MQ 服务器时,消息将拥有一个路由键,即便是空的 ,RabbitMQ 也会将其和绑定使用的路由键进行匹配。如果相匹配,消息将会投递到该队列。如果不匹配,消息将会进入黑洞。
  
8.Connection
链接。指 rabbit 服务器和服务建立的 TCP 链接。
  
9.Channel
1.Channel 中文叫做信道,是 TCP 里面的虚拟链接。例如:电缆相当于 TCP,信道是一个独立光纤束,一条 TCP 连接上创建多条信道是没有问题的。
2.TCP 一旦打开,就会创建 AMQP 信道。
3.无论是发布消息、接收消息、订阅队列,这些动作都是通过信道完成的。
  
10.Virtual Host
虚拟主机。表示一批交换器,消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是AMQP 概念的基础,必须在链接时指定,RabbitMQ 默认的 vhost 是/
11.Borker
表示消息队列服务器实体。
  
交换器和队列的关系
交换器是通过路由键和队列绑定在一起的,如果消息拥有的路由键跟队列和交换器的路由键匹配,那么消息就会被路由到该绑定的队列中。也就是说,消息到队列的过程中,消息首先会经过交换器,接下来交换器在通过路由键匹配分发消息到具体的队列中。路由键可以理解为匹配的规则。
  
RabbitMQ 为什么需要信道?为什么不是 TCP 直接通信?
1. TCP 的创建和销毁开销特别大。创建需要 3 次握手,销毁需要 4 次分手。
2. 如果不用信道,那应用程序就会以 TCP 链接 Rabbit,高峰时每秒成千上万条链接会造成资源巨大的浪费,而且操作系统每秒处理 TCP 链接数也是有限制的,必定造成性能瓶颈。

3. 信道的原理是一条线程一条通道,多条线程多条通道同用一条 TCP 链接。一条 TCP链接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能的瓶颈。
  

二、RabbitMQ交换器

  
1.Direct案例
DirectExchange 路由策略是将消息队列绑定到 DirectExchange 上,当 一条消息到达DirectExchange 时会被转发到与该条消息 routing key 相同的 Queue 上,例如消息队列名为“hello-queue ”,则 routingkey 为“hello-queue ”的消息会被该消息队列接收。

  
1.1 创建消费者
创建项目,并添加依赖。
    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency></dependencies>
  
配置文件
spring.application.name=rabbitmq-demo02
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.100.120# 设置交换器名称
mq.config.exchange=log.direct# info 队列名称
mq.config.queue.info=log.info
# info 路由键
mq.config.queue.info.routing.key=log.info.routing.key# error 队列名称
mq.config.queue.error=log.error
# error 路由键
mq.config.queue.error.routing.key=log.error.routing.key
  
消费者
package com.bobo.consumer;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;/*** info 日志的消费者*    @QueueBinding value 绑定的队列名称**    autoDelete:是否是一个可删除的临时队列*    @Exchange:交换器名称和类型*    key:路由键*/
@Component
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.error}",autoDelete = "false"),exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.DIRECT),key = "${mq.config.queue.error.routing.key}")
)
public class ErrorRecevier {@RabbitHandlerpublic void process(String msg) {System.out.println("error....recevier:" + msg);boolean flag = true;if(flag){System.out.println(1/0);}}
}
package com.bobo.consumer;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;/*** info 日志的消费者*    @QueueBinding value 绑定的队列名称**    autoDelete:是否是一个可删除的临时队列*    @Exchange:交换器名称和类型*    key:路由键*/
@Component
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.info}",autoDelete = "false"),exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.DIRECT),key = "${mq.config.queue.info.routing.key}")
)
public class InfoRecevier {@RabbitHandlerpublic void process(String msg){System.out.println("info....recevier:" + msg);}
}
  
1.2 创建生产者
创建一个SpringBoot项目,添加和上面一样的依赖。
配置文件有区别,不需要添加队列的配置信息。
spring.application.name=rabbitmq-demo03
spring.rabbitmq.password=guest
spring.rabbitmq.username=guest
spring.rabbitmq.port=5672
spring.rabbitmq.host=192.168.100.120# 设置交换器名称
mq.config.exchange=log.direct# info 路由键
mq.config.queue.info.routing.key=log.info.routing.key# error 路由键
mq.config.queue.error.routing.key=log.error.routing.key
  
添加生产者的类
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class Sender {@Autowiredprivate AmqpTemplate template;@Value("${mq.config.exchange}")private String exchange;@Value("${mq.config.queue.error.routing.key}")private String routingKey;public void send(String msg){// 发送消息template.convertAndSend(exchange,routingKey,msg);}
}
package com.bobo;import com.bobo.provider.Sender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class RabbitmqDemo03ApplicationTests {@Autowiredprivate Sender sender;@Testvoid contextLoads() throws Exception{//sender.send("Hello RabbitMQ .... " + 666);}}
  
测试效果

    
2.Topic案例
TopicExchange 是比较复杂也比较灵活的 种路由策略,在TopicExchange 中,Queue 通过routingkey 绑定到 TopicExchange 上,当消息到达 TopicExchange 后,TopicExchange 根据消息的routingkey 消息路由到一个或者多 Queue上,相比direct模式topic会更加的灵活些。

  
package com.bobo.consumer;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;@Component
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.error}",autoDelete = "true"),exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.TOPIC),key = "*.log.error"))
public class ErrorRecevier {@RabbitHandlerpublic void process(String msg){System.out.println("error ... recevier:" + msg);}
}
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class OrderSender {@Autowiredprivate AmqpTemplate template;@Value("${mq.config.exchange}")private String exchange;/*** 发送消息* @param msg*/public void send(String msg){template.convertAndSend(exchange,"Order.log.debug","Order log debug"+msg);template.convertAndSend(exchange,"Order.log.info","Order log info"+msg);template.convertAndSend(exchange,"Order.log.error","Order log error"+msg);template.convertAndSend(exchange,"Order.log.warn","Order log warn"+msg);}
}
  
3.Fanout案例
FanoutExchange 的数据交换策略是把所有到达 FanoutExchang 的消息转发给所有与它绑定的Queue ,在这种策略中, routingkey 将不起任何作用。

  

  
package com.bobo.consumer;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;@Component
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "${mq.config.queue.sms}",autoDelete = "true"),exchange = @Exchange(value = "${mq.config.exchange}",type = ExchangeTypes.FANOUT))
)
public class SmsRecevier {@RabbitHandlerpublic void process(String msg){System.out.println("Sms .... recevider:" + msg);}
}
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class Sender {@Autowiredprivate AmqpTemplate template;@Value("${mq.config.exchange}")private String exchange;/*** 发送消息* @param msg*/public void send(String msg){template.convertAndSend(exchange,"",msg);}
}
  

三、RabbitMQ高级

1. 持久化
消息的可靠性是RabbitMQ的一大特色,RabbitMQ是如何保证消息的可靠性的呢?--> 消息的持久化。
  
创建消费者

  
注意,此时我们需要设置autoDelete=false。

  
创建服务提供者
package com.bobo.provider;import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class Sender {@Autowiredprivate AmqpTemplate template;@Value("${mq.config.exchange}")private String exchange;@Value("${mq.config.queue.error.routing.key}")private String routingKey;public void send(String msg){// 发送消息template.convertAndSend(exchange,routingKey,msg);}
}
   
单元测试
package com.bobo;import com.bobo.provider.Sender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class RabbitmqDemo03ApplicationTests {@Autowiredprivate Sender sender;@Testvoid contextLoads() throws Exception{//for (int i = 0; i < 10000; i++) {Thread.sleep(2000);sender.send("Hello RabbitMQ .... " + i);}}
}
  
当消费者处理了一段时间的消息之后,断开连接,然后消费者再上线我们发现消费者又能够处理掉下线后提供者发送的消息,保证了消息的完整性。

 
autoDelete属性
@Queue:当所有的消费者客户端连接断开后,是否自定删除队列
true:删除,false:不删除
@Exchange:当所有的绑定队列都不再使用时,是否自动删除交换器
true:删除,false:不删除
  
2. ACK确认机制
2.1 什么是ACK
如果消息在处理过程中,消费者的服务器在处理消息时出现了异常,那么可能这条正在处理的消息就没有完成消息消费,数据就会丢失,为了确保数据不会丢失,RabbitMQ支持消息确认机制-ACK。
  
2.2 ACK消息确认机制
ACK(Acknowledge Character)是消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ的,RabbitMQ接收到反馈信息后才会将消息从队列中删除。
1. 如果一个消费者在处理消息出现了网络不稳定,福区群异常等现象,会将消息重新放入队列中。
2. 如果在集群的情况下,RabbitMQ会立即将这个消息推送给这个在线的其他的消费者,这种机制保障了消费者在服务端故障的时候不会丢失任何的数据和任务。
3. 消息永远不会从RabbitMQ中删除:只有当消费者正确发送ACK反馈后,RabbitMQ收到确认后,消息才会从RabbitMQ的服务中删除。
4. 消息的ACK机制默认就是打开的。
  
ACK的验证
在服务端我们给出一个错误。

  
然后我们再去掉错误,发现消息会被正常的消费。

  
ACK的注意事项
如果忘记掉ACK,那么后果会比较严重,当Consumer退出时,Message会一直重复分发,然后RabbitMQ会占用越来越多的内存,由于RabbitMQ会长时间的运行,因此这个 内存泄漏 是致命的,我们可以通过设置重试次数来防止这个问题,在Consumer的application.properties中设置如下参数。
spring.rabbitmq.listener.simple.retry.enabled=true
## 设置重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
## 重试的间隔时间
spring.rabbitmq.listener.simple.retry.initial-interval=5000

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

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

相关文章

【C#】知识点实践序列之Lock的输出多线程信息

大家好&#xff0c;我是全栈小5&#xff0c;欢迎来到《小5讲堂之知识点实践序列》文章。 2024年第2篇文章&#xff0c;此篇文章是C#知识点实践序列之Lock知识点&#xff0c;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 本篇在Lock锁定代码…

P1019 [NOIP2000 提高组] 单词接龙

网址如下&#xff1a;P1019 [NOIP2000 提高组] 单词接龙 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 很怪&#xff0c;不知道该说什么 我试了题目给的第一个测试点的输入&#xff0c;发现输出和测试点的一样&#xff0c;但是还是WA 不是很懂为什么 有没有大佬帮我看一下…

20231228在Firefly的AIO-3399J开发板的Android11使用Firefly的DTS配置单前后摄像头ov13850

20231228在Firefly的AIO-3399J开发板的Android11使用Firefly的DTS配置单前后摄像头ov13850 2023/12/28 19:20 缘起&#xff0c;突然发现只能打开前置的ov13850&#xff0c;或者后置的ov13850。 但是不能切换&#xff01; 【SDK&#xff1a;rk3399-android-11-r20211216.tar.xz】…

Java-replaceAll()同时替换多个字符

今天复现了raplaceAll&#xff08;&#xff09;的用法&#xff0c;但是通常都是对一种字符进行替换&#xff0c;我就在想有没有操作可以一次性替换多个不同的字符&#xff0c;百度一搜&#xff0c;果然有。具体情况如下 首先是替换字的 String str1 "小明&#xff0c;小…

Vue - 多行文本“展开、收起”功能

TextClamp 使用 js 实现文本展开、收起&#xff0c;并非纯 CSS 实现。 Props&#xff1a; fontSize&#xff1a;Number&#xff0c;默认&#xff1a;14lines&#xff1a;Number&#xff0c;默认&#xff1a;1lineHeight&#xff1a;Number&#xff0c;默认&#xff1a;20 F…

双碳管理系统任务需求分析(第10套)

需求规格说明书 一、引言 &#xff08;一&#xff09;项目背景 编写本需求规格说明书的目的是为了详细呈现碳足迹产品需求和系统的功能描述&#xff0c;以进一步定制应用软件系统开发的细节问题&#xff0c;便于与项目开发协调工作。本文档面向的读者主要是项目委托单位的管…

vivado 管理宏

管理宏 宏存储为XDC约束。根据定义&#xff0c;它们是Tcl命令。这允许要在XDC约束文件和Tcl脚本中使用并交互使用的宏。宏是使用write_xdc命令编写的。使用read_xdc读取宏命令-cell选项可用于将作用域限制为特定的单元格。-cell选项特别适用于将一个宏的相对放置应用到不同层次…

4.快速实现增删改查,模糊查询功能

打开springboot项目&#xff0c;在com.example下建包common,在common下新建Result.java 4.1封装统一的返回数据结构 1.在Result.java中编写如下代码&#xff1a; private static final String *SUCCESS*"0"; private static final String *ERROR*"-1"; p…

ROS学习笔记(8)进一步深入了解ROS第二步

0.前提 在上一讲中我提到过该系列是基于宾夕法尼亚大学工程学院的ROS公开课&#xff0c;系列文章将来源于公开课中的课后习题。该系列可以很好的帮助大家更加深入的了解ROS的一些概念。&#xff08;有效面对HR的提问。&#xff09; 1. (C)What is a nodehandle object? Can we…

项目经验简单总结

引擎 unity 2020 语言 C# lua python(用于工具链) java (用于SDK对接) js&#xff08;PC WEB SDK对接&#xff09; 编辑器 VS VSCODE IDEA eclipse 项目开发模块规划分 主项目工程&#xff0c;UI资源项目工程&#xff0c;模型场景资源项目工程 主项目工程&#xff1a;所有的…

Excel模板填充:从minio上获取模板使用easyExcel填充

最近工作中有个excel导出的功能&#xff0c;要求导出的模板和客户提供的模板一致&#xff0c;而客户提供的模板有着复杂的表头和独特列表风格&#xff0c;像以往使用poi去画是非常耗时间的&#xff0c;比如需要考虑字体大小&#xff0c;单元格合并&#xff0c;单元格的格式等问…

vue-打包

打包的作用 说明&#xff1a;vue脚手架只是开发过程中&#xff0c;协助开发的工具&#xff0c;当真正开发完了>脚手架不参与上线 打包的作用&#xff1a; 1&#xff09;将多个文件压缩合并成一个文件 2&#xff09;语法降级 3&#xff09;less sass ts语法解析 打包后…

liunx操作系统基础及进阶

一、基础入门 1、Linux系统简介 什么是Liunx&#xff1f; Linux在设计之初&#xff0c;是一个基于POSIX的多用户、多任务并且支持多线程和多CPU的操作系统&#xff0c;它是由世界各地成千上万的程序员设计和开发实现&#xff1b; 在当今社会&#xff0c;Linux 系统主要被应…

【智慧零售】东胜物联蓝牙网关硬件解决方案,促进零售门店数字化管理

依托物联网&#xff08;IoT&#xff09;、大数据、人工智能&#xff08;AI&#xff09;等快速发展&#xff0c;数字化和智能化已成为零售企业的核心竞争力。更多的企业通过引入人工智能、大数据等先进技术手段&#xff0c;提高门店运营效率和服务质量。 某连锁咖啡企业牢牢抓住…

[嵌入式C][入门篇] 快速掌握基础(9个语句)

开发环境&#xff1a; 网页版&#xff1a;跳转本地开发(Vscode)&#xff1a;跳转 文章目录 一、基础语法&#xff08;1&#xff09;if (如果)示例1: 普通使用 if示例2: 带否则 else示例3: 否则如果 else if &#xff08;2&#xff09;switch case (选择)规则示例1: &#xff0…

谷歌浏览器 模拟定位

注意事项&#xff1a; 如果要清除位置信息&#xff0c;需将Geolocation修改为No override模拟定位之后需要刷新页面&#xff0c;网页才会生效如果模拟定位&#xff0c;一段时间没有操作&#xff0c;就会清空模拟定位&#xff0c;类似于No override

ubuntu远程桌面连接之novnc

一、前言 该操作是为了实现vnc桌面连接为url连接方式&#xff0c;且在浏览器中可以对ubuntu进行操作。在使用novnc进行操作前&#xff0c;需要先安装vnc才可。ubuntu下如何安装vnc&#xff0c;可看博主前面写的一篇文&#xff0c;ubuntu远程桌面连接之vnc-CSDN博客&#xff0c;…

案例074:基于微信小程序的儿童预防接种预约管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder …

物流实时数仓:数仓搭建(DWS)一

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 物流实时数仓&#xff1a;数仓搭建&#xff08;DIM&#xff09; 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&#xff09;一 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&am…

22款奔驰GLE450升级香氛负离子 车载香薰

相信大家都知道&#xff0c;奔驰自从研发出香氛负离子系统后&#xff0c;一直都受广大奔驰车主的追捧&#xff0c;香氛负离子不仅可以散发出清香淡雅的香气外&#xff0c;还可以对车内的空气进行过滤&#xff0c;使车内的有害气味通过负离子进行过滤&#xff0c;达到车内保持清…