《ElasticSearch6.x實戰教程》之複雜搜索、Java客戶端(下)

来源:https://www.cnblogs.com/yulinfeng/archive/2019/07/23/11229352.html
-Advertisement-
Play Games

第八章 複雜搜索 黑夜給了我黑色的眼睛,我卻用它尋找光明。 經過瞭解簡單的API和簡單搜索,已經基本上能應付大部分的使用場景。可是非關係型資料庫數據的文檔數據往往又多又雜,各種各樣冗餘的欄位,組成了一條"記錄"。複雜的數據結構,帶來的就是複雜的搜索。所以在進入本章節前,我們要構建一個儘可能"複雜"的 ...


第八章-複雜搜索

黑夜給了我黑色的眼睛,我卻用它尋找光明。

經過瞭解簡單的API和簡單搜索,已經基本上能應付大部分的使用場景。可是非關係型資料庫數據的文檔數據往往又多又雜,各種各樣冗餘的欄位,組成了一條"記錄"。複雜的數據結構,帶來的就是複雜的搜索。所以在進入本章節前,我們要構建一個儘可能"複雜"的數據結構。

下麵分為兩個場景,場景1偏向數據結構上的複雜並且介紹聚合查詢指定欄位返回深分頁,場景2偏向搜索精度上的複雜。

場景1

存儲一個公司的員工,員工信息包含姓名、工號、性別、出生年月日、崗位、上級、下級、所在部門、進入公司時間、修改時間、創建時間。其中員工工號作為主鍵ID全局唯一,員工只有一個直屬上級,但有多個下級,可以通過父子文檔實現。員工有可能屬於多個部門(特別是領導可能兼任多個部門的負責人)。

數據結構

創建索引並定義映射結構:

