淺談個人對12306的核心用戶訴求的核心模型設計思路和架構設計

来源:http://www.cnblogs.com/netfocus/archive/2016/02/12/5187241.html
-Advertisement-
Play Games

前言 春節期間,無意中看到一篇文章,文章中講到12306的業務複雜度遠遠比淘寶天貓這種電商網站要複雜。後來自己想想,也確實如此。所以,很想挑戰一下12306這個系統的核心領域模型的設計。一般的電商網站,購買都是基於商品的概念,每個商品有一定量的庫存,用戶的購買行為是針對商品的。當用戶發起購買行為時,


前言

春節期間,無意中看到一篇文章,文章中講到12306的業務複雜度遠遠比淘寶天貓這種電商網站要複雜。後來自己想想,也確實如此。所以,很想挑戰一下12306這個系統的核心領域模型的設計。一般的電商網站,購買都是基於商品的概念,每個商品有一定量的庫存,用戶的購買行為是針對商品的。當用戶發起購買行為時,系統只需要生成訂單並對用戶要購買的商品減庫存即可。但是,12306就不是那麼簡單了,具體複雜在哪裡,我下麵會進一步分析。

另外一個讓我寫這篇文章的原因,是我發現也許是否是因為目前12306的核心領域模型設計的不夠好,導致用戶購票時要處理的業務邏輯異常複雜,維護數據一致性的難度也幾百倍的上升,同時面對高併發的訂票也難以支持很高的TPS。我覺得,越是複雜的業務,就越要重視業務分析,重視領域模型的抽象和設計。如果不假思索,憑以往經驗行事,則很可能會被以往的設計經驗先入為主,陷入死衚衕。我發現技術人員往往更註重技術層面的解決方案,比如一上來就分析如何集群、如何負載均衡、如何排隊、如何分庫分表、如何用鎖,如何用緩存等技術問題,而忽略了最根本的業務層面的思考,如分析業務、領域建模。我認為越是複雜的業務系統,則越要設計一個健壯的領域模型。如果一個系統的架構我們設計錯了,還有補救的餘地,因為架構最終沉澱的只是代碼,調整架構即可(一個系統的架構本身就是不斷演進的);而如果領域模型設計錯了,那要補救的代價是非常大的,因為領域模型沉澱的是數據結構及其對應的大量數據,對任何一個大型系統,要改核心領域模型都是成本非常高的。

本文的重點不是在如何解決高併發的問題,而是希望從業務角度去分析,12306的理想模型應該是怎麼樣的。網上目前談12306的文章貌似都是千篇一律的只談技術,不談業務分析和如何建模的。所以我想寫一下自己的設計和大家交流學習。

需求簡述

12306這個系統,核心要解決的問題是網上售票。涉及到2個角色使用該系統:用戶、鐵道部。用戶的核心訴求是查詢餘票、購票;鐵道部的核心訴求是售票。購票和售票其實是一個場景,對用戶來說是購票,對鐵道部來說是售票。因此,我們要設計一個線上的網站系統,解決用戶的查詢餘票、購票,以及鐵道部的售票這3個核心訴求。看起來,這3個場景都是圍繞火車票展開的。

查詢餘票:用戶輸入出發地、目的地、出發日三個條件,查詢可能存在的車次,用戶可以看到每個車次經過的站點名稱,以及每種座位的餘票數量。

購票:購票分為訂票和付款兩個階段,本文重點分析訂票的模型設計和實現思路。

其實還有很多其他的需求,比如給不同的車次設定銷售座位數配額,以及不同的區段設置不同的限額。我覺得這個需求不是核心最重要的訴求,所以,本文針對這個需求不做具體討論,也不是本文分析設計的重點。

需求分析

確實,12306也是一個電商系統,而且看起來商品就是票了。因為如果把一張票看成是一個商品,那購票就類似於購買商品,然後每張票都有庫存,商品也有庫存的概念。但是如果我們仔細想想,會發現12306要複雜很多,因為我們無法預先確定好所有的票,如果非要確定,那隻能通過窮舉法了。

