java與es8實戰之六:用JSON創建請求對象(比builder pattern更加直觀簡潔)

来源:https://www.cnblogs.com/bolingcavalry/archive/2023/08/31/17658365.html
-Advertisement-
Play Games

向ES發送請求時,如何創建請求對象呢?官方推薦的builder patter,在面對複雜的請求對象結構時還好用嗎?有沒有更加直觀簡潔的方法,盡在本文一網打盡 ...


歡迎訪問我的GitHub

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

本篇概覽

  • 本文是《java與es8實戰》系列的第六篇,經過前面的實戰,咱們初步掌握了一些Java對ES的基本操作,通過發送請求對象(例如CreateIndexResponse)到ES服務端,達到操作ES的目的,但是細心的您可能發現了:請求對象可能很複雜,例如多層對象嵌套,那麼用代碼來創建這些請求對象也必然不會容易
  • 今天的文章,咱們先來體驗用代碼創建請求對象的不便之處,再嘗試ES官方給我們提供的解決之道:用JSON創建請求對象
  • 接下來,咱們從一個假設的任務開始

任務安排

  • 現在咱們要創建一個索引,此索引記錄的是商品信息
  1. 有一個副本(屬於setting部分)
  2. 共三個分片(屬於setting部分)
  3. 共三個欄位:商品名稱name(keyword),商品描述description(text),價格price(integer)(屬於mapping部分)
  4. name欄位值長為256,超出此長度的欄位將不會被索引,但是會存儲
  • 接下來,咱們在kibana上用JSON創建索引,再寫代碼創建相同索引,然後對比兩種方式的複雜程度

kibana上創建索引

  • 如果在kibana上用json來創建,請求內容如下,索引名是product001
PUT product001
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword",
        "ignore_above": 256
      },
      "description": {
        "type": "text"
      },
      "price": {
        "type": "integer"
      }
    }
  }
}
  • 效果如下,符合預期
image-20220625110440090
  • 通過eshead觀察,也是符合預期
image-20220625110708346
  • 可見基於JSON的操作簡單明瞭,接下來看看創建相通索引的代碼是什麼樣子

基於代碼創建

  • 關於如何連接ES的代碼並非本篇重點,而且前面的文章已有詳細說明,就不多贅述了
  • 首先創建一個API,可以接受外部傳來的Setting和Mapping設定,然後用這些設定來創建索引
    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Override
    public void create(String name,
                       Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn,
                       Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn) throws IOException {
        elasticsearchClient
                .indices()
                .create(c -> c
                        .index(name)
                        .settings(settingFn)
                        .mappings(mappingFn)
                );
    }
  • 然後就是如何準備Setting和Mapping參數,再調用create方法完成創建,為了讓代碼順利執行,我將調用create方法的代碼寫在單元測試類中,這樣後面只需要執行單元測試即可調用create方法
@SpringBootTest
class EsServiceImplTest {

    @Autowired
    EsService esService;

    @Test
    void create() throws Exception {
        // 索引名
        String indexName = "product002";

        // 構建setting時,builder用到的lambda
        Function<IndexSettings.Builder, ObjectBuilder<IndexSettings>> settingFn = sBuilder -> sBuilder
                .index(iBuilder -> iBuilder
                        // 三個分片
                        .numberOfShards("3")
                        // 一個副本
                        .numberOfReplicas("1")
                );

        // 新的索引有三個欄位,每個欄位都有自己的property,這裡依次創建
        Property keywordProperty = Property.of(pBuilder -> pBuilder.keyword(kBuilder -> kBuilder.ignoreAbove(256)));
        Property textProperty = Property.of(pBuilder -> pBuilder.text(tBuilder -> tBuilder));
        Property integerProperty = Property.of(pBuilder -> pBuilder.integer(iBuilder -> iBuilder));

        // // 構建mapping時,builder用到的lambda
        Function<TypeMapping.Builder, ObjectBuilder<TypeMapping>> mappingFn = mBuilder -> mBuilder
                .properties("name", keywordProperty)
                .properties("description", textProperty)
                .properties("price", integerProperty);

        // 創建索引,並且指定了setting和mapping
        esService.create(indexName, settingFn, mappingFn);

    }
}
  • 由於Java API Client中所有對象都統一使用builder pattern的方式創建,這導致代碼量略多,例如setting部分,除了setting自身要用Lambda表達式,設置分片和副本的代碼也要用Lambda的形式傳入,這種嵌套效果在編碼中看起來還是有點繞的,閱讀起來可能會有點不適應
  • 執行單元測試,如下圖,未發生異常
