03-SpringAMQP入門

来源:https://www.cnblogs.com/OnlyOnYourself-lzw/archive/2022/07/18/16491791.html
-Advertisement-
Play Games

三、SpringAMQP SpringAMQP是基於RabbitMQ封裝的一套模板,並且還利用SpringBoot對其實現了自動裝配,使用起來非常方便 SpringAMQP的官方地址 https://spring.io/projects/spring-amqp AMQP Spring AMQP Sp ...


三、SpringAMQP

SpringAMQP是基於RabbitMQ封裝的一套模板,並且還利用SpringBoot對其實現了自動裝配,使用起來非常方便

SpringAMQP的官方地址

AMQP

Spring AMQP

SpringAMQP提供了三個功能

  • 自動聲明隊列、交換機及其綁定關係
  • 基於註解的監聽模式,非同步接收消息
  • 封裝了RabbitTemplate工具,用於發送消息

3.1、Basic Queue 簡單隊列模型

在父工程引入依賴

  • <!--AMQP依賴,包含RabbitMQ-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    

3.1.1、消息發送

  • ①、配置MQ地址,在publisher服務的application.yml中添加配置

    • spring:
        rabbitmq:
          host: 192.168.222.135 # 主機名
          port: 5672  # 埠號
          virtual-host: /coolman # 虛擬主機
          username: root  # 用戶名
          password: root  # 密碼
      
  • ②、在publisher服務中編寫測試類,並利用RabbitTemplate實現消息發送

    • package cn.coolman.mq.springamqptest;
      
      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 SimpleProducerTest {
      
          @Autowired
          private RabbitTemplate rabbitTemplate;
      
          @Test
          public void test01() {
              String queueName = "simpleQueue";
              String message = "革命尚未完成,同志仍需努力";
      
              rabbitTemplate.convertAndSend(queueName, message);
              System.out.println("發送簡單消息:【" + message + "】完畢");
          }
      }
      

3.1.2、消息接收

  • ①、配置MQ地址,在consumer服務的application.yml中添加配置

    • spring:
        rabbitmq:
          host: 192.168.222.135 # 主機名
          port: 5672  # 埠號
          virtual-host: /coolman # 虛擬主機
          username: root  # 用戶名
          password: root  # 密碼
      
  • ②、在consumer服務中定義隊列

    • package cn.coolman.mq.config;
      
      import org.springframework.amqp.core.Queue;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class RabbitQueueConfig{
      
          /**
           * 定義隊列,隊列名稱為:simpleQueue
           * @return
           */
          @Bean
          public Queue simpleQueue(){
              return new Queue("simpleQueue");
          }
      }
      
      
  • ③、在consumer服務中創建一個SpringRabbitListener類

    • package cn.coolman.mq.listener;
      
      import org.springframework.amqp.rabbit.annotation.RabbitListener;
      import org.springframework.stereotype.Component;
      
      
      @Component
      public class SimpleListener {
      
          /**
           * 方法的調用時機:如果隊列中一旦出現了消息,馬上就會調用這個方法去處理,並且會把這個消息傳遞給message參數
           *  @RabbitListener 指定監聽的隊列的名稱
           * @param message
           */
          @RabbitListener(queues = "simpleQueue")
          public void listener1(String message) {
              System.out.println("監聽器聽到的消息:【"+ message + "】");
          }
      }
      
      

3.1.3、測試

  • 啟動consumer服務,然後在publisher服務中運行測試代碼,發送MQ消息

3.2、Work Queue 工作隊列模型

Work Queues,也杯稱為(Task Queues),任務模型

  • 簡單來說就是讓多個消費者綁定到一個隊列,共同消費隊列中的消息

當消息處理比較耗時的時候,可能生產消息的速度會遠遠大於消費的速度。

長此以往,消息就會堆積越來越多,無法及時處理。

此時就可以使用work模型,多個消費者共同處理消息,速度就能大大提高