我們以北京西到深圳北的G71車次高鐵為例(這裡只考慮南下的方向,不考慮深圳北到北京西的,那是另外一個車次,叫G72),它有17個站(北京西是01號站,深圳北是17號站),3種座位(商務、一等、二等)。錶面看起來,這不就是3個商品嗎?G71商務座、G71一等座、G71二等座。大部分輕易噴12306的技術人員(包括某些中等規模公司的專家、CTO)就是在這裡栽第一個跟頭的。實際上,G71有136*3=408種商品(408個SKU),怎麼算來的?如下:

如果賣北京西始發的,有16種賣法(因為後面有16個站),北京西到:保定、石家莊、鄭州、武漢、長沙、廣州、虎門、深圳。。。。都是一個獨立的商品,同理,石家莊上車的,有15種下車的可能,以此類推,單以上下車的站來計算,有136種票:16+15+14....+2+1=136。每種票都有3種座位,一共是408個商品。

為了方便後面的討論,我們先明確一下票是什麼?

一張票的核心信息包括:出發時間、出發地、目的地、車次、座位號。持有票的人就擁有了一個憑證,該憑證表示持有它的人可以坐某個車次的某個座位號,從某地到某地。所以,一張票,對用戶來說是一個憑證,對鐵道部來說是一個承諾;那對系統來說是什麼呢?不知道。這就是我們要分析業務,領域建模的原因,我們再繼續思考吧。

明白了票的核心信息後,我們再看看G71這個車次的高鐵,可以賣多少張票?

討論前先說明一下,一輛火車的物理座位數(站票也可以看成是一種座位,因為站票也有數量配額)不等於可用的最大配合。所有的物理座位不可能都通過12306網站來銷售,而是只會銷售一部分,比如40%。其餘的還是會通過線下的方式銷售。不僅如此,可能有些站點上車的人會比較多,有些比較少,所以我們還會給不同的區間配置不同的限額。比如D31北京南至上海共有765張,北京南有260張,楊柳青有80張,泰安有76張。如果楊柳青的80張票售完就會顯示無票,就算其他站有票也會顯示無票的。每個車次肯定會有各種座位的配額和限額的配置的,這種配置我目前無法預料,但我已經把這些規則都封裝近車次聚合根里了,所有的配置策略都是基於座位類型、站點、區間配置的。關於票的配置抽象出來,我覺得主要有兩種:1)某個區段最多允許出多少張;2)某個區段最少允許出多少張。當用戶訂票時,把用戶指定的區段和這兩種配置條件進行比較,兩個條件都滿足,則可以出票。不滿足,則認為無票了。下麵舉個例子:

ABCDEFG,這是所有站點。座位總配額是100,假設B站點上車,E站下車的人比較少,那我們就可以設定BE這個區段最多只能出10張票。所以,只要是用戶的訂票是在這個區段內的,就最多出10張。再比如,一列車次,總共100個座位配額,希望全程票最少滿足80張,那我們只要給AG這個區段設定最少80張。那任何訂票請求,如果是子區間的,就不能超過100-80,即20張。這兩種條件必須同時滿足,才允許出票。

但是,不管如何做配額和限額,我們總是針對某個車次進行配置,這些配置只是車次內部售票時的一些額外的判斷條件(業務規則),不影響車次模型的核心地位和對外暴露的功能。所以,為了本文討論的清楚起見,我後續的討論都不涉及配額和限額的問題,而是認為任何區段都可以享受火車最大的物理座位數。

並且,為了討論問題方便,我們減少一些站點來討論。假設某個車次有A,B,C,D四個站點。那001這個人購買了A,B這個區間,系統會分配給001一個座位x;但是因為001坐到B站點後會下車,所以相當於x這個座位又空出來了,也就是說,從B站點開始,系統又可以認為x這個座位是可用的。所以,我們得出結論:同一個座位,其實可以同時出售AB,BC這兩張票。通過這個簡單的分析,我們知道,一列火車雖然只有有限的座位數,比如1000個座位。但可以賣出的票遠遠不止1000個。還是以A,B,C,D四個站點為例,假如火車總共有1000個座位,那AB可以賣1000張,BC也可以賣1000張,同樣,CD也可以賣1000張。也就是說,理論上最多可以賣出3000張票。但是如果換一種賣法,所有人都是買ABCD的票,也就是說所有的票都是經過所有站點的,那就是最多只能賣出1000張票了。而實際的場景,一定是介於1000到3000之間。然後實際的G71這個車次,有17個站,那到底可以賣出多少個票,大家應該可以算了吧。理論上這17個站中的任意兩個站點之間所形成的線段,都可以出售為一張票。我數學不好,算不太清楚,麻煩有數學好的人幫我算算,呵呵。