image-20220625114226165
  • 用kibana查看新建的索引

image-20220625114553023

  • 最後,將product001和product002的mapping放在一起對比,可見一模一樣
image-20220625114939286
  • 再用eshead對比分片和副本的效果,也是一模一樣
image-20220625115042349

小結和感慨

  • 至此,可以得出結論:
  1. Java API Client的對ES的操作,能得到kibana+JSON相同的效果
  2. 然而,用java代碼來實現JSON的嵌套對象的內容,代碼的複雜程度上升,可讀性下降(純屬個人感覺)
  • 另外,在開發期間,我們也常常先用kibana+JSON先做基本的測試和驗證,然後再去編碼
  • 因此,如果能在代碼中直接使用kibana的JSON,以此取代複雜的builder pattern代碼去創建各種增刪改查的請求對象,那該多好啊
  • ES官方預判了我的預判,在Java API Client中支持使用JSON來構建請求對象
image-20220625153336739

能用JSON的根本原因

  • 動手實踐之前,有個問題先思考一下

  • 剛纔咱們寫了那麼多代碼,才能創建出CreateIndexResponse對象(註意代碼:elasticsearchClient.indices().create),怎麼就能用JSON輕易的創建出來呢?有什麼直接證據或者關鍵代碼嗎?

  • 來看看CreateIndexResponse的builder的源碼,集成了父類,也實現了介面,

public static class Builder extends WithJsonObjectBuilderBase<Builder>
			implements
				ObjectBuilder<CreateIndexRequest> {
  • 用IDEA查看類圖的功能,Builder的繼承和實現關係一目瞭然,註意紅色箭頭指向的WithJson介面,它是Builder父類實現的介面,也是讓CreateIndexResponse可以通過JSON來創建的關鍵
image-20220625155614986
  • 強大的IDEA,可以在上圖直接展開WithJson介面的所有方法簽名,如下圖,一目瞭然,三個方法三種入參,證明瞭使用者可以用三種方式將JSON內容傳給Builder,再由Builer根據傳入的內容生成CreateIndexResponse實例

image-20220625160132898

創建工程

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!-- 請改為自己項目的parent坐標 -->
    <parent>
        <artifactId>elasticsearch-tutorials</artifactId>
        <groupId>com.bolingcavalry</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <!-- 請改為自己項目的artifactId -->
    <artifactId>object-from-json</artifactId>
    <packaging>jar</packaging>
    <!-- 請改為自己項目的name -->
    <name>object-from-json</name>
    <url>https://github.com/zq2599</url>

    <!--不用spring-boot-starter-parent作為parent時的配置-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>

                <version>${springboot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 不加這個,configuration類中,IDEA總會添加一些提示 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <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>

            <!-- exclude junit 4 -->
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>

        </dependency>

        <!-- junit 5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- elasticsearch引入依賴  start -->
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <!-- 使用spring boot Maven插件時需要添加該依賴 -->
        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 需要此插件,在執行mvn test命令時才會執行單元測試 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M4</version>
                <configuration>
                    <skipTests>false</skipTests>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>

        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>
  • 是個普通的SpringBoot應用,入口類FromJsonApplication.java如下,非常簡單
package com.bolingcavalry.fromjson;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FromJsonApplication {
    public static void main(String[] args) {
        SpringApplication.run(FromJsonApplication.class, args);
    }
}
  • 然後是連接ES的配置類ClientConfig.java,關於如何連接ES,在《java與es8實戰之四》一文已經詳細說明,不再贅述,直接使用配置類的elasticsearchClient方法創建的ElasticsearchClient對象即可操作ES
@ConfigurationProperties(prefix = "elasticsearch") //配置的首碼
@Configuration
public class ClientConfig {

    @Setter
    private String hosts;

    /**
     * 解析配置的字元串,轉為HttpHost對象數組
     * @return
     */
    private HttpHost[] toHttpHost() {
        if (!StringUtils.hasLength(hosts)) {
            throw new RuntimeException("invalid elasticsearch configuration");
        }

        String[] hostArray = hosts.split(",");
        HttpHost[] httpHosts = new HttpHost[hostArray.length];
        HttpHost httpHost;
        for (int i = 0; i < hostArray.length; i++) {
            String[] strings = hostArray[i].split(":");
            httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "http");
            httpHosts[i] = httpHost;
        }

        return httpHosts;
    }

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        HttpHost[] httpHosts = toHttpHost();
        RestClient restClient = RestClient.builder(httpHosts).build();
        RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
        // And create the API client
        return new ElasticsearchClient(transport);
    }
}
  • 最後是配置文件application.yml