PUT http://localhost:9200/company
{
    "mappings":{
        "employee":{
            "properties":{
                "id":{
                    "type":"keyword"
                },
                "name":{
                    "type":"text",
                    "analyzer":"ik_smart",
                    "fields":{
                        "keyword":{
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "sex":{
                    "type":"keyword"
                },
        "age":{
          "type":"integer"
                },
                "birthday":{
                    "type":"date"
                },
                "position":{
                    "type":"text",
                    "analyzer":"ik_smart",
                    "fields":{
                        "keyword":{
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "level":{
                    "type":"join",
                    "relations":{
                        "superior":"staff",
            "staff":"junior"
                    }
                },
                "departments":{
                    "type":"text",
                    "analyzer":"ik_smart",
                    "fields":{
                        "keyword":{
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "joinTime":{
                    "type":"date"
                },
                "modified":{
                    "type":"date"
                },
                "created":{
                    "type":"date"
                }
            }
        }
    }
}

數據

接下來是構造數據,我們構造幾條關鍵數據。

  • 張三是公司的董事長,他是最大的領導,不屬於任何部門。
  • 李四的上級是張三,他的下級是王五、趙六、孫七、周八,他同時是市場部和研發部的負責人,也就是隸屬於市場部和研發部。
  • 王五、趙六的上級是張三,他沒有下級,他隸屬於市場部。
  • 孫七、周八的上級是李四,他沒有下級,他隸屬於研發部。

更為全面直觀的數據如下表所示:

姓名 工號 性別 年齡 出生年月日 崗位 上級 下級 部門 進入公司時間 修改時間 創建時間
張三 1 49 1970-01-01 董事長 / 李四 / 1990-01-01 1562167817000 1562167817000
李四 2 39 1980-04-03 總經理 張三 王五、趙六、孫七、周八 市場部、研發部 2001-02-02 1562167817000 1562167817000
王五 3 27 1992-09-01 銷售 李四 / 市場部 2010-07-01 1562167817000 1562167817000
趙六 4 29 1990-10-10 銷售 李四 / 市場部 2010-08-08 1562167817000 1562167817000
孫七 5 26 1993-12-10 前端工程師 李四 / 研發部 2016-07-01 1562167817000 1562167817000
周八 6 25 1994-05-11 Java工程師 李四 / 研發部 2018-03-10 1562167817000 1562167817000

插入6條數據:

POST http://localhost:9200/company/employee/1?routing=1
{
    "id":"1",
    "name":"張三",
    "sex":"男",
  "age":49,
    "birthday":"1970-01-01",
    "position":"董事長",
    "level":{
    "name":"superior"
  },
    "joinTime":"1990-01-01",
    "modified":"1562167817000",
    "created":"1562167817000"
}
POST http://localhost:9200/company/employee/2?routing=1
{
    "id":"2",
    "name":"李四",
    "sex":"男",
  "age":39,
    "birthday":"1980-04-03",
    "position":"總經理",
    "level":{
    "name":"staff",
    "parent":"1"
  },
  "departments":["市場部","研發部"],
    "joinTime":"2001-02-02",
    "modified":"1562167817000",
    "created":"1562167817000"
}
POST http://localhost:9200/company/employee/3?routing=1
{
    "id":"3",
    "name":"王五",
    "sex":"女",
  "age":27,
    "birthday":"1992-09-01",
    "position":"銷售",
    "level":{
    "name":"junior",
    "parent":"2"
  },
  "departments":["市場部"],
    "joinTime":"2010-07-01",
    "modified":"1562167817000",
    "created":"1562167817000"
}
POST http://localhost:9200/company/employee/4?routing=1
{
    "id":"4",
    "name":"趙六",
    "sex":"男",
  "age":29,
    "birthday":"1990-10-10",
    "position":"銷售",
    "level":{
    "name":"junior",
    "parent":"2"
  },
  "departments":["市場部"],
    "joinTime":"2010-08-08",
    "modified":"1562167817000",
    "created":"1562167817000"
}
POST http://localhost:9200/company/employee/5?routing=1
{
    "id":"5",
    "name":"孫七",
    "sex":"男",
  "age":26,
    "birthday":"1993-12-10",
    "position":"前端工程師",
    "level":{
    "name":"junior",
    "parent":"2"
  },
  "departments":["研發部"],
    "joinTime":"2016-07-01",
    "modified":"1562167817000",
    "created":"1562167817000"
}
POST http://localhost:9200/company/employee/6?routing=1
{
    "id":"6",
    "name":"周八",
    "sex":"男",
  "age":28,
    "birthday":"1994-05-11",
    "position":"Java工程師",
    "level":{
    "name":"junior",
    "parent":"2"
  },
  "departments":["研發部"],
    "joinTime":"2018-03-10",
    "modified":"1562167817000",
    "created":"1562167817000"
}

搜索

  1. 查詢研發部的員工
GET http://localhost:9200/company/employee/_search
{
    "query":{
        "match":{
            "departments":"研發部"
        }
    }
}
  1. 查詢在研發部且在市場部的員工
GET http://localhost:9200/company/employee/_search
{
    "query": {
        "bool":{
            "must":[{
                "match":{
                    "departments":"市場部"
                }
            },{
                "match":{
                    "departments":"研發部"
                }
            }]
        }
    }
}

*被搜索的欄位是一個數組類型,但對查詢語句並沒有特殊的要求。

  1. 查詢name="張三"的直接下屬。
GET http://localhost:9200/company/employee/_search
{
    "query": {
        "has_parent":{
            "parent_type":"superior",
            "query":{
                "match":{
                    "name":"張三"
                }
            }
        }
    }
}
  1. 查詢name="李四"的直接下屬。
GET http://localhost:9200/company/employee/_search

{
    "query": {
        "has_parent":{
            "parent_type":"staff",
            "query":{
                "match":{
                    "name":"李四"
                }
            }
        }
    }
}
  1. 查詢name="王五"的直接上級。
GET http://localhost:9200/company/employee/_search
{
    "query": {
        "has_child":{
            "type":"junior",
            "query":{
                "match":{
                    "name":"王五"
                }
            }
        }
    }
}

聚合查詢

ES中的聚合查詢類似MySQL中的聚合函數(avg、max等),例如計算員工的平均年齡。

GET http://localhost:9200/company/employee/_search?pretty
{
    "size": 0,
    "aggs": {
        "avg_age": {
            "avg": {
                "field": "age"
            }
        }
    }
}

指定欄位查詢

指定欄位返回值在查詢結果中指定需要返回的欄位。例如只查詢張三的生日。

GET http://localhost:9200/company/employee/_search?pretty
{
    "_source":["name","birthday"],
    "query":{
        "match":{
            "name":"張三"
        }
    }
}

深分頁

ES的深分頁是一個老生常談的問題。用過ES的都知道,ES預設查詢深度不能超過10000條,也就是page * pageSize < 10000。如果需要查詢超過1萬條的數據,要麼通過設置最大深度,要麼通過scroll滾動查詢。如果調整配置,即使能查出來,性能也會很差。但通過scroll滾動查詢的方式帶來的問題就是只能進行"上一頁"、"下一頁"的操作,而不能進行頁碼跳轉。

scroll原理簡單來講,就是一批一批的查,上一批的最後一個數據,作為下一批的第一個數據,直到查完所有的數據。

首先需要初始化查詢

GET http://localhost:9200/company/employee/_search?scroll=1m
{
    "query":{
        "match_all":{}
    },
    "size":1,
    "_source": ["id"]
}

像普通查詢結果一樣進行查詢,url中的scroll=1m指的是游標查詢的過期時間為1分鐘,每次查詢就會更新,設置過長占會用過多的時間。

接下來就可以通過上述API返回的_scroll_id進行滾動查詢,假設上面的結果返回"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAFBFk1pNzdFUVhDU3hxX3VtSVFUdDJBWlEAAAAAAAABQhZNaTc3RVFYQ1N4cV91bUlRVHQyQVpRAAAAAAAAAUMWTWk3N0VRWENTeHFfdW1JUVR0MkFaUQAAAAAAAAFEFk1pNzdFUVhDU3hxX3VtSVFUdDJBWlEAAAAAAAABRRZNaTc3RVFYQ1N4cV91bUlRVHQyQVpR"

GET http://localhost:9200/_search/scroll
{
    "scroll":"1m",
    "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAFBFk1pNzdFUVhDU3hxX3VtSVFUdDJBWlEAAAAAAAABQhZNaTc3RVFYQ1N4cV91bUlRVHQyQVpRAAAAAAAAAUMWTWk3N0VRWENTeHFfdW1JUVR0MkFaUQAAAAAAAAFEFk1pNzdFUVhDU3hxX3VtSVFUdDJBWlEAAAAAAAABRRZNaTc3RVFYQ1N4cV91bUlRVHQyQVpR"
}

這種方式有一個小小的弊端,如果超過過期時間就不能繼續往下查詢,這種查詢適合一次全量查詢所有數據。但現實情況有可能是用戶在一個頁面停留很長時間,再點擊上一頁或者下一頁,此時超過過期時間頁面不能再進行查詢。所以還有另外一種方式,範圍查詢。

另一種深分頁

假設員工數據中的工號ID是按遞增且唯一的順序,那麼我們可以通過範圍查詢進行分頁。

例如,按ID遞增排序,第一查詢ID>0的數據,數據量為1。

GET http://localhost:9200/company/employee/_search
{
    "query":{
        "range":{
            "id":{
                "gt":0
            }
        }
    },
    "size":1,
    "sort":{
        "id":{
            "order":"asc"
        }
    }
}

此時返回ID=1的1條數據,我們再繼續查詢ID>1的數據,數據量仍然是1。

GET http://localhost:9200/company/employee/_search
{
    "query":{
        "range":{
            "id":{
                "gt":1
            }
        }
    },
    "size":1,
    "sort":{
        "id":{
            "order":"asc"
        }
    }
}

這樣我們同樣做到了深分頁的查詢,並且沒有過期時間的限制。

場景2

存儲商品數據,根據商品名稱搜索商品,要求準確度高,不能搜索洗面奶結果出現麵粉。

由於這個場景主要涉及的是搜索的精度問題,所以並不會有複雜的數據結構,只有一個title欄位。

定義一個只包含title欄位且分詞器預設為standard的索引:

PUT http://localhost:9200/ware_index
{
    "mappings": {
        "ware": {
            "properties": {
                "title":{
                    "type":"text"
                }
            }
        }
    }
}

插入兩條數據:

POST http://localhost:9200/ware_index/ware
{
    "title":"洗面奶"
}
POST http://localhost:9200/ware_index/ware
{
    "title":"麵粉"
}

搜索關鍵字"洗面奶":

POST http://localhost:9200/ware_index/ware/_search
{
    "query":{
        "match":{
            "title":"洗面奶"
        }
    }
}

搜索結果出現了"洗面奶"和"麵粉"兩個風馬牛不相及的結果,這顯然不符合我們的預期。

原因在分詞一章中已經說明,text類型預設分詞器為standard,它會將中文字元串一個字一個字拆分,也就是將"洗面奶"拆分成了"洗"、"面"、"奶",將"麵粉"拆分成了"面"、"粉"。而match會將搜索的關鍵詞拆分,也就拆分成了"洗"、"面"、"奶",最後兩個"面"都能匹配上,也就出現了上述結果。所以對於中文的字元串搜索我們需要指定分詞器,而常用的分詞器是ik_smart,它會按照最大粒度拆分,如果採用ik_max_word它會將詞按照最小粒度拆分,也有可能造成上述結果。

DELETE http://localhost:9200/ware_index刪除索引,重新創建並指定title欄位的分詞器為ik_smart

PUT http://localhost:9200/ware_index
{
    "mappings":{
        "ware":{
            "properties":{
        "id":{
          "type":"keyword"
        },
                "title":{
                    "type":"text",
                    "analyzer":"ik_smart"
                }
            }
        }
    }
}

這時如果插入“洗面奶”和“麵粉”,搜索“洗面奶”是結果就只有一條。但此時我們插入以下兩條數據:

POST http://localhost:9200/ware_index/ware
{
    "id":"1",
    "title":"新希望牛奶"
}
POST http://localhost:9200/ware_index/ware
{
    "id":"2",
    "title":"春秋上新短袖"
}

搜索關鍵字”新希望牛奶“:

POST http://localhost:9200/ware_index/ware/_search
{
    "query":{
        "match":{
            "title":"新希望牛奶"
        }
    }
}

搜索結果出現了剛插入的2條,顯然第二條”春秋上新短袖“並不是我們想要的結果。出現這種問題的原因同樣是因為分詞的問題,在ik插件的詞庫中並沒有"新希望"一詞,所以它會把搜索的關鍵詞"新希望"拆分為"新"和"希望",同樣在"春秋上新短袖"中"新"也並沒有組合成其它詞語,它也被單獨拆成了"新",這就造成了上述結果。解決這個問題的辦法當然可以在ik插件中新增"新希望"詞語,如果我們在分詞中所做的那樣,但也有其它的辦法。

短語查詢

match_phrase,短語查詢,它會將搜索關鍵字"新希望牛奶"拆分成一個詞項列表"新 希望 牛奶",對於搜索的結果需要完全匹配這些詞項,且位置對應,本例中的"新希望牛奶"文檔數據從詞項和位置上完全對應,故通過match_phrase短語查詢可搜索出結果,且只有一條數據。

POST http://localhost:9200/ware_index/ware/_search
{
    "query":{
        "match_phrase":{
            "title":"新希望牛奶"
        }
    }
}

儘管這能滿足我們的搜索結果,但是用戶實際在搜索中常常可能是"牛奶 新希望"這樣的順序,但遺憾的是根據match_phrase短語匹配的要求是需要被搜索的文檔需要完全匹配詞項且位置對應,關鍵字"牛奶 新希望"被解析成了"牛奶 新 希望",儘管它與"新希望牛奶"詞項匹配但位置沒有對應,所以並不能搜索出任何結果。同理,此時如果我們插入"新希望的牛奶"數據時,無論是搜索"新希望牛奶"還是"牛奶新希望"均不能搜索出"新希望的牛奶"結果,前者的關鍵字是因為詞項沒有完全匹配,後者的關鍵字是因為詞項和位置沒有完全匹配

所以match_phrase也沒有達到完美的效果。

短語首碼查詢

match_phrase_prefix,短語首碼查詢,類似MySQL中的like "新希望%",它大體上和match_phrase_prefix一致,也是需要滿足文檔數據和搜索關鍵字在詞項和位置上保持一致,同樣如果搜索"牛奶新希望"也不會出現任何結果。它也並沒有達到我們想要的結果。

最低匹配度

前面兩種查詢中雖然能通過"新希望牛奶"搜索到我們想要的結果,但是對於"牛奶 新希望"卻無能為力。接下來的這種查詢方式能"完美"的達到我們想要的效果。

先來看最低匹配度的查詢示例:

POST http://localhost:9200/ware_index/ware/_search
{
    "query": {
        "match": {
            "title": {
                "query": "新希望牛奶",
                "minimum_should_match": "80%"
            }
        }
    }
}

minimum_should_match即最低匹配度。"80%"代表什麼意思呢?還是要從關鍵字"新希望牛奶"被解析成哪幾個詞項說起,前面說到"新希望牛奶"被解析成"新 希望 牛奶"三個詞項,如果通過match搜索,則含有"新"的數據同樣出現在搜索結果中。"80%"的含義則是3個詞項必須至少匹配80% * 3 = 2.4個詞項才會出現在搜索結果中,向下取整為2,即搜索的數據中需要至少包含2個詞項。顯然,"春秋上新短袖"只有1個詞項,不滿足最低匹配度2個詞項的要求,故不會出現在搜索結果中。

同樣,如果搜索"牛奶 新希望"也是上述的結果,它並不是短語匹配,所以並不會要求詞項所匹配的位置相同。

可以推出,如果"minimum_should_match":"100%"也就是要求完全匹配,此時要求數據中包含所有的詞項,這樣會出現較少的搜索結果;如果"minimun_should_match:0"此時並不代表一個詞項都可以不包含,而是只需要有一個詞項就能出現在搜索結果,實際上就是預設的match搜索,這樣會出現較多的搜索結果。

找到一個合適的值,就能有一個較好的體驗,根據二八原則,以及實踐表明,設置為"80%"能滿足大部分場景,既不會多出無用的搜索結果,也不會少。

第九章-Java客戶端(下)

基於Java客戶端(上),本文不再贅述如何創建一個Spring Data ElasticSearch工程,也不再做過多文字敘述。更多的請一定配合源碼使用,源碼地址https://github.com/yu-linfeng/elasticsearch6.x_tutorial/tree/master/code/spring-data-elasticsearch,具體代碼目錄在complex包。

本章請一定結合代碼重點關註如何如何通過Java API進行父子文檔的數據插入,以及查詢。

父子文檔的數據插入

父子文檔在ES中存儲的格式實際上是以鍵值對方式存在,例如在定義映射Mapping時,我們將子文檔定義為:

{
    ......
    "level":{
        "type":"join",
        "relations":{
                    "superior":"staff",
            "staff":"junior"
        }
    }
    ......
}

在寫入一條數據時:

{
    ......
    "level":{
        "name":"staff",
        "parent":"1"
    }
    ......
}

對於於Java實體,我們可以把level欄位設置為Map<String, Object>類型。關鍵註意的是,在使用Spring Data ElasticSearch時,我們不能直接調用sava或者saveAll方法。ES規定父子文檔必須屬於同一分片,也就是說在寫入子文檔時,需要定義routing參數。下麵是代碼節選:

BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
bulkRequestBuilder.add(client.prepareIndex("company", "employee", employeePO.getId()).setRouting(routing).setSource(mapper.writeValueAsString(employeePO), XContentType.JSON)).execute().actionGet();

一定參考源碼一起使用。

ES實在是一個非常強大的搜索引擎。能力有限,實在不能將所有的Java API一一舉例講解,如果你在編寫代碼時,遇到困難也請聯繫作者郵箱hellobug at outlook.com,或者通過公眾號coderbuff,解答得了的一定解答,解答不了的一起解答。

關註公眾號:CoderBuff,回覆“es”獲取《ElasticSearch6.x實戰教程》完整版PDF。
這是一個能給程式員加buff的公眾號 (CoderBuff)


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

-Advertisement-
Play Games
更多相關文章
  • 大家在玩抖音的過程中是不是會遇到下麵的問題呢,是否玩了很長時間的抖音,並且花了很多原創的心血,作品卻依然沒有上熱門是否感覺明明已經非常努力了,而熱門與你還是擦肩而過是否發現看到別人的視頻在某平臺上見過,卻在這裡上了熱門。 其實,對於80%的抖音用戶來說,這些問題都在困惑著大家,實際上做成功的僅僅只有 ...
  • "1. 為什麼要分散式" "2. 分散式架構帶來的挑戰" "3. 提高可靠性的設計" "3.1 監控設計" "3.2 一致性設計" "3.3 重試設計" "3.4 熔斷設計" "3.5 限流設計" "3.6 降級設計" "4. 提高性能的設計" "4.1 緩存設計" "4.2 非同步設計" "4.3 ...
  • 一、什麼是容器 1、容器是一種輕量級、可移植、自包含的軟體打包技術,使應用程式可以在幾乎任何地方以相同的方式運行。開發人員在自己的筆記本上創建並測試好的容器,無需任何修改就能夠在生產系統的虛擬機、或物理伺服器、或公有雲主機上運行。 2、容器與虛擬機的異同: 容器與虛擬機都是為應用提供封裝和隔離。 容 ...
  • 黑馬2017年java就業班全套視頻教程 ava學習路線圖.pptx等多個文件 ava學習路線圖.pptx等多個文件 - 2019-07-20 10:03 老師分享的資料 老師分享的資料 - 2019-07-20 10:03 面試相關資料 面試相關資料 - 2019-07-20 10:03 java ...
  • python全套視頻,第十五期,從入門到精通,基礎班,就業班,面試,軟體包 下載地址 ...
  • 一、變數 1.註意點: 在同一個“作用域”中,變數名不能重名,但是變數可以重新賦值。 2.什麼是作用域? 答:描述的是變數的有效範圍,在範圍之內是可以被訪問的,只要出了作用域就無法訪問(也就是在大括弧裡面才行) 3.關於變數的分類 (1)局部變數:在方法體中聲明的變數; (2)成員變數:在方法體外聲 ...
  •   Class文件是一組以8位位元組為基礎單位的二進位流,各個數據項目嚴格按照順序緊湊排列在Class文件中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內容幾乎全部是程式運行的必要數據,沒有空隙。當遇到需要占用8位位元組以上的數據項時,會按照高位在前的方式分割成若幹個8 ...
  • 註意: HttpServletRequest 請求中的 body 內容僅能調用 request.getInputStream(), request.getReader()和request.getParameter("key") 方法讀取一次,重覆讀取會報 java.io.IOException: S ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...