3.2.1、消息發送

  • 這次我們迴圈發送,模擬大量消息堆積現象

  • 在publisher服務中的SpringAmqpTest類中添加一個測試方法

    •     @Test
          public void testWorkQueue() {
              String queueName = "workQueue";
              String message = "good news";
      
              for (int i = 1; i < 11; i++) {
                  rabbitTemplate.convertAndSend(queueName, message + "\t" + i);
                  System.out.println("發送簡單消息:【" + message + "\t" + i + "】完畢");
              }
          }
      

3.2.2、消息接收

  • 修改RabbitQueueConfig類,將隊列名稱改為workQueue

    • package cn.coolman.mq.config;
      
      import org.springframework.amqp.core.Queue;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class RabbitQueueConfig{
      
          /**
           * 定義隊列,隊列名稱為:simpleQueue
           * @return
           */
      //    @Bean
      //    public Queue simpleQueue(){
      //        return new Queue("simpleQueue");
      //    }
      
          /**
           * 定義隊列,隊列名稱為:workQueue
           * @return
           */
          @Bean
          public Queue workQueue(){
              return new Queue("workQueue");
          }
      }
      
  • 要模擬多個消費者綁定同一個隊列,我們在consumer服務的SpringRabbitListener中添加1個新的方法,這兩個方法綁定同一個隊列

    • package cn.coolman.mq.listener;
      
      import org.springframework.amqp.rabbit.annotation.RabbitListener;
      import org.springframework.stereotype.Component;
      
      
      @Component
      public class SimpleListener {
      
          /**
           * 方法的調用時機:如果隊列中一旦出現了消息,馬上就會調用這個方法去處理,並且會把這個消息傳遞給message參數
           *  @RabbitListener 指定監聽的隊列的名稱
           * @param message
           */
          @RabbitListener(queues = "workQueue")
          public void listener01(String message) {
              System.out.println("監聽器01聽到的消息:【"+ message + "】");
          }
      
          @RabbitListener(queues = "workQueue")
          public void listener02(String message) throws InterruptedException {
              System.out.println("監聽器02聽到的消息:【"+ message + "】");
      
              Thread.sleep(10000);
          }
      }
      
      
    • Listener02這個消費者sleep了10秒,模擬任務耗時

3.2.3、測試

  • 啟動ConsumerApplication後,執行publisher服務中剛編寫的發送測試方法testWorkQueue
  • 可以看到,我們發佈的10條消息,居然平均分配給了兩個消費者,要知道我們其中一個消費者可是每次都睡眠了10秒鐘
  • 這並沒有考慮到消費者的處理能力,顯然是有問題的

3.2.4、任務策略

能者多勞

  • 在Spring中有個簡單的配置,可以解決這個問題

  • 修改consumer服務的application.yml文件,添加配置

  • spring:
      rabbitmq:
        listener:
          simple:
            prefetch: 1   # 每次只能獲取一條消息,處理完成才能後去下一個消息
    
  • 再進行測試

3.2.5、小結

  • Work Queue模型的使用
    • 多個消費者綁定到一個隊列,同一條消息只會被一個消費者處理
    • 通過設置prefetch來控制消費者預取的消息數量

3.3、Publish/Subscribe 發佈/訂閱模型

  • 發佈/訂閱的模型如圖所示
  • 可以看到,在發佈/訂閱模型中,多了一個exchange角色,而且過程略有變化
    • Publisher:生產者,也就是要發送消息的程式,但是不再發送到隊列中,而是發給exchange(交換機)
    • Exchange:交換機。一方面,接收生產者發送的消息;另一方面,知道如何處理消息,例如遞交給某個特別隊列、遞交給所有隊列、或者是將消息丟棄
      • 到底如何操作,取決與Exchange的類型;Exchange有以下3中類型
        • Fanout:廣播,將消息交給所有綁定到交換機的隊列
        • Direct:定向,把消息交給符合指定routing key的隊列
        • Topic:通配符,把消息交給符合routing pattern(路由模式)的隊列
      • Consumer:消費者,與以前一樣,訂閱隊列,沒有變化
      • Queue:消息隊列也與以前一樣,接收消息、緩存消息
  • PS:
    • Exchange(交換機)只負責轉發消息,不具備存儲消息的能力
    • 因此如果沒有任何隊列與Exchange綁定,或者沒有符合路由規則的隊列,那麼消息會丟失