elasticsearch:
  # 多個IP逗號隔開
  hosts: 127.0.0.1:9200
  • 現在工程已經建好,接下來開始實踐如何通過JSON得到請求對象,通過剛纔對WithJson介面的分析,JSON轉請求對象共有三種方式
  1. ImputStream
  2. JSON字元串
  3. Parse
  • 接下來逐個實踐

第一種:InputStream作為入參

  • 最簡單的方式莫過通過InputStream轉換,InputStream是大家常用到的IO類,相信您已經胸有成竹了,流程如下圖
流程圖 (12)
  • 開始編碼,首先創建一個介面EsService.java,裡面有名為create的方法,這是創建索引用的,入參是索引名和包含有JSON內容的InputStream
public interface EsService {
    /**
     * 以InputStream為入參創建索引
     * @param name 索引名稱
     * @param inputStream 包含JSON內容的文件流對象
     */
    void create(String name, InputStream inputStream) throws IOException;
}
  • 接下來是重點:EsService介面的實現類EsServiceImpl.java,可見非常簡單,只要調用builder的withJson方法,將InputStream作為入參傳入即可
@Service
public class EsServiceImpl implements EsService {

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Override
    public void create(String name, InputStream inputStream) throws IOException {
        // 根據InputStrea創建請求對象
        CreateIndexRequest request = CreateIndexRequest.of(builder -> builder
                .index(name)
                .withJson(inputStream));

        elasticsearchClient.indices().create(request);
    }
}
  • 為了驗證EsServiceImpl的create方法,先準備好json文件,文件名為product003.json,完整路徑是:/Users/will/temp/202206/25/product003.json
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword",
        "ignore_above": 256
      },
      "description": {
        "type": "text"
      },
      "price": {
        "type": "integer"
      }
    }
  }
}
  • 最後寫一個單元測試類,調用EsServiceImpl的create方法,將product003.json轉成InputStream對象作為其入參,驗證create方法的功能是否符合預期,如下所示,代碼非常簡單
    @Test
    void createByInputStream() throws Exception {
        // 文件名
        String filePath = "/Users/will/temp/202206/25/product003.json";
        // 索引名
        String indexName = "product003";
        // 通過InputStream創建索引
        esService.create(indexName, new FileInputStream(filePath));
    }
  • 運行單元測試代碼,一切正常
image-20220625181209377
  • 用kibana查看product003索引,如下所示,符合預期
image-20220625181448280
  • 再用eshead查看副本和分片,和之前的兩個索引一致
image-20220625181537006

分析Reader類

  • 接下來嘗試WithJson介面的第二個方法
default T withJson(Reader input) {
        JsonpMapper mapper = SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
        return withJson(mapper.jsonProvider().createParser(input), mapper);
    }
  • 先來看看這個Reader的繼承關係,本篇不會詳細分析Reader代碼,咱們重點關註它的兩個比較重要的子類:StringReader和FileReader
image-20220625194541347
  • 接下來先用FileReader作為withJson方法的入參,驗證用文件來創建請求對象,再用StringReader作為withJson方法的入參,驗證用字元串來創建請求對象

第二種:FileReader作為入參

  • 首先,給EsService介面新增一個方法
    /**
     * 以Reader為入參創建索引
     * @param name 索引名稱
     * @param reader 包含JSON內容的文件流對象
     */
    void create(String name, Reader reader) throws IOException;
  • 接下來是重點:EsService介面的實現類EsServiceImpl.java,可見非常簡單,只要調用builder的withJson方法,將Reader作為入參傳入即可
    @Override
    public void create(String name, Reader reader) throws IOException {
        // 根據Reader創建請求對象
        CreateIndexRequest request = CreateIndexRequest.of(builder -> builder
                .index(name)
                .withJson(reader));

        elasticsearchClient.indices().create(request);
    }
  • json文件繼續使用剛纔創建的product003.json文件

  • 單元測試代碼中也增加一個方法,用於驗證剛纔寫的create方法

    @Test
    void createByReader() throws Exception {
        // 文件名
        String filePath = "/Users/will/temp/202206/25/product003.json";
        // 索引名
        String indexName = "product004";

        // 通過InputStream創建索引
        esService.create(indexName, new FileReader(filePath));
    }
  • 接下來是執行單元測試方法,在kibana和eshead上驗證product004索引和之前新建的幾個索引是否一致,這裡就不多占用篇幅了,結論是一模一樣
  • 其實吧,用InputStream或者Reader作為參數,內部實現是一回事,來看看FileReader構造方法的源碼吧,裡面是InputStream
