CodeArt入門教程(三)

来源:http://www.cnblogs.com/codeart/archive/2017/07/01/7103875.html
-Advertisement-
Play Games

5.領域模型設計 下麵我們創建賬戶子系統(AccountSubsystem),賬戶子系統雖然被門戶服務使用,但是子系統本身是獨立於任何服務存在的。所以我們為賬戶子系統創建獨立的項目解決方案: 子系統的項目解決方案比服務的項目解決方案需要引用的程式集少很多。除瞭解決方案文件夾Framework里需要引 ...


5.領域模型設計

  在開始考慮如何構建賬戶子系統的領域模型之前,我們先來看看關於CA里領域模型的基本概念。初次接觸這些陌生的概念確實會一知半解,不過沒有關係,大家實踐幾次領域設計後就會融會貫通,深刻體會到這些概念背後隱藏的優點。

  概念1:領域對象。領域模型里的一切對象都應該是領域對象所謂的領域對象就是指遵守領域規則的對象,這是它與普通對象最大的區別。領域規則主要由以下幾點組成:

  1) 領域對象永遠都不會為null。在詳細說明這一點之前,大家一定要知道這個規則跟資料庫里的欄位不能為null沒有任何關係。到目前為止,我們都沒有提到如何使用資料庫技術,這個教程之後的內容也不會講述與資料庫有關的話題,因為這些都與領域模型的建立沒有一丁點的關係,沒有資料庫一樣可以成功建立領域模型。各位一定要摒棄以前根深蒂固的開發思路,放空自己的思想,重新接收領域模型里的概念。

  言歸正傳,在領域的世界里,一切對象都是有其存在的意義的。什麼叫存在的意義?這體現在職責上,一個對象可以履行某項職責那麼這個對象就是有意義的存在。在領域世界里絕對不會出現不具備任何職責的對象。大多數情況下,對象需要提供方法以便其履行職責(.Net里的屬性也是方法,只是被包裝了而已)。

  每年企業都會向政府交納一定的稅收,這是企業的職責之一,所以領域世界里的企業模型會有一個計算納稅金額的方法,該方法會根據企業的盈利計算出交稅的金額。如果我問你,你創辦的公司今年要交多少稅?你可以通過該方法計算結果,告訴我需要交納10萬元。那麼,你沒有公司呢?你根本就沒有自己創辦的公司,那你要如何回答我這個問題?回答“很抱歉,我沒有公司”嗎?錯,程式的世界是理性的世界,一切都是嚴謹的,計算納稅金額的方法定義返回值是浮點數,那麼這個方法在任何情況下都應該返回一個浮點數(拋出異常例外)。當我問你的公司要交納多少稅,你只能告訴我具體的數值,我不管你是否有自己的公司,那是你自己要考慮的事情。所以,你應該回答“0”。

  分析到這裡,大家發現一個問題沒有,一個沒有創辦公司的人卻也可以知道“自己創辦的公司”今年要交納多少稅收,只不過由於他實際上沒有公司,所以計算的結果為0。因此,判定事物能否履行某項職責和這個事物的實例是否存在沒有必然的聯繫。我們只要設計了名稱為Corp的企業領域模型,在Corp類里定義了方法CalculateTax(計算稅收),那麼在任何情況下,Corp的實例都應該可以成功的調用該方法,至於計算的結果由Corp內部去處理,外界不用考慮這些細節。所以,即使不存在編號為135的Corp對象,我們依然可以根據編號135找到一個為Empty的Corp對象,調用該對象的CalculateTax方法會計算並得到稅收值為0的結果。

  所以,在領域的世界里,我們規定對象可以為Empty但是永遠不能為null,因為null表示不存在,不存在的對象無法履行任何職責。我們衡量對象是否存在的唯一依據就是職責,一個沒有職責的對象根本就不會被設計出來,所以領域世界里沒有不存在的對象,而Empty則表示對象是存在的,只不過數據都是空的,空對象依然可以履行職責,只是履行職責的結果會和非空對象執行的結果有所不同。下圖是體現這一規則的INotNullObject介面定義:

  2) 每個領域對象都具有驗證固定規則的能力。固定規則與業務規則不同,這組規則不會隨著使用對象的場景的變化而變化,它是對象自身固有的規則。例如人的年齡不可能有上萬歲,汽車的輪胎個數也不會有上百個,這些都是領域模型里的固定規則,不會隨著人或汽車這一事物在不同的使用場景里而發生規則的改變。CA通過實現ISupportFixedRules介面來完成領域對象驗證固定規則的能力:

  3) 我們可以明確的知道領域對象的倉儲狀態,領域對象的倉儲狀態與它在倉儲中保存的數據映射有關。當新建一個領域對象時,該對象的倉儲狀態就是“新建”的;使用完領域對象,將其存入倉儲後,該對象的狀態就是“乾凈”的;我們從倉儲中獲取一個對象,並更改了對象的屬性值,但還未提交給倉儲再次保存的時候,該對象的狀態就是“臟”的。對象的倉儲狀態與對象的持久化操作息息相關,所以CA明確規定每個領域對象都能提供和更改自身的倉儲狀態,該領域規則由IStateObject詮釋:

  除了以上3項規則外,我們在設計領域對象時還需要遵循一些其他的領域規則,這些規則會以約定的形式給出而不是顯示的介面,這在後面的實踐中會詳細討論。所有的領域對象都實現了IDomainObject介面:

  概念2:實體對象。這類對象具有可區別性,可以與其他事物區分開來。不同顏色、款式的衣服肯定是不同的實體,但是同樣顏色、同樣款式的衣服就一定是同一個實體嗎?在現實世界,人們可以感性的判定事物的唯一性,比如我買的衣服和你穿的衣服就算一模一樣,但是它們理所當然的不是同一件衣服。然而在程式世界一切都是理性的,所有判斷都是要有根據的。

  領域實體對象的職責之一就是幫助程式辨別不同的對象實例,區分事物的唯一性。每個實體對象都需要提供唯一的標識符來標識自己的存在。我們能夠通過該標識符找到唯一一個對應的實體,這是實體對象的重要特征。通過標識符可以引用到一個對象,這也是實體對象常被稱為引用對象的原因。

  我們可以為衣服設置一個叫做編號的唯一標示符。我買的衣服編號為1,你穿的衣服編號為2,就算衣服的其他屬性值都相同,但是由於唯一標識符不同,所以這兩件衣服在系統看來是不同的實體。當我調用洗衣機的方法去洗我的衣服(編號1),不會對你的衣服(編號為2)造成影響。也就是說,隨著時間軸的推移,實體對象的狀態會由於各種領域行為的發生而導致了改變,但我們依然可以根據標識符找到目標實體並關註狀態變化的情況。以洗衣為例,我的衣服(編號1)變的乾凈了,因此我很滿意。而你的衣服(編號2)依然那麼臟,但與我無關。

  這正是我們使用實體對象的根本原因:追蹤目標對象狀態的變化情況,以便其行為更清楚且可預測。也就是說,當我們需要持續關註一個事物的變化情況時,我們應該將該事物的模型設計為實體對象。以下是CA里實體對象必須實現的介面(該介面不必程式員實現,CA提供了實體對象的基類):

  概念3:值對象。如果一個對象的所有屬性都是用於從某種角度來描述另外一個事物的狀態時,這個對象就是值對象。

  繼續以衣服為例,我們可以把衣服的顏色、圖案、尺碼這3個屬性提取出來,作為一個單獨的值對象叫“外貌”,再由衣服去引用這個“外貌”。這樣我與你的衣服雖然不是同一件衣服,但是外貌特征可以相同,都是白色、花型圖案、XL碼的體恤衫。由於衣服是實體對象,我們依然可以清楚的區分衣服的唯一性,但是我們的衣服共用了相同的外貌特征。所以,值對象是沒有唯一性判斷依據的,它只是多個描述事物狀態值的綜合載體,如果兩個值對象的屬性值都相同,那麼我們認為這兩個值對象是同一個對象,因為他們描述事物的結果是一樣的。使用值對象需要遵守兩個領域規則:

  1) 值對象的所有屬性都是只讀的,只有當構造它的時候才能傳入值,構造完畢後,值對象的屬性將無法更改。這意味著當我想把衣服染成紅色的時候,我只能新建一個“紅色、花型圖案、XL碼”的外貌特征,並將衣服的外貌屬性設置成新建的這個值對象。我不能更改現有“外貌”的“顏色”屬性,因為一旦更改了,你衣服的外貌也被更改了,因為我和你的衣服都是引用的同一個外貌特征值對象。因此,在CA里,值對象的設計必須保證屬性是只讀的。

  2) 我們要保證值對象里的所有屬性都是從同一個角度去描述事物的。使用值對象的一個很重要的原因就是將複雜事物的屬性提取出來,單獨作為一個對象管理,這樣可以提高程式的可維護性。例如在訂單這個事物里涉及到的信息會很多:購買的商品、商品數量、優惠活動、購買人、支付方式、發貨方式等。其中“收件人所在的省份城市”、“詳細地址”、“郵編”這三項信息都與收貨地址有關,我們可以把這類描述收貨地址的屬性集中起來,設計一個地址(Address)的值對象來管理他們。訂單就可以使用該對象來承擔與收貨地址相關的職責。因此,如果一個值對象的屬性是雜亂無章的,不能從同一個角度描述事物的特點,那這個值對象是沒有存在價值的。

  概念4:內聚模型。一個模塊內部各個元素彼此結合的越緊密則它的內聚性越強。也正是由於這些元素結合的非常緊密,他們往往也只負責某一項任務,這也就是所謂的單一職責原則。

  我們之前創建了門戶服務,因為我們認為菜單、角色、許可權等事物是整個項目運行的基礎,這幾個事物之間或多或少存在某些聯繫,有一定的內聚性,所以將這幾個事物劃分到一個服務里共同驅動門戶服務工作。

  另外,角色、許可權、賬號三者的關係遠比菜單更加緊密,即使沒有菜單對象,他們仍然可以共同擔負起身份識別的職責。因此我們將他們納入到賬戶子系統中,賬戶子系統可以脫離門戶服務獨立被其他服務使用。所以,賬戶子系統是一個比門戶服務更高的內聚模塊。子系統的內聚性要遠高於服務。

  那麼在子系統內部呢?子系統內部我們依然可以按照類似的思路,根據對象之間的緊密程度劃分出更高的內聚模型,這就是我們要熟悉的第4個概念:領域模型里的內聚模型。在詳細討論這個概念之前,我們需要搞清楚如何判斷對象之間是緊密的。如果對象A引用了對象B,當A在履行某些職責的時候,需要對象B的支持,我們就說A依賴於B。這在程式實現里常常表現為當調用A的方法MA時,MA內部又調用了B的方法MB來協助MA順利的執行,這種情況下A和B的關係就比較緊密。那麼,有沒有比這種AB關係更加緊密的關係呢?

  有的。如果對象A的生命周期依賴於對象B的生命周期,也就是說,只有構造了B,A才有可能被構造,而當B被銷毀了,A就一定會被銷毀。對象A的生命周期始終依賴於對象B的生命周期,那麼這種關係就是更加緊密的,這也就是內聚模型里的對象之間的關係。下麵我們以書這個事物為例詳細講述內聚模型的領域規則:

  1) 每個內聚模型里都會有且僅有一個內聚根,內聚根是一個實體對象。這意味著你可以通過唯一標識找到該內聚根。我們將書(Book)設計成為一個內聚根,那麼Book對象就一定是實體對象。在CA里所有的內聚根都會實現IAggregateRoot介面,IaggregateRoot同時也實現了IEntityObject實體對象的介面:

  2)除了內聚根外,我們還可以在內聚模型里設計其他的對象,這些對象被稱為內聚成員。內聚成員可以是實體對象也可以是值對象。我們將書的封面(BookCover)設計為一個內聚值對象成員:

  ValueObject是值對象的基類。BookCover繼承了該類就表示BookCover是一個值對象。那麼我們如何體現出BookCover是Book的內聚成員呢?有兩種方式,我們先講解第一種方式,這種方式可以滿足絕大多數應用,而第二種方式是CA里的高級話題,在後續教程中再結合實例說明。我們可以為Book設計一個屬性Cover(封面):

  大家不用在意代碼截圖裡關於領域屬性定義的代碼,稍後就會有詳細說明。我們現在只用知道Book里有一個類型為BookCover的封面屬性,這就意味著BookCover是以Book為聚合根的內聚模型的成員。

  3) 我們可以通過聚合根的引用找到所有的內聚成員。或者說,如果你想找到某個內聚成員就必須先載入聚合根,再通過聚合根去查找內聚成員。通過類似book.Cover的代碼就可以找到某本書的封面了。但是你無法憑空獲取任何一個BookCover實例的信息,必須先找到它所屬的聚合根Book再通過Book的屬性Cover去訪問對應的信息。因此,值對象和實體對象都是無法脫離內聚根而單獨存在的,它們必須依附於聚合根組成內聚模型。
  也就是說,領域模型層里唯一獨立的元素就是“內聚模型”。所有的領域對象都會處於某個內聚模型內,脫離於內聚模型而單獨存在的領域對象是不存在的。Book和BookCover就屬於“以書為聚合根的內聚模型”。它們共同構建了現實事物里的“書”在領域世界里的模型。

  4) 內聚模型A不能直接引用另外一個內聚模型B里的成員,但是A可以引用B的聚合根。只允許外部對象保持對根的引用,對內部成員的臨時引用可以被傳遞出去,但僅在一次操作中有效。假設我們有一項需求,需要知道人們喜歡看的書籍。為了描述人這個事物,我們設計了關於人的內聚模型,該模型的聚合根為Person(人)。那麼在該內聚模型內,Person或者其他成員都不能直接引用BookCover,因為BookCover是書的內聚模型的成員。但是Person及其成員可以引用Book,然後通過Book訪問Cover,這在代碼上體現為person.MostLikeBook.Cover。表示找到person最愛的書籍(MostLikeBook)的封面(Cover)。這項規則的目的是用聚合根來控制其成員的訪問細節,由於根控制訪問,因此不能繞過它來修改內部對象。聚合根之所以被稱為“根”就是因它是內聚模型里一切的根基,它對成員有最高的控制權。

  5) 只有聚合根才能被加入到倉儲,內聚模型里的其他成員只能在聚合根被加入到倉儲的時候隨著根被一起保存。我們可以編寫代碼直接保存聚合根,但是無法編寫代碼直接保存內聚成員。內聚成員的持久化機制是伴隨著根的持久化而被保存的。示例代碼如下:

  在示例代碼中,我們創建了BookCover的示例cover,然後創建了Book的實例book並將book的封面屬性值設置為cover,最後創建Book的倉儲介面的實現repository,並調用repository.Add方法保存book對象。我們直接倉儲化的是Book類,而BookCover的保存會在倉儲內部實現,領域模型層並不主動保存內聚成員BookCover。倉儲的概念會在後文里單獨講解,這裡暫時不過多說明。

  雖然我們對以上規則進行了詳細的說明,但是大家肯定不能完全理解,甚至會覺得莫名其妙,疑惑遵守這些規則能帶來什麼好處呢?有這種想法很正常,畢竟各位沒有真正實踐過領域驅動的開發。在稍後的內容里,我們會結合實際例子告訴各位每項規則落實的細節,到時候你們就能深刻體會到它們帶來的好處了。目前你僅需知道有這些規則存在即可,不必太過刨根問底。

  在瞭解了內聚模型的設計原則後,我們不禁要問:為什麼會有內聚模型?為什麼我們不直接使用領域對象呢?

  原因有兩個,一方面,我們將關係緊密、職責統一的對象以領域規則的形式約束在一起,可以極大的提高系統的維護性。當程式需要修正的時候只需找到對應的內聚模型,調整其內部的實現細節即可,其他內聚模型受到的影響很少,連鎖改動降到了最低點,避免了混亂。另外一方面,我們所有的設計原理都是需要落實到代碼上的,需要有技術實施的基礎。設計思想可以脫離技術獨立存在,但是這些思想實施起來則需要充分考慮技術上的實現方式。舉一個常見的例子,在傳統開發中,我們為了避免併發衝突會對錶相關的數據進行鎖定。在領域開發中,我們同樣面臨類似的問題,只不過鎖定的是領域對象而不是直接鎖定數據(事實上領域模型層里沒有“數據”這個概念)。那麼如果沒有內聚模型,所有的對象都可以直接從倉儲中以帶鎖的形式載入,這就很容易造成死鎖。但是引入了內聚模型的概念後,我們只能載入聚合根,也只能鎖根。當根被鎖定了,內聚成員的任何操作都是線程安全的,這樣不僅避免了死鎖同時也滿足了開發程式的需要。因此,內聚模型可以提高程式的穩定性、健壯性。

  概念5:倉儲。倉儲是用於持久化領域對象的。領域對象要履行自己的職責必定需要有數據的支持,但是領域對象自身並不負責這項工作。對象的載入、修改、刪除等操作交由倉儲完成。你可以形象的認為倉儲就像一個倉庫那樣存放各類對象。當你需要某個領域對象的時候,它負責幫你從倉庫里取出來。當你創建了新對象也可以交由倉儲幫你存入倉庫。關於倉儲我們需要重點說明以下幾點設計原則:

  1) 在領域模型層里的倉儲一定是以介面的形式定義的,它不是一個對象,而是一組方法的約定。這組約定的實現由基礎設施層完成,領域模型層不會涉及到具體的存儲演算法,也不關心如何存儲對象。示例代碼:

  IBookRepository是聚合根Book的倉儲介面,這個介面約定了可以通過作者名稱查找相關的書籍對象。IBookRepository介面繼承了IRepository<TRoot>介面。該介面是CA提供的倉儲基礎介面,所有的倉儲都要繼承該介面。部分代碼如下:

  從代碼里我們可以看出IRepository<TRoot>定義了持久化領域對象的基本操作。因此我們使用IBookRepository可以添加、修改、刪除以及鎖定Book對象,還能根據作者名稱查找Book對象。

  2) 倉儲介面的約定必須是清晰、明確的。這項設計原則的意思是,你必須明確的告訴倉儲,你要根據哪些條件找到領域對象,你給出的條件不能是多樣化的。

  有些朋友在嘗試使用領域驅動開發的時候,將查詢條件設計成對象Query ,Query對象可以設置各種查詢子條件,例如 query.AddCondition(“name”, Condition.Equals,”作者名稱”);query.AddCondition(“sex”, Condition.Equals,”男”);這兩句代碼定義了查詢要根據name和sex來查找對象,當創建完query後將其交給倉儲,由倉儲負責翻譯query的含義,執行對應的查詢操作。

  在CA里是絕對不允許這樣設計的!倉儲介面處於領域模型層,它雖然是用於持久化領域對象的,但是倉儲也是領域模型的一部分,它從“如何找到事物”的角度反映了事物在這方面的本質特征。我們在示例里為IBookRepository倉儲定義的方法之一就是“根據作者名查找書”,該方法反應了”書這一事物可以通過作者這個事物被找到的”本質特征。而事物的本質特征必須是明確的,不能含糊不清。

  另外,從技術實現的角度來說,在傳統的開發模式里,程式員圍繞資料庫做開發,對數據的操作都是隨意的,是不可預知的。你可以在任意代碼段里對任意數據表進行任意的操作。這是混亂的根源,正是由於這個原因大多數項目都被做成豆腐渣了,所以我們一定要避免這一點。倉儲介面的約定必須是清晰、明確的。

  3) 倉儲介面的約定越少越好。我們認為倉儲介面定義的方法越少,“如何找到事物”這個維度里的本質特征就越清晰。試想一下,如果找到Book對象的方法有10幾個,那麼當我們需要找Book對象的時候必定混亂不堪(說淺顯點,就是10幾個方法給你去選,你都需要花費大量的時間去琢磨該用哪個方法)。大家一定要記住,倉儲介面不是存儲過程,我們不能為了尋求使用的方便而肆意增加它提供的方法。它提供的方法越少,領域模型對基礎設施層的依賴就越小。

  4) 倉儲的實現里不要有任何業務代碼。倉儲的職責就是存儲對象,它的代碼里不應該有任何關於“名稱是否重覆”、“轉賬是否成功”等一切與業務有關的邏輯。請記住,倉儲的實現只是機械式的“找到數據裝載領域對象”或者”得到領域對象後保存到數據倉庫中”。

  5) 只能創建聚合根的倉儲。聚合根是訪問內聚模型的入口,所以我們的倉儲介面里提供的方法僅能持久化聚合根。只是在倉儲的實現里會保存聚合根和相關的成員對象。請大家註意“倉儲介面”和“倉儲實現”的區別,倉儲介面屬於領域模型層,倉儲實現屬於基礎設施層。我們可以通過配置隨時切換倉儲介面的實現,而倉儲介面是領域模型層里的定義,使用該介面只能對聚合根進行持久化操作。內聚根的成員都是通過內聚根來訪問的而不是通過倉儲介面訪問。

  以上是CA里領域模型層的基本概念,這些概念大家一時半會還消化不了,後面的教程里我會結合實際的例子讓大家對它們有更加深刻的認識。其他高級話題涉及到的內容也會在後續教程里依次說明。在下個章節里,我們可以正式開始編碼工作了。


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