3.3.1、Fanout 模型

  • Fanout,中文意思是扇出,一般稱為廣播
  • 在廣播模式下,消息發送流程如下所示
    • 1)可以有很多個隊列
    • 2)每個隊列都要綁定到Exchange(交換機)
    • 3)生產者發送的消息,只能發送到交換機,交換機來決定要發給哪個隊列,生產者無法決定
    • 4)交換機把消息發送給綁定過的所有隊列
    • 5)訂閱隊列的消費者都能拿到消息

①、聲明隊列和交換機

  • Spring提供了一個介面Exchange,來表示所有不同類型的交換機

  • 在consumer中創建一個類,聲明隊列和交換機

    • package cn.coolman.mq.config;
      
      import org.springframework.amqp.core.Binding;
      import org.springframework.amqp.core.BindingBuilder;
      import org.springframework.amqp.core.FanoutExchange;
      import org.springframework.amqp.core.Queue;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      
      @Configuration
      public class RabbitQueueConfig{
      
          //    ========================定義發佈訂閱模式的交換機與隊列,並且把隊列綁定到交換機上=================================
      
          /**
           * 定義fanout的交換機
           * @return
           */
          @Bean // 這裡的返回值對象已經存儲到spring的容器中。 key   方法名: new FanoutExchange("fanoutExchange")
          public FanoutExchange fanoutExchange() {
              return new FanoutExchange("fanoutExchange");
          }
      
          /**
           * 定義一個隊列
           */
          @Bean
          public Queue fanoutQueue01() {
              return new Queue("fanoutQueue01");
          }
      
          /**
           * 定義一個隊列
           */
          @Bean
          public Queue fanoutQueue02() {
              return new Queue("fanoutQueue02");
          }
      
          /**
           * 把隊列綁定到交換機上
           */
          @Bean
          public Binding bindingQueueToExchange01(FanoutExchange fanoutExchange, Queue fanoutQueue01) {
              return BindingBuilder.bind(fanoutQueue01).to(fanoutExchange);
          }
      
          /**
           * 把隊列綁定到交換機上
           */
          @Bean
          public Binding bindingQueueToExchange02(FanoutExchange fanoutExchange, Queue fanoutQueue02) {
              return BindingBuilder.bind(fanoutQueue02).to(fanoutExchange);
          }
      
      }
      
      

②、消息發送

  • 在publisher服務的SpringAMQPTest類中添加測試方法

    •     @Test
          public void testFanoutQueue() {
              String exchangeName = "fanoutExchange";
              String message = "嘿嘿嘿嘿嘿嘿,fanout,i am coming ";
              
              rabbitTemplate.convertAndSend(exchangeName, "", message);
              System.out.println("發送成功!");
          }
      

③、消息接收

  • 在consumer服務的SpringRabbitListener中添加兩個方法,作為消費者

    • package cn.coolman.mq.listener;
      
      
      import org.springframework.amqp.rabbit.annotation.RabbitListener;
      import org.springframework.stereotype.Component;
      
      @Component
      public class FanoutListener {
      
          /**
           * 方法的調用時機:如果隊列中一旦出現了消息,馬上就會調用這個方法去處理,並且會把這個消息傳遞給message參數
           *  @RabbitListener 指定監聽的隊列的名稱
           * @param message
           */
          @RabbitListener(queues = "fanoutQueue01")
          public void listener01(String message) {
              System.out.println("監聽器01聽到的消息:【"+ message + "】");
          }
      
          @RabbitListener(queues = "fanoutQueue02")
          public void listener02(String message) throws InterruptedException {
              System.out.println("監聽器02聽到的消息:【"+ message + "】");
          }
      }
      
      
  • 運行結果如下所示

    • 由此可見,FanoutExchange會將消息無條件轉發給每個綁定的隊列
    • 即routekey為空:rabbitTemplate.convertAndSend(exchangeName, "", message);