public class FileReader extends InputStreamReader {

    public FileReader(String fileName) throws FileNotFoundException {
        super(new FileInputStream(fileName));
    }

第三種:字元串作為入參

  • 接下來要驗證的是用字元串來創建請求對象,這個比較實用,用字元串創建請求對象,給我們的應用開發提供了很大的自由度,廢話少說,開始寫代碼

  • 首先還是給EsService介面新增一個方法,入參是索引名稱和JSON字元串

    /**
     * 以字元串為入參創建索引
     * @param name 索引名稱
     * @param jsonContent 包含JSON內容的字元串
     */
    void create(String name, String jsonContent) throws IOException;
  • 接下來是重點:EsService介面的實現類EsServiceImpl.java,可見非常簡單,用字元串創建StringReader對象,然後只要調用builder的withJson方法,將StringReader對象作為入參傳入即可
    @Override
    public void create(String name, String jsonContent) throws IOException {
        // 根據Reader創建請求對象
        CreateIndexRequest request = CreateIndexRequest.of(builder -> builder
                .index(name)
                .withJson(new StringReader(jsonContent)));

        elasticsearchClient.indices().create(request);
    }
  • 為了驗證上面的create方法,在單元測試類中新增一個方法來驗證
    @Test
    void createByString() throws Exception {
        // 文件名
        String jsonContent = "{\n" +
                "  \"settings\": {\n" +
                "    \"number_of_shards\": 3,\n" +
                "    \"number_of_replicas\": 1\n" +
                "  },\n" +
                "  \"mappings\": {\n" +
                "    \"properties\": {\n" +
                "      \"name\": {\n" +
                "        \"type\": \"keyword\",\n" +
                "        \"ignore_above\": 256\n" +
                "      },\n" +
                "      \"description\": {\n" +
                "        \"type\": \"text\"\n" +
                "      },\n" +
                "      \"price\": {\n" +
                "        \"type\": \"integer\"\n" +
                "      }\n" +
                "    }\n" +
                "  }\n" +
                "}\n";

        // 索引名
        String indexName = "product005";

        // 通過InputStream創建索引
        esService.create(indexName, jsonContent);
    }
  • 接下來是執行單元測試方法,在kibana和eshead上驗證product004索引和之前新建的幾個索引是否一致,這裡就不多占用篇幅了,結論是一模一樣

第四種:JsonParser和JsonpMapper作為入參

  • 基於JSON創建ES請求對象的最後一種方法如下,入參是JsonParser和JsonpMapper
T withJson(JsonParser parser, JsonpMapper mapper)
  • 前面三種方法,咱們都寫了代碼去驗證,不過最後這種就不寫代碼驗證了,原因很簡單:沒必要,咱們先來看看WithJson介面的源碼
public interface WithJson<T> {

    default T withJson(InputStream input) {
        JsonpMapper mapper = SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
        return withJson(mapper.jsonProvider().createParser(input), mapper);
    }

    default T withJson(Reader input) {
        JsonpMapper mapper = SimpleJsonpMapper.INSTANCE_REJECT_UNKNOWN_FIELDS;
        return withJson(mapper.jsonProvider().createParser(input), mapper);
    }

    T withJson(JsonParser parser, JsonpMapper mapper);
}
  • 可見,前面使用過的withJson(InputStream input)withJson(Reader input),其實都是在調用withJson(JsonParser parser, JsonpMapper mapper),所以,在實際使用中,掌握withJson(InputStream input)withJson(Reader input)就已經夠用了,如果一定要使用withJson(JsonParser parser, JsonpMapper mapper),就參考上面的代碼去構造JsonParser即可

代碼和JSON內容混用