-Advertisement-
Play Games
更多相關文章
  • 通過學習,一句話概括Java工廠模式的特點——通過建立一個工廠來創建對象,不必關心構造對象實例能不能被實例化啊等諸多細節和複雜過程。 工廠模式呢?就像我們從勞動密集型社會轉型到技術密集型社會。打個比方,從前要製造一個桌子,從上山選木頭、砍木頭、運木頭,到設計桌子,製造桌子等細節問題都需要一個人去做好 ...
  • 題目描述 輸入N(N<=10000),驗證4~N所有偶數是否符合哥德巴赫猜想。 (N為偶數)。 如果一個數,例如10,則輸出第一個加數相比其他解法最小的方案。如10=3+7=5+5,則10=5+5是錯誤答案。 輸入輸出格式 輸入格式: 第一行N 輸出格式: 4=2+2 6=3+3 …… N=x+y ...
  • 一、配置Maven環境 1.下載Maven 下載鏈接http://maven.apache.org/download.cgi 2.下載完成解壓壓縮包並創建本地倉庫文件夾 3.打開解壓縮文件,配置本地倉庫路徑 4.配置Maven環境變數 5.在cmd中查看maven是否配置正確 在cmd中輸入mvn ...
  • 題目描述 Farmer John's N (1 <= N <= 100,000) cows are lined up in a row and numbered 1..N. The cows are conducting another one of their strange protests, ...
  • JSON 即是key-value存儲方式,存儲內容對象和數組 ...
  • 題目描述 湯姆斯生活在一個等級為0的星球上。那裡的環境極其惡劣,每天12小時的工作和成堆的垃圾讓人忍無可忍。他嚮往著等級為N的星球上天堂般的生活。 有一些航班將人從低等級的星球送上高一級的星球,有時需要向駕駛員支付一定金額的費用,有時卻又可以得到一定的金錢。 湯姆斯預先知道了從0等級星球去N等級星球 ...
  • 題目背景 USACO 題目描述 很少有人知道奶牛愛吃蘋果。農夫約翰的農場上有兩棵蘋果樹(編號為1和2), 每一棵樹上都長滿了蘋果。奶牛貝茜無法摘下樹上的蘋果,所以她只能等待蘋果 從樹上落下。但是,由於蘋果掉到地上會摔爛,貝茜必須在半空中接住蘋果(沒有人愛吃摔爛的蘋果)。貝茜吃東西很快,她接到蘋果後僅 ...
  • 題目背景 Usaco Feb08 Bronze 題目描述 為了避免餐廳過分擁擠,FJ要求奶牛們分2批就餐。每天晚飯前,奶牛們都會在餐廳前排隊入內,按FJ的設想,所有第2批就餐的奶牛排在隊尾,隊伍的前半部分則由設定為第1批就餐的奶牛占據。由於奶牛們不理解FJ的安排,晚飯前的排隊成了一個大麻煩。 第i頭 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...