通過上面的分析,我們知道一張票的本質是某個車次的某一段區間(一條線段),這個區間包含了若幹個站點。然後我們還發現,只要區間不重疊,那座位就不會發生競爭,可以被回收利用,也就是說,可以同時預先出售。

另外,經過更深入的分析,我們還發現區間有4種關係:1)不重疊;2)部分重疊;3)完全重疊;4)覆蓋;不重疊的情況我們已經討論過了,而覆蓋也是重疊的一種。所以我們發現如果重疊,比如有兩個區間發生重疊,那重疊部分的區間(可能誇一個或多個站點)是在爭搶座位的。因為假設一列火車有100個座位,那每個原子區間(兩個相鄰站點的連線),最多允許重疊99次。

所以,經過上面的分析,我們知道了一個車次能夠出售一張車票的核心業務規則是什麼?就是:這張車票所包含的每個原子區間的重疊次數加1都不能超過車次的總座位數,實際上重疊次數+1也可以理解為線段的厚度。

模型設計

上面我分析了一下票的本質是什麼。那接下來我們再來看看怎麼設計模型,來快速實現購票的需求,重點是怎麼設計商品聚合以及減庫存的邏輯。

傳統電商的思路

如果按照普通電商的思路,把票(站點區間)設計為商品(聚合根),然後為票設計庫存數量。我個人覺得是很糟糕的。因為一方面這種聚合根非常多(上面的G71就有408個);另一方面,即便枚舉出來了,一次購票也一定會影響非常多其他聚合根的庫存數量(只要被部分或全部重疊的區間都受影響)。這樣的一次訂單處理的複雜度是難以評估的。而且這麼多聚合根的更新要在一個事務里,這不是為難資料庫嗎?而且,這種設計必然帶來大量的事務的併發衝突,很可能導致資料庫死鎖。總之,我認為這種是典型的由於領域模型的設計錯誤,導致併發衝突高、數據持久化落地困難。或者如果要解決併發問題,只能排隊單線程處理,但是仍然解決不了要在一個事務里修改大量聚合根的尷尬局面。聽說12306是採用了Pivotal Gemfire這種高大上的記憶體資料庫,我對這個不太瞭解。我不可想象要是不使用記憶體資料庫,他們要怎麼實現車次內的票之間的數據強一致性(就是保證所有出售的票都是符合上面討論的業務規則的)?所以,這種設計,我個人認為是思維定勢了,把火車票看成是普通電商的商品來看待。所以,我們有時做設計又要依賴於經驗,又要不能被以往經驗所束縛,真的不容易,關鍵還是要根據具體的業務場景多多深入分析,儘量分析抽象出問題的本質出來,這樣才能對症下藥。那是否有其他的設計思路呢?

我的思路

通過上面的分析我們知道,其實任何一次購票都是針對某個車次的。我們看看一個車次包含了哪些信息?一個車次包括了:1)車次名稱,如G71;2)座位數,實際座位數會分類型,比如商務座20個,一等座200個;二等座500個;我們這裡為了簡化問題,可以暫時忽略類型,我認為這個類型不影響核心的模型的設計決策。需要格外註意的是:這裡的座位數不要理解為真實的物理座位數,很有可能比真實的座位數要少。因為我們不可能把一個車次的所有座位都在網上通過12306來出售,而是只出售一部分,具體出售多少,要由工作人員人工指定。3)經過的站點信息(包括站點的ID、站點名稱等),註意:車次還會記錄這些站點之間的順序關係;4)出發時間;看過GRASP九大模式中的信息專家模式的同學應該知道,將職責分配給擁有執行該職責所需信息的類。我們這個場景,車次具有一次出票的所有信息,所以我們應該把出票的職責交給車次。另外學過DDD的同學應該知道,聚合設計有一個原則,就是:聚合內強一致性,聚合之間最終一致性。經過上面的分析,我們知道要產生一張票,其實要影響很多和這個票對應的線段相交的其他票的可用數量。因為所有的站點信息都在車次聚合內部,所以車次聚合內部自然可以維護所有的原子區間,以及每個原子區間的可用票數(相當於是庫存數)。當一個原子區間的可用票數為0的時候,意味著火車針對這個區間的票已經賣完了。所以,我們完全可以讓車次這個聚合根來保證出票時對所有原子區間的可用票數的更新的強一致性。對於車次聚合根來說,這很簡單,因為只是幾次簡單的記憶體操作而已,耗時可以忽略。一列火車假如有ABCD四個站點,那原子區間就是3個。對於G71,則是16個。