④、小結

  • 交換機的作用
    • 接收publisher發送的消息
    • 將消息按照規則路由到與之綁定的隊列
    • 不能緩存消息,路由失敗,消息丟失
    • FanoutExchange的會將消息路由到每個綁定的隊列
  • 聲明隊列、交換機、綁定關係的Bean是什麼
    • Queue
    • FanoutExchange
    • Binding

3.3.2、Direct 模型

  • 在Fanout模式中,一條消息,會被所有訂閱的隊列消費。但是,在某些場景下,我們希望不同的消息被不同的隊列消費。這時候就要用到Direct類型的Exchange
  • 在Direct模型中
    • 隊列與交換機的綁定,不能是任意綁定了,而是要指定一個RoutingKey(路由Key)
    • Pulisher在向Excahnge發送消息的時候,也必須指定消息的RoutingKey
    • Exchange不再把消息交給每一個綁定的隊列,而是根據消息的RoutingKey在進行判斷,只有隊列的RoutingKey與消息的RoutingKey完全一致,才會接收到消息

①、基於註解聲明隊列和交換機

  • 基於@Bean的方式聲明隊列和交換機比較麻煩,Spring還提供了基於註解的方式來聲明

  • 在consumer的SpringRabbitListener中添加兩個消費者,同時基於註解來聲明隊列和交換機

    • package cn.coolman.mq.listener;
      
      import org.springframework.amqp.core.ExchangeTypes;
      import org.springframework.amqp.rabbit.annotation.Exchange;
      import org.springframework.amqp.rabbit.annotation.Queue;
      import org.springframework.amqp.rabbit.annotation.QueueBinding;
      import org.springframework.amqp.rabbit.annotation.RabbitListener;
      import org.springframework.stereotype.Component;
      
      @Component
      public class DirectListener {
      
          @RabbitListener(
                  bindings = @QueueBinding(
                          exchange = @Exchange(
                                  name = "directExchange", type = ExchangeTypes.DIRECT
                          ),
                          value = @Queue("directQueue01"),
                          key = "pig")
          )
          public void listener01(String message) {
              System.out.println("監聽器01聽到的消息:【"+ message + "】");
          }
      
          @RabbitListener(
                  bindings = @QueueBinding(
                          exchange = @Exchange(
                                  name = "directExchange", type = ExchangeTypes.DIRECT
                          ),
                          value = @Queue("directQueue02"),
                          key = "dog")
          )
          public void listener02(String message) {
              System.out.println("監聽器02聽到的消息:【"+ message + "】");
          }
      }
      
      

②、消息發送

  • 在publisher服務的SpringAMQPTest類中添加測試方法

    • @Test
          public void testDirectQueue() {
              String exchangeName = "directExchange";
              String key1 = "pig";
              String key2 = "dog";
      
              String message = "I am a ";
      
              rabbitTemplate.convertAndSend(exchangeName, key1, message + key1);
              System.out.println("向directExchange交換機發送了路由key為【" + key1 + "】的消息【" + message + key1 + "】完畢!");
      
              rabbitTemplate.convertAndSend(exchangeName, key2, message + key2);
              System.out.println("向directExchange交換機發送了路由key為【" + key2 + "】的消息【" + message + key2 + "】完畢!");
      
          }
      
      
  • 運行結果如下所示

    • 可以明顯看到,DirectExchange是根據routekey來分發消息的

③、總結

  • Direct交換機與Fanout交換機的差異
    • Fanout交換機將消息路由給每一個與之綁定的隊列
    • Direct交換機根據RoutingKey判斷路由給哪個隊列
    • 如果多個隊列具有相同的RoutingKey,則與Fanout功能類似
  • 基於@RabbitListener註解聲明隊列和交換機有那些常見的註解
    • @Queue
    • @Exchange

