支持JDK19虛擬線程的web框架,之二:完整開發一個支持虛擬線程的quarkus應用

来源:https://www.cnblogs.com/bolingcavalry/archive/2023/09/14/17689826.html
-Advertisement-
Play Games

本篇咱們從零開發一個quarkus應用,支持虛擬線程響應web服務,響應式操作postgresql資料庫,並且在quarkus官方還未支持的情況下,率先並將其製作成docker鏡像 ...


歡迎訪問我的GitHub

這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 本篇是《支持JDK19虛擬線程的web框架》系列的中篇,前文咱們體驗了有虛擬線程支持的web服務,經過測試,發現性能上它與其他兩種常見web架構並無明顯區別,既然如此,還有必要研究和學習嗎?
  • 當然有必要,而且還要通過實戰更深入瞭解虛擬線程與常規線程的區別,在各大框架和庫廣泛支持虛擬線程之前,打好理論和實踐基礎,這才是本系列的目標
  • 為了接下來的深入瞭解,咱們先在本篇打好基礎:詳細說明前文的web功能是如何開發出來的
  • 為了突出重點,這裡先提前劇透,從編碼的角度說清楚如何開啟虛擬線程支持,其實非常簡單,如下圖,左側是quarkus框架下的一個普通web服務,每收到一個web請求,是由線程池中的線程負責響應的,右側的web服務多了個@RunOnVirtualThread註解,就變成了由新建的虛擬線程去處理web請求,沒錯,在quarkus框架下使用虛擬線程就是這麼簡單

image-20221019081651928

  • 在前文中,我們通過返回值也看到了上述兩個web服務中,負責web響應的線程的不同,如下所示,從線程名稱上很容易看出線程池和虛擬線程的區別

image-20221019083324043

  • 看到這裡,您可能會說:就這?一個註解就搞定的事情,你還要寫一篇文章?這不是在浪費作者你自己和各位讀者的時間嗎?

  • 確實,開啟虛擬線程,編碼只要一行,然而就目前而言,虛擬線程是JDK19專屬,而且還只是預覽功能,要想在實際運行的時候真正開啟並不容易,需要從JDK、maven、IDE等方方面面都要做相關設置,而且如果要做成前文那樣的docker鏡像,一行docker run命令就能開啟虛擬線程,還要在Dockerfile上做點事情(quarkus提供的基礎鏡像中沒有JDK19版本,另外啟動命令也要調整)

  • 上述這些都是本文的重點,欣宸已經將這些梳理清楚了,接下來咱們一起實戰吧,讓前文體驗過的web從無到有,再到順利運行,達到預期

  • 整個開發過程如下圖所示,一共十步,接下來開始動手

image-20221020081857985

開發環境

  • 開發電腦:MacBook Pro M1,macOS Monterey 12.6
  • IDE:IntelliJ IDEA 2022.3 EAP (Ultimate Edition) (即未發佈前的早期預覽版)
  • 另外,M1晶元的電腦上開發和運行JDK19應用,與普通的X86相比感受不到任何變化,只有一點要註意:上傳docker鏡像到hub.docker.com時,鏡像的系統架構是ARM的,這樣的鏡像在X86電腦上下載下來後不能運行

下載JDK19

image-20221015082121173
  • 實際上,azul的jdk很全面,x86晶元的各平臺版本安裝包都提供了,您可以根據自己電腦環境選擇下載,下麵是我選擇的適合M1晶元的版本
image-20221015082450750
  • 下載完成後雙擊安裝即可

修改maven的配置

  • 我這裡使用的是本地maven,其對應的JDK也要改成19,修改方法是調整環境變數JAVA_HOME,令其指向JDK19目錄(在我的電腦上,環境變數是在~/.zshrc裡面)

image-20221015091138678

  • 修改後令環境變數生效,然後執行一下命令確認已經使用了JDK19
➜  ~ mvn -version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: /Users/zhaoqin/software/apache-maven-3.8.5
Java version: 19, vendor: Azul Systems, Inc., runtime: /Library/Java/JavaVirtualMachines/zulu-19.jdk/Contents/Home
Default locale: zh_CN_#Hans, platform encoding: UTF-8
OS name: "mac os x", version: "12.6", arch: "aarch64", family: "mac"