  • 有時候用代碼和JSON混合使用來創建請求對象,既能用JSON省去大量代碼工作,又能用代碼保持該有的靈活性,如下所示,查詢用JSON字元串,聚合參數用builder的API生成
Reader queryJson = new StringReader(
    "{" +
    "  \"query\": {" +
    "    \"range\": {" +
    "      \"@timestamp\": {" +
    "        \"gt\": \"now-1w\"" +
    "      }" +
    "    }" +
    "  }" +
    "}");

SearchRequest aggRequest = SearchRequest.of(b -> b
    .withJson(queryJson) 
    .aggregations("max-cpu", a1 -> a1 
        .dateHistogram(h -> h
            .field("@timestamp")
            .calendarInterval(CalendarInterval.Hour)
        )
        .aggregations("max", a2 -> a2
            .max(m -> m.field("host.cpu.usage"))
        )
    )
    .size(0)
);

Map<String, Aggregate> aggs = client
    .search(aggRequest, Void.class) 
    .aggregations();
  • 另外,不光是請求對象,與請求對象有關的實例也能用JSON生成,回顧本文最開始的那段代碼中,構造CreateIndexResponse對象時還要創建Property對象,實際上這個Property是可以通過JSON生成的,參考代碼如下
String json = "{ " +
            "        \"type\": \"text\"," +
            "        \"fields\": {" +
            "          \"some_field\": { " +
            "            \"type\": \"keyword\"," +
            "            \"normalizer\": \"lowercase\"" +
            "          }" +
            "        }" +
            "      }";

        Property p = Property.of(b -> b
            .withJson(new StringReader(json))
        );
  • 至此,基於JSON構造ES請求對象的實戰就完成了,今後在kibana上驗證通過的JSON請求體,可以直接放在代碼中用於使用,這將有效的降低代碼量,也提升了整體可讀性

源碼下載

名稱 鏈接 備註
項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議
git倉庫地址(ssh) [email protected]:zq2599/blog_demos.git 該項目源碼的倉庫地址,ssh協議
  • 這個git項目中有多個文件夾,本次實戰的源碼在elasticsearch-tutorials文件夾下,如下圖紅框
    在這裡插入圖片描述
  • elasticsearch-tutorials是個父工程,裡面有多個module,本篇實戰的module是object-from-json,如下圖紅框

image-20220717205709101

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

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


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

-Advertisement-
Play Games
更多相關文章
  • 隨著互聯網的發展,越來越多的企業開始構建自己的API介面。而隨著API介面的增多,調用者需要瞭解API的參數和返回值,這時我們就需要一種方便快捷的查詢方式。本文將介紹一種以介面參數線上查詢為核心的方式,同時以銀行三要素驗證介面為例,展示如何實現介面參數線上查詢。 一、什麼是API介面 API(App ...
  • ## 常用經驗 - 在HTTP中,我們要通過 URL 進行資源的定位 >比如: > >要取 id=888 的用戶信息,我們就向/user/{id} 這個路徑發送請求, > >要取 id=888 的用戶的訂單列表,我們就向/user/{id}/orders 這個路徑發送請求 - 在HTTP 中,DEL ...
  • Excel是一種常用的電子錶格軟體,廣泛應用於金融、商業和教育等領域。它提供了強大的數據處理和分析功能,可進行各種計算和公式運算,並能創建各種類型的圖表和可視化數據。Excel的靈活性使其成為處理和管理數據的重要工具。本文將介紹如何使用 Spire.XLS for Python 通過代碼創建Exce ...
  • ## 1、前言 作為一名後臺開發人員,許可權這個名詞應該算是特別熟悉的了。就算是java里的類也有 public、private 等“許可權”之分。之前項目里一直使用shiro作為許可權管理的框架。說實話,shiro的確挺強大的,但是它也有很多不好的地方。shiro預設的登錄地址還是login.jsp,前 ...
  • ### 一、簡介 Spring框架提供了一種名為Spring Cache的緩存策略。Spring Cache是一種抽象層,它提供了一種方便的方式來管理緩存,並與Spring應用程式中的各種緩存實現(如EhCache、Guava、Caffeine等)集成。 Spring Cache使用註解(如@Cac ...
  • 程式設計領域的`設計模式的六大設計原則` + `合成復用原則`(Composite Reuse Principle) ,都是一些很**泛**的思想(它們既可以指這個,也可以代指那個),無法生搬硬套,無法做到很具體的指導。我的建議是,有空多看幾遍、多思考看看怎麼能運用在實際項目中,在未來時**保佑** ...
  • ## 前言 一款app,消息頁面有:錢包通知、最近訪客等各種通知類別,每個類別可能有新的通知消息,實現已讀、未讀功能,包括多少個未讀,這個是怎麼實現的呢?比如用戶A訪問了用戶B的主頁,難道用rabitmq給B發通知消息嗎?量大了成本受得了嗎?有沒有成本低的方案呢 ![img](https://img ...
  • 來源:進擊雲原生 ### 1、檢測兩台伺服器指定目錄下的文件一致性 ``` #!/bin/bash ###################################### 檢測兩台伺服器指定目錄下的文件一致性 ##################################### #通過對 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...