然後基於上面的聚合設計,出票時扣減庫存的邏輯是:

根據訂單信息,拿到出發地和目的地,然後獲取這段區間里的所有的原子區間。然後嘗試將每個原子區間的可用票數減1,如果所有的原子區間都夠減,則購票成功;否則購票失敗,提示用戶該票已經賣完了。是不是很簡單呢?知道了出票的邏輯,那退票的邏輯也就很簡單了,就是把這個票的所有原子區間的可用票數加1就OK了。如果我們從線段的厚度的角度去考慮,那出票時,每個原子區間的厚度就是+1,退票時就是減一。就是相反的操作,但本質是一樣的。

所以,通過這樣的思路,我們將一次訂票的處理控制在了一個聚合根里,用聚合根內的強一致性的特性保證了訂票處理的強一致性,同時也保證了性能,免去了併發衝突的可能性。傳統電商那種把票單做類似商品的核心聚合根的設計,我當時第一眼看到就覺得不妥。因為這違背了DDD強調的強一致性應該由聚合根來保證、聚合根之間的最終一致性通過Saga來保證的原則。

還有一個很重要的概念我想說一下我的看法,就是座位和區間的關係。因為有些朋友和我講,考慮座位號的問題,雖然都能減1,座位號也必須是同一個。我覺得座位是全局共用的,和區段無關(也許我的理解完全有誤,請大家指正)。座位是一個物理概念,一個用戶成功購買了一張票後,座位就會少一個,一張票唯一對應一個座位,但是一個座位有可能會對應多張票;而區間是一個邏輯上的概念,區間的作用有兩個:1)表示票的出發地和目的地;2)記錄票的可用數額。如果區間能連通(即該區間內的每個原子區間的可用數額都大於0),則表示允許擁有一個座位。所以,我覺得座位和票(區間)是兩個維度的概念。

模型分析總結:我認為票不是核心聚合根,票只是一個計算的結果,一個憑證而已,票本身沒有什麼邏輯;12306真正的核心模型應該是車次,車次具有出票的職責,並以強一致性的方式維護一次出票(或退票)時所有原子區間的可用票數。

架構設計(非本文重點,沒興趣的朋友可以略過,呵呵)

我覺得12306這樣的業務場景,非常適合使用CQRS架構;因為首先它是一個查多寫少、但是寫的業務邏輯非常複雜的系統。所以,非常適合做架構層面的讀寫分離,即採用CQRS架構。而且應該使用數據存儲也分離的CQRS。這樣CQ兩端才可以完全不需要顧及對方的問題,各自優化自己的問題即可。我們可以在C端使用DDD領域模型的思路,用良好設計的領域模型實現複雜的業務規則和業務邏輯。而Q端則使用分散式緩存方案,實現可伸縮的查詢能力。

訂票的實現思路

同時藉助像ENode這樣的框架,我們可以實現in-memory + Event Sourcing的架構。Event Sourcing技術,可以讓領域模型的所有狀態修改的持久化統一起來,本來要用ORM的方式保存聚合根最新狀態的,現在只需要簡單的通用的方式保存一個事件即可(一次訂票只涉及一個車次聚合根的修改,修改只產生一個事件,只需要持久化一個事件(一個JSON串)即可,保證了高性能,無須依賴事務,而且通過ENode可以解決併發問題)。我們只要保存了聚合根每次變化的事件(事件的結構怎麼設計,本文不做多的介紹了,大家可以思考下),就相當於保存了聚合根的最新狀態。而正是由於Event Sourcing技術的引入,讓我們的模型可以一直存活在記憶體中,即可以使用in-memory技術。不要小看in-memory技術,in-memory技術在某些方面對提高命令的處理性能非常有幫助。比如就以我們車次聚合根處理出票的邏輯,假設某個車次有大量的命令發送到分散式消息隊列,然後有一臺機器訂閱了這個隊列的消息,然後這台機器處理這個車次的訂票命令時,由於這個車次聚合根一直在記憶體,所以就省去了每次要去資料庫取出聚合根的步驟,相當於少了一次資料庫IO。這樣的好處是,因為一個車次能夠真正出售的票是有限的,因為座位就那麼幾個,比如就1000個座位,估計一般正常情況也就出個2000個左右的座位吧(具體能出多少張票要取決於區間的相交程度,上面分析過)。也就是說,這個聚合根只會產生2000個事件,也就是說只會有2000個訂票命令的處理是會產生事件,並持久化事件;而其餘的大量命令,因為車次在記憶體計算後發現沒有餘票了,就不會做任何修改,也不會產生領域事件,這樣就可以直接處理下一個訂票命令了。這樣就可以大大提高處理訂票命令的性能。