創建Quarkus項目

  • 打開IDEA,新建項目,選擇Quarkus項目
image-20221015083615179
  • 接下來選擇要用到的擴展包(其實就是在圖形化頁面添加jar依賴),這裡的選擇如下圖:Reactive PostgreSQL clientRESTEasy Reactive Jackson
image-20221015084459075
  • 點擊上圖右下角的Create按鈕後項目開始創建,稍作等待,項目創建完成,如下圖,此刻只能感慨:quarkus太貼心,不但有demo源碼,還有各種版本的Dockerfile文件,而且git相關的配置也有,甚至README.md都寫得那麼詳細,我是不是可以點擊運行按鈕直接把程式run起來了
image-20221015085440544

IDEA設置

  • 由於要用到JDK19,下麵幾項設置需要檢查並確認
  • 首先是Project設置,如下圖

image-20221015112614328

  • 其次是Modules設置,先配置Sources這個tab頁

image-20221015112738859

  • 接下來是Dependencies這個tab頁
image-20221015112819900
  • 進入IDEA系統設置菜單
image-20221015112115388
  • 如下圖,三個位置需要設置
image-20221015113055594
  • 設置完成了,接下來開始編碼

編碼

  • 首先確認pom.xml,這是IDEA幫我們創建的,內容如下,有兩處改動稍後會說到
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.bolingcavalry</groupId>
    <artifactId>quarkus-virual-threads-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <compiler-plugin.version>3.8.1</compiler-plugin.version>
        <maven.compiler.release>19</maven.compiler.release>
        <maven.compiler.source>19</maven.compiler.source>
        <maven.compiler.target>19</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
        <quarkus.platform.version>2.13.2.Final</quarkus.platform.version>
        <skipITs>true</skipITs>
        <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-reactive-pg-client</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-arc</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive</artifactId>
        </dependency>

        <!-- 生成測試數據 -->
        <dependency>
            <groupId>net.datafaker</groupId>
            <artifactId>datafaker</artifactId>
            <version>1.6.0</version>
        </dependency>

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
                <version>${quarkus.platform.version}</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                            <goal>generate-code</goal>
                            <goal>generate-code-tests</goal>
                        </goals>
                    </execution>
                </executions>
                <!-- 這裡是新增的虛擬線程相關特性,start -->
                <configuration>
                    <source>19</source>
                    <target>19</target>
                    <compilerArgs>
                        <arg>--enable-preview</arg>
                    </compilerArgs>
                    <jvmArgs>--enable-preview --add-opens java.base/java.lang=ALL-UNNAMED</jvmArgs>
                </configuration>
                <!-- 這裡是新增的虛擬線程相關特性,end -->
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${compiler-plugin.version}</version>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${surefire-plugin.version}</version>
                <configuration>
                    <systemPropertyVariables>
                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                        <maven.home>${maven.home}</maven.home>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${surefire-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                        <configuration>
                            <systemPropertyVariables>
                                <native.image.path>${project.build.directory}/${project.build.finalName}-runner
                                </native.image.path>
                                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                                <maven.home>${maven.home}</maven.home>
                            </systemPropertyVariables>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <profiles>
        <profile>
            <id>native</id>
            <activation>
                <property>
                    <name>native</name>
                </property>
            </activation>
            <properties>
                <skipITs>false</skipITs>
                <quarkus.package.type>native</quarkus.package.type>
            </properties>
        </profile>
    </profiles>
</project>

  • pom.xml的第一處改動如下圖,要確保全部是19
image-20221020082700997
  • 第二處改動,是在quarkus-maven-plugin插件中增加額外的配置參數,如下圖紅框
image-20221020082849667
  • 接下來新增配置文件application.properties,在resources目錄下
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.max-size=8
quarkus.datasource.jdbc.min-size=2

quarkus.datasource.username=quarkus
quarkus.datasource.password=123456
quarkus.datasource.reactive.url=postgresql://192.168.0.1:5432/quarkus_test
  • 開始寫java代碼了,首先是啟動類VirtualThreadsDemoApp.java
package com.bolingcavalry;

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.annotations.QuarkusMain;

@QuarkusMain
public class VirtualThreadsDemoApp {

    public static void main(String... args) {
        Quarkus.run(args);
    }
}
  • 資料庫對應的model類有兩個,第一個是gender欄位的枚舉
package com.bolingcavalry.model;

public enum Gender {
    MALE, FEMALE;
}
  • 表對應的實體類
package com.bolingcavalry.model;

import io.vertx.mutiny.sqlclient.Row;

public class Person {
    private Long id;
    private String name;
    private int age;
    private Gender gender;
    private Integer externalId;

    public String getThreadInfo() {
        return threadInfo;
    }

    public void setThreadInfo(String threadInfo) {
        this.threadInfo = threadInfo;
    }

    private String threadInfo;

    public Person() {
    }

    public Person(Long id, String name, int age, Gender gender, Integer externalId) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.externalId = externalId;
        this.threadInfo = Thread.currentThread().toString();
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public Integer getExternalId() {
        return externalId;
    }

    public void setExternalId(Integer externalId) {
        this.externalId = externalId;
    }

    public static Person from(Row row) {
        return new Person(
                row.getLong("id"),
                row.getString("name"),
                row.getInteger("age"),
                Gender.valueOf(row.getString("gender")),
                row.getInteger("external_id"));
    }
}
  • 接下來是操作資料庫的dao類,可見使用操作方式還是很原始的,還要在代碼中手寫SQL,取出也要逐個欄位匹配,其實quarkus也支持JPA,只不過本篇使用的是響應式資料庫驅動,所以選用的是Vert.x生成的連接池PgPool
package com.bolingcavalry.repository;

import com.bolingcavalry.model.Person;
import io.vertx.mutiny.pgclient.PgPool;
import io.vertx.mutiny.sqlclient.Row;
import io.vertx.mutiny.sqlclient.RowSet;
import io.vertx.mutiny.sqlclient.Tuple;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

@ApplicationScoped
public class PersonRepositoryAsyncAwait {

    @Inject
    PgPool pgPool;

    public Person findById(Long id) {
        RowSet<Row> rowSet = pgPool
           .preparedQuery("SELECT id, name, age, gender, external_id FROM person WHERE id = $1")
           .executeAndAwait(Tuple.of(id));
        List<Person> persons = iterateAndCreate(rowSet);
        return persons.size() == 0 ? null : persons.get(0);
    }

    private List<Person> iterateAndCreate(RowSet<Row> rowSet) {
        List<Person> persons = new ArrayList<>();
        for (Row row : rowSet) {
            persons.add(Person.from(row));
        }
        return persons;
    }
}
  • 接下來就是前面截圖看到的web服務類VTPersonResource.java,它被註解@RunOnVirtualThread修飾,表示收到web請求在虛擬線程中執行響應代碼
package com.bolingcavalry.resource;

import com.bolingcavalry.model.Person;
import com.bolingcavalry.repository.PersonRepositoryAsyncAwait;
import io.smallrye.common.annotation.RunOnVirtualThread;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

@Path("/vt/persons")
@RunOnVirtualThread
public class VTPersonResource {

    @Inject
    PersonRepositoryAsyncAwait personRepository;

    @GET
    @Path("/{id}")
    public Person getPersonById(@PathParam("id") Long id) {
        return personRepository.findById(id);
    }
}
  • 最後是用於對比的常規web服務類PoolPersonResource.java,這個就是中規中矩的線上程池中取一個線程來執行響應代碼
package com.bolingcavalry.resource;

import com.bolingcavalry.model.Person;
import com.bolingcavalry.repository.PersonRepositoryAsyncAwait;
import io.smallrye.common.annotation.RunOnVirtualThread;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

@Path("/pool/persons")
public class PoolPersonResource {

    @Inject
    PersonRepositoryAsyncAwait personRepository;

    @GET
    @Path("/{id}")
    public Person getPersonById(@PathParam("id") Long id) {
        return personRepository.findById(id);
    }
}
  • 至此,編碼完成

IDEA啟動設置

  • 編碼完成後,在IDEA上啟動應用做本地調試是咱們的基本操作,所以IDEA運行環境也要設置成支持JDK19的預覽特性
  • 打開入口類,點擊main方法前面的綠色箭頭,在彈出的菜單上選擇Modify Run Configuration
image-20221015115712663
  • 在運行應用的設置頁面,如下操作

image-20221015115542673

  • 選中Add VM options
image-20221015115859652
  • 填入下圖箭頭所指的內容
image-20221015120024775
  • 終於,設置完成,接下來要啟動應用了

啟動和驗證

  • 啟動應用之前,請確認postgresql資料庫已啟動,並且數據已經導入,具體啟動和導入方法請參考前文
  • 點擊下圖紅色箭頭中指向的按鈕,即可在IDEA中運行應用
image-20221021081112061 image-20221021082603972
  • 在前文中,咱們是在docker上運行應用的,另外在實際場景中應用運行在docker或者k8s環境也是普遍情況,所以接下來一起實戰將用做成docker鏡像並驗證

構建鏡像

  • 在創建工程的時候,IDEA就用quarkus模板自動創建了多個Dockerfile文件,下圖紅框中全是
image-20221021083348600
  • 如果當前應用的JDK不是19,而是11或者17,那麼上圖紅框中的Dockerfile文件就能直接使用了,然而,由於今天咱們應用的JDK必須是19,就無法使用這些Dockerfile了,必須自己寫一個,原因很簡單,打開Dockerfile.jvm,如下圖紅色箭頭所示,基礎鏡像是jdk17,而這個倉庫中並沒有JDK19,也就是說quarkus還沒有發佈JDK19版本的基礎鏡像,咱們要自己找一個,另外,容器啟動命令也要調整,需要加入--enable-preview才能開啟JVM的虛擬線程
image-20221021083635218
  • 自己寫的Dockerile文件名為Dockerfile.19,內容如下,可見非常簡單:先換基礎鏡像,再把mvn構建結果複製過去,最後加個啟動命令就完事兒了(遠不如官方的分層構建節省空間,然而在官方的JDK19鏡像方案出來之前,先用下麵這個將就著用吧)
FROM openjdk:19

ENV LANGUAGE='en_US:en'

# 執行工作目錄
WORKDIR application

COPY --chown=185 target/*.jar ./

RUN mkdir config

EXPOSE 8080
USER 185
ENTRYPOINT ["java", "-jar", "--enable-preview", "quarkus-virual-threads-demo-1.0-SNAPSHOT-runner.jar"]
  • 接下來可以製作鏡像了,請確保自己電腦上docker已在運行

  • 首先是常規maven編譯打包(uber-jar表示生成的jar中包含了所有依賴庫)

mvn clean package -U -DskipTests -Dquarkus.package.type=uber-jar
  • 構建docker鏡像
docker build -f src/main/docker/Dockerfile.19 -t bolingcavalry/quarkus-virual-threads-demo:0.0.2 .
  • 鏡像製作成功,控制台輸出如下圖
image-20221022072718957
  • 如果您有hub.docker.com的賬號,也可以像我一樣推送到公共倉庫,方便大家使用

異常測試(沒有enable-preview參數會怎麼樣?)

  • 回顧Dockerfile中啟動應用的命令,由於虛擬線程是JDK19的預覽功能,因此必須添加下圖紅色箭頭所指的--enable-preview參數才能讓虛擬線程功能生效
image-20221022073455393
  • 於是我就在想:不加這個參數會咋樣?也就是不開啟虛擬線程,但是代碼中卻要用它,那麼真正運行的時候會如何呢?
  • 瞎猜是沒用的,還是試試吧,在啟動參數中刪除--enable-preview,如下圖,再重新構建鏡像
image-20221022074847459
  • 像前文那樣運行容器(再次提醒,確保資料庫是正常的),再在瀏覽器訪問http://localhost:8080/vt/persons/1,頁面正常顯示了,看來功能是不受影響的
image-20221022082633379
  • 再用docker logs命令查看後臺日誌,如下圖箭頭所示,quarkus給出了WARN級別的提示:由於當前虛擬機不支持虛擬線程,改為使用預設的阻塞來執行業務邏輯
image-20221022075007690
  • 小結:在不支持虛擬線程的環境強行使用虛擬線程,quarkus會選擇相容的方式繼續完成任務

小結和展望

  • 至此,一個完整的quarkus應用已開發完成,該應用使用虛擬線程來響應web請求,而且在quarkus官方還沒有提供方案的前提下,咱們依舊完成了docker鏡像的製作,最後,因為好奇,還關閉重要參數嘗試了一下,一系列操作下來,相信您已經對基礎開發瞭如指掌了

  • 最後,還剩下兩個遺留問題,相信您也會有類似困惑

    1. 虛擬線程和常規子線程的區別,究竟能不能看出來?前文已經驗證了性能上區別不大,那還有別的方式來觀察和區分嗎?
    2. 能不能稍微深入一點,僅憑一個@RunOnVirtualThread註解就強行寫了兩篇博客,實在是太忽悠人了
  • 以上問題會在接下來的《支持JDK19虛擬線程的web框架,終篇》得到解決,還是那句熟悉的廣告詞:欣宸原創,不辜負您的期待

歡迎關註博客園:程式員欣宸

學習路上,你不孤單,欣宸原創一路相伴...


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

-Advertisement-
Play Games
更多相關文章
  • 日誌管理包含日誌數據存儲、處理、分析和可視化,通過利用日誌管理工具,可以監控性能趨勢、解決問題、檢測異常並優化整體系統性能。 近年來,開源日誌管理解決方案在大家尋求靈活且經濟有效的方式來管理現代系統典型的大量日誌數據時,獲得了顯著的關註。這些工具為商業產品提供了有力的替代方案,使各種規模的企業都能有 ...
  • 電腦思維和人的思維的不同 對於一個算式3+2*(4-3)/5 人的思維是根據括弧和符號優先順序,優先計算括弧中的數據,在進行乘法和除法,在處理加法運算 但是電腦的思維是線性的,電腦會按照算式的前後順序,從前往後進行運算,這樣會導致運算結果錯誤 電腦如何套用人的運算思維 想要讓電腦具有人的”思 ...
  • 應大家需求,出一個 wp 自動發佈每日 60 秒讀懂世界文章的教程. 1.複製下方的 php 代碼 <?php $date = file_get_contents("https://www.zhihu.com/api/v4/columns/c_1261258401923026944/items"); ...
  • 1、UIScrollView增加了屬性allowsKeyboardScrolling表示是否根據連接的物理鍵盤的方向鍵而滾動。 import UIKit class ViewController: UIViewController { lazy var scrollView: UIScrollVie ...
  • 1.ioc 1 pom導包spring-mvc 2 創建資源文件xml、pojo對象() 3 資源文件中配置bean,對pojo對象屬性 4 測試中直接getBean獲取。 1.1 一些不重要的 取別名:在資源文件中取別名,一種是直接在bean標簽中用name,另一種是單獨設置標簽alias 合併配 ...
  • 線程間共用數據的問題 多線程之間共用數據,最大的問題便是數據競爭導致的異常問題。多個線程操作同一塊資源,如果不做任何限制,那麼一定會發生錯誤。例如: 1 int g_nResource = 0; 2 void thread_entry() 3 { 4 for (int i = 0; i < 1000 ...
  • 原本你寫的程式是靜態鏈接的系統的vulkan-1.dll,如果系統不存在vulkan-1.dll,則會直接崩潰。 關於將ncnn靜態鏈接vulkan改成動態載入vulkan的形式,然後提供這兩個函數 bool ncnn::has_vulkan(); void ncnn::use_vulkan(boo ...
  • 1 前言 高性能的HTTP和反向代理伺服器,Nginx用來: 搭建Web Server 作負載均衡 供配置的日誌欄位豐富,從各類HTTP頭部到內部性能數據都有 Nginx的訪問日誌中,存在499狀態碼的日誌。但常見4xx狀態碼只有400、401、403、404等,499並未在HTTP RFC文檔。這 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...