3.3.3、Topic 模型

  • Topic模型的Exchange與Direct模型相比,都是可以根據RoutingKey把消息路由到不同的隊列
  • 只不過Topic模型的Exchange可以讓隊列在綁定RoutingKey的時候使用通配符
  • RoutingKey一般是由一個或多個單片語成,多個單詞之間以.號分割,例如item.insert
  • 通配符規則
    • #:匹配一個或多個詞
    • *:匹配不多不少,恰好一個詞
  • 舉例
  • item.#:表示能夠匹配item.spu.insert或者item.spu
  • item.*:只能匹配item.spu
  • 再比如下圖所示
  • Queue1:綁定的是china.# ,因此凡是以 china.開頭的routing key 都會被匹配到。包括china.newschina.weather
  • Queue2:綁定的是#.news ,因此凡是以 .news結尾的 routing key 都會被匹配。包括china.newsjapan.news
  • 案例需求
    • ①、利用@RabbitListener聲明Exchange、Queue、RoutingKey
    • ②、在consumer服務中,編寫兩個消費者方法,分別監聽topic.queue1topic.queue2
    • ③、在publisher中編寫測試方法,向topicExchange發送消息

①、消息發送

  •     @Test
        public void testTopicQueue() {
            String exchangeName = "topicExchange";
            String routeKey1 = "coolman.hand";
            String routeKey2 = "coolman.hand.hand.hand";
            String message1 = "世紀之握!!!!";
            String message2 = "究極無敵世紀之握!!!!";
    
            rabbitTemplate.convertAndSend(exchangeName, routeKey1, message1);
            System.out.println("向【topicExchange】交換機發送 routeKey = 【coolman.hand】類型消息:【" + message1 + "】");
    
            rabbitTemplate.convertAndSend(exchangeName, routeKey2, message2);
            System.out.println("向【topicExchange】交換機發送 routeKey = 【coolman.hand.hand.hand】類型消息:【" + message2 + "】");
        }
    

②、消息接收

  • package cn.coolman.mq.listener;
    
    import org.springframework.amqp.core.ExchangeTypes;
    import org.springframework.amqp.rabbit.annotation.Exchange;
    import org.springframework.amqp.rabbit.annotation.Queue;
    import org.springframework.amqp.rabbit.annotation.QueueBinding;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TopicListener {
    
        @RabbitListener(
                bindings = @QueueBinding(
                        exchange = @Exchange(name = "topicExchange", type = ExchangeTypes.TOPIC),
                        value = @Queue("topic.queue1"),
                        key = "coolman.*"
                )
        )
        public void listener01(String message) {
            System.out.println("【routeKey = " + "coolman.*】的監聽器【topic.queue1】接收到消息:【" + message + "】");
        }
    
        @RabbitListener(
                bindings = @QueueBinding(
                        exchange = @Exchange(name = "topicExchange", type = ExchangeTypes.TOPIC),
                        value = @Queue("topic.queue2"),
                        key = "coolman.#"
                )
        )
        public void listener02(String message) {
            System.out.println("【routeKey = " + "coolman.#】的監聽器【topic.queue2】接收到消息:【" + message + "】");
    
        }
    }
    
    
  • 運行結果如下所示

    • 顯然,#號可以匹配一個或多個詞;*號匹配不多不少,恰好一個詞

③、小結

  • Direct交換機和Topic交換機的差別
    • Topic交換機接收的消息RoutingKey必須是多個單詞,以**.**分割
    • Topic交換機與隊列綁定時的bingdingKey可以指定通配符
    • #:代表0個或多個詞
    • *:代表1個詞

3.4、消息轉換器

  • 之前說過,Spring會把發送的消息序列化為位元組發送給MQ,接收消息的時候,還會把位元組反序列化為Java對象
  • 只不過,預設情況下Spring採用的序列化方式是JDK序列化。眾所周知,JDK序列化存在下列問題
    • 數據體積過大
    • 有安全漏洞
    • 可讀性差
  • 接下來我們可以測試一下