另外一個問題我覺得還需要提一下,因為用戶訂票成功後,還需要付款。但用戶有可能不去付款或者沒有在規定的時間內完成付款。那這種情況下,系統會自動釋放該用戶之前訂購的票。所以基於這樣的需求,我們在業務上需要支持業務級別的2pc。即先預扣庫存,也就是先占住這張票一定時間(比如15分鐘),然後付款成功後再真實給你這張票,系統做真正的庫存修改。通過這樣的預扣處理,可以保證不會出現超賣的情況。這個思路其實和傳統電商比如淘寶這樣的系統類似,我就不多展開了,我之前寫的Conference案例也是這樣的思路,大家有興趣的可以去看一下我之前錄製的視頻。

查詢餘票的實現思路

我覺得餘票的查詢的實現相對簡單。雖然對於12306來說,查詢的請求占了80%,提交訂單的請求只占20%。但查詢由於對數據沒有修改,所以我們完全可以使用分散式緩存來實現。我們只需要精心設計好緩存的key即可;緩存key的多少要看成本,如果所有可能的查詢都設計對應的key,那時間複雜度為1,查詢性能自然高;但代價也大,因為key多了。如果想key少一點,那查詢的複雜度自然要上去一點。所以緩存設計無非就是空間換時間的思路。然後,緩存的更新無非就是:自動失效、定時更新、主動通知3種。通過CQRS架構,由於CQ兩端是事件驅動的,當C端有任何狀態變化,都會產生對應的事件去通知Q端,所以我們幾乎可以做到Q端的準實時更新。

同時由於CQ兩端的完全解耦,Q端我們可以設計多種存儲,如資料庫和緩存(Redis等);資料庫用於線下維護關係型數據,緩存用戶實時查詢。資料庫和緩存的更新速度相互不受影響,因為是並行的。對同一個事件,可以10台機器負責更新緩存,100台機器負責更新資料庫。即便資料庫的更新很慢,也不會影響緩存的更新進度。這就是CQRS架構的好處,CQ的架構完全不同,且我們隨時可以重建一種新的Q端存儲。不知道大家體會到了沒有?

關於緩存key的設計,我覺得主要從查詢餘票時傳遞的信息來考慮。12306的關鍵查詢是:出發地、目的地、出發日期三個信息。我覺得有兩種key的設計思路:1)直接設計了該查詢條件的key,然後快速拿到車次信息,直接返回;這種方式就是要求我們系統已經枚舉了所有車次的所有可能出現的票(區間)的緩存key,相信你一定知道這樣的key是非常多的。2)不是枚舉所有區間,而是把每個車次的每個原子區間(相鄰的兩個站點所連成的直線)的可用票數作為key。這樣,key就非常少了,因為車次假如有10000個,然後每個車次平均15個區間,那也就15W個key而已。當我們要查詢時,只需要把用戶輸入的出發地和目的地之間的所有原子區間的可用票數都查出來,然後比較出最小可用票數的那個原子區間。則這個原子區間的可用票數就是用戶輸入的區間的可用票數了。當然,到這裡我提到考慮出發日期。我認為出發日期是用來決定具體是哪個車次聚合根的。同一個車次,不同的日期,對應的聚合根實例是不同的,即便是同一天,也可能有多個車次聚合根,因為有些車次一天有幾班的,比如上午9點發車的一班,下午3點發車的一般。所以,我們也只要把日期也作為緩存key的一部分即可。