3.4.1、測試預設轉換器

  • 我們修改消息發送的代碼,發送一個Map對象

    •     @Test
          public void testDefaultConvert() {
              String queueName = "simple.queue";
      
              // 準備消息
              HashMap<String, Object> message = new HashMap<>();
              message.put("name", "coolman");
              message.put("age", 8);
              message.put("power", "SSS+");
      
              // 發送消息
              rabbitTemplate.convertAndSend(queueName, message);
      
          }
      
  • 停止consumer服務

  • 發送消息後查看RabbitMQ控制台,查看map數據

  • 顯然,JDK序列化方式並不合適;我們希望消息體的體積更小、可讀性更高,因此可以使用JSON方式來做序列化和反序列化

3.4.2、配置JSON轉換器

  • 在publisher和consumer兩個服務中都引入依賴

    • <dependency>
          <groupId>com.fasterxml.jackson.dataformat</groupId>
          <artifactId>jackson-dataformat-xml</artifactId>
          <version>2.9.10</version>
      </dependency>
        <dependency>
                  <groupId>com.fasterxml.jackson.core</groupId>
                  <artifactId>jackson-databind</artifactId>
                  <version>2.9.10.5</version>
      </dependency>
      
  • 配置消息轉換器,在啟動類中添加一個Bean即可,如下所示

    •     /**
           * 消息轉換器
           * @return
           */
          @Bean
          // amqp的MessageConverter
          public MessageConverter jsonMessageConverter() {
              return new Jackson2JsonMessageConverter();
          }
      }
      
      
  • 再次向simple.queue發送消息,再在控制台查看,如下圖所示

  • 顯然,使用JSON進行序列化,可以有效減小體積和提高可讀性


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對文檔樹中的節點們 可以用js進行增刪改查: 增: 1、創建元素:這個元素是不會渲染到頁面上的,它不在DOM中 傳入字元串(標簽的名字) var box=document.createElement("div") 2、添加到文檔樹中,x.appendChild(y) 把y節點對象添加到x節點中 bo ...
  • 巨集 #define命令是C語言中的一個巨集定義命令,它用來將一個標識符定義為一個字元串,該標識符被稱為巨集名,被定義的字元串稱為替換文本. 使用巨集時是簡單的代碼段替換. #define的概念 簡單的巨集定義 #define <巨集名> <字元串> 例: #define PI 3.1415926 註:使用簡單 ...
  • 源碼地址 https://gitee.com/bin-0821/chat-room-demo-go-websocket 關於websocket,上一篇文章講述瞭如何通過websocket進行服務端與客戶端的通信,本篇將會帶領大家把各個websocket進行相互通信,在開始之前,請確保有理解 1 go ...
  • # 流程式控制制練習題 # 一、編程題 1、實現一個課程名稱和課程代號的轉換器:輸入下表中的課程代號,輸出課程的名稱。用戶可以迴圈進行輸入,如果輸入0就退出系統。(**使用****switch +while****迴圈實現**) **課程名稱和課程代號對照表** | **課程名稱** | **課程代碼* ...
  • 看《C++ Primer Plus》時整理的學習筆記,部分內容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,張海龍 袁國忠譯。只做學習記錄用途。 ...
  • # 流程式控制制 學習目標: ~~~txt1. idea安裝與使用2. 流程式控制制if...else結構3. 流程式控制制switch結構4. 流程式控制制迴圈結構5. 流程式控制制關鍵字~~~ # 一、流程式控制制概述 什麼是流程式控制制? 流程式控制制是用來控製程序中各語句執行順序的語法。流程式控制制主要包含: * 順序結構 * ...
  • 一、字典、元組的多重嵌套 例 1:記錄全班學生的成績。 分析:定義一個 SimpleGradebook類, 學生名是字典self._grades的鍵,成績是字典self._grades的值。 class SimpleGradebook(): def __init__(self): self._gra ...
  • Django python網路編程回顧 之前我們介紹過web應用程式和http協議,簡單瞭解過web開發的概念。Web應用程式的本質 接收並解析HTTP請求,獲取具體的請求信息 處理本次HTTP請求,即完成本次請求的業務邏輯處理 構造並返回處理結果——HTTP響應 import socket ser ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...