總結

本文完全是憑自己對12306這個網站的核心業務的簡單思考而得到的一些設計結果。如果真正的DDD領域建模,更多的是要和業務一線的工作人員、領域專家進行深入溝通,才能更深入的瞭解該領域內的業務知識,從而才能設計出更靠譜的領域模型和架構設計。我本人非常慚愧因為沒有上12306買過火車票,家離的比較近,就算要買也是家人給我買:)所以,本文所分享的內容難免是紙上談兵。但我覺得12306這個系統的業務確實比傳統的電商系統要複雜,且併發又這麼高。所以,我覺得這個系統真的很值得大家重視模型的設計,而不只是只關註技術層面的實現。2016年,我有計劃打算基於ENode實現一套12306的核心功能,比如餘票查詢、訂票的功能。有興趣的朋友可以加入ENode QQ群(185916873)報名哦。


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

-Advertisement-
Play Games
更多相關文章
  • 談及此部分,多多少少有一定的難度,筆者寫下這篇文章,差不多是在學習FPGA一年之後的成果,儘管當時也是看過類似的文章,但是都沒有引起筆者註意,筆者現在再對此知識進行梳理,也發現了有很多不少的收穫。筆者根據網上現有的資源,作進一步的總結,希望能夠有所幫助。 一個不錯的網站,類似於一個手冊,隨時可以去查
  • TTL,CMOS以及LVTTL,LVCMOS TTL和CMOS是數字電路中兩種常見的邏輯電平,LVTTL和LVCMOS是兩者低電平版本。TTL是流控器件,輸入電阻小,TTL電平器件速度快,驅動能力大,但功耗大。CMOS是MOS管邏輯,為壓控器件,且輸入電阻極大,CMOS電平器件速度慢,驅動能力不足T
  • 一致性Hash演算法 關於一致性Hash演算法,在我之前的博文中已經有多次提到了,MemCache超詳細解讀一文中"一致性Hash演算法"部分,對於為什麼要使用一致性Hash演算法、一致性Hash演算法的演算法原理做了詳細的解讀。 演算法的具體原理這裡再次貼上: 先構造一個長度為232的整數環(這個環被稱為一致性
  • Eclipse快捷鍵大全 Ctrl+1 快速修複(最經典的快捷鍵,就不用多說了)Ctrl+D: 刪除當前行 Ctrl+Alt+↓ 複製當前行到下一行(複製增加)Ctrl+Alt+↑ 複製當前行到上一行(複製增加)Alt+↓ 當前行和下麵一行交互位置(特別實用,可以省去先剪切,再粘貼了)Alt+↑ 當
  • 想必很多人都看過“頭文件中用到的 #ifndef/#define/#endif 來防止該頭文件被重覆引用”。但是是否能理解“被重覆引用”是什麼意思?頭文件被重覆引用了,會產生什麼後果?是不是所有的頭文件中都要加入#ifndef/#define/#endif 這些代碼? 1、 其實“被重覆引用”是指一
  • 拉丁方陣是一種n×n的方陣,方陣中恰有n種不同的元素,每種元素恰有n個,並且每種元素在一行和一列中 恰好出現一次。著名數學家和物理學家歐拉使用拉丁字母來作為拉丁方陣里元素的符號,拉丁方陣因此而得名。 代碼思路簡介:使用單迴圈鏈表來實現輸出拉丁方陣。 在輸出第一行的時候,從第一個元素開始輸出,會輸出至
  • 一、什麼是適配器 適配器是將一種介面改造成另外一種介面的一個包裝類; 二、補充說明 有兩種實現方式,一種是通過繼承,另外一種通過組合; 使用組合實現的時候,跟裝飾者模式有點類似,不過側重點不同,裝飾者模式是給對象增加新行為,適配器是為了適配介面; 優點:符合開閉原則;適配者類和目標類解耦;代碼復用;
  • 模式結構: 實現: 1 package com.shejimoshi.create.FactoryMethod; 2 3 4 /** 5 * 功能:工廠方法模式 6 * 意圖 : 7 * 定義一個用於創建對象的介面,讓子類決定實例化哪一個類。 8 * 適用性: 9 * 當一個類不知道它所必須創建的對
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...