應用程式框架(一):DDD分層架構:領域實體(基礎篇)

来源:https://www.cnblogs.com/supersnowyao/archive/2018/03/01/8488127.html
-Advertisement-
Play Games

一、什麼是實體 由標識來區分的對象稱為實體。 實體的定義隱藏了幾個信息: 兩個實體對象,只要它們的標識屬性值相等,哪怕標識屬性以外的所有屬性值都不相等,這兩個對象也認為是同一個實體,這意味著兩個對象是同一實體在其生命周期內的不同階段。 為了能正確區分實體,標識必須唯一。 實體的標識屬性值是不可變的, ...


  一、什麼是實體

  由標識來區分的對象稱為實體。

  實體的定義隱藏了幾個信息:

  • 兩個實體對象,只要它們的標識屬性值相等,哪怕標識屬性以外的所有屬性值都不相等,這兩個對象也認為是同一個實體,這意味著兩個對象是同一實體在其生命周期內的不同階段。
  • 為了能正確區分實體,標識必須唯一。
  • 實體的標識屬性值是不可變的,標識屬性以外的屬性值是可變的。如果標識值不大穩定,偶爾會變化,那麼就無法將該實體在生命周期內的所有變化關聯在一起,這可能導致嚴重的問題。

  二、實體標識

  從實體的定義可以發現,標識是實體的關鍵特征。關於標識,有幾個值得思考的問題。

  1、將什麼選作標識

  比如中國人都有身份證,身份證號碼是唯一的,那麼可能會有人使用身份證號作為實體標識。這看起來好像沒什麼問題,但身份證每隔N年就會換代,身份證號可能發生變化。這違反了標識不可變性和穩定性要求,所以不適合作為實體標識。

  對於手工錄入流水號作為實體標識的情況,要用戶自己保證唯一性已經很困難,如果提供了修改標識的功能,將導致標識不穩定,如果不提供,用戶錄入錯誤就只能刪除後重新輸入,這就太不人道了。

  通過程式自動生成一個有意義的流水號作為實體標識,並且不提供修改,這可能是可行的,對於唯一性要求,程式和資料庫可以保證,另外不允許修改,就可以保證穩定性。對於像訂單號一類的場景可能有效。

  可以看到,使用有意義的值作為標識有一定風險,並且難度比較大,為了簡單和方便,生成一個無意義的唯一值作為標識更可行。

  2、為標識選擇什麼類型

  對於使用Sql Server的同學,一般會傾向於使用int類型,映射到資料庫中的自增長int。它的優勢是簡單,唯一性由資料庫保障,占用空間小,查詢速度快。我之前也採用了很長時間,大部分時候很好用,不過偶爾會很頭痛。

  由於實體標識需要等到插入資料庫之後才創建出來,所以你在保存之前不可能知道標識值是多少,如果在保存之前需要拿到Id,唯一的方法是先插入資料庫,得到Id以後,再執行另外的操作,換句話說,需要把本來是同一個事務中的操作分成多個事務執行。

  使用自增長int類型的第二個毛病是,如果需要合併同一個實體對應的多個數據表記錄,悲劇就會發生。比如你現在把一個實體對應的記錄水平分區到多個資料庫的表中,由於Id是自增長的,每個表都會從1開始自增,你要合併到一個表中,Id就會發生衝突。所以對於比較大點的項目,使用自增長int類型是有一些風險的。

  對於比較小,且不是太複雜的項目,使用自增長int類型是個不錯的選擇,但如果你經常碰到上面提到的問題,說明你需要重新選擇標識類型了。

  要解決以上問題,最簡單的方法是選擇Guid作為標識類型。

  它的主要優勢是生成Guid非常容易,不論是Js,C#還是在資料庫中,都能輕易的生成出來。另外,Guid的唯一性很強,基本不可能生成出兩個相同的Guid。

  Guid類型的主要缺點是占用空間太大。另外實體標識一般映射到資料庫的主鍵,而Sql Server會預設把主鍵設成聚集索引,由於Guid的不連續性,這可能導致大量的頁拆分,造成大量碎片從而拖慢查詢。一個解決辦法是使用Sql Server來生成Guid,它可以生成連續的Guid值,但這又回到了老路,只有插入資料庫你才知道具體的Id值,所以行不通。另一個解決辦法是把聚集索引移到其它列上,比如創建時間。如果你打算把聚集索引繼續放到Guid標識列上,可以觀察到碎片一般都在90%以上,寫一個Sql腳本,定時在半夜整理一下碎片,也算一個勉強的辦法。(針對此種情況,可以參考abp框架中生成guid的類庫方法,其機制是可以預先生成guid,同時生成的guid是遞增的。

  如果生成一個有意義的流水號來作為標識,這時候標識類型就是一個字元串。

  有些時候可能還要使用更複雜的組合標識,這一般需要創建一個值對象作為標識類型。

  我目前一般都使用Guid作為標識類型,偶爾使用字元串類型。

  對於需要更詳細的瞭解實體標識,請參考《企業應用架構模式》標識域一節。

  三、實體層超類型的實現

  既然每個實體都有一個標識,那麼為所有實體創建一個基類就顯得很有用了,這個基類就是層超類型,它為所有領域實體提供基礎服務。

  為了降低依賴性,現在需要在本系列應用程式框架的VS解決方案中增加一個類庫Util.Domains和單元測試項目Util.Domains.Tests,並使用解決方案文件夾進行分類,如下圖所示。

  

  各程式集的依賴關係如下圖所示。

   

  實體基類可以取名為EntityBase,它應該是一個抽象類,具有一個名為Id的屬性。如果採用int作為標識類型,代碼可能是這樣。

namespace Util.Domains {
    /// <summary>
    /// 領域實體
    /// </summary>
    public abstract class EntityBase{
        /// <summary>
        /// 初始化領域實體
        /// </summary>
        /// <param name="id">標識</param>
        protected EntityBase( int id ) {
            Id = id;
        } 

        /// <summary>
        /// 標識
        /// </summary>
        public int Id { get; private set; }
    }
}

   觀察上面的代碼,這裡要考慮的關鍵問題是Id的set屬性是否應該公共出來。根據前面的介紹,實體標識應該是不可變的,如果把Id的set屬性設為公開,那麼任何人都可以隨時很方便的修改它,從而破壞了封裝性。

  那麼,把Id的set屬性設成私有,外界確實無法修改它,設置Id的唯一方法是在創建這個實體時,從構造函數傳進來。但這會導致哪些問題?先看看ORM,它需要將資料庫中的Id列映射到實體的Id屬性上,如果set被設為私有,還能映射成功嗎。通過測試,一般的ORM都具備映射私有屬性的能力,比如EF,所以這不是問題。再來看看表現層,比如Mvc,Mvc提供了一個模型綁定功能,可以把表現層的數據映射到實體的屬性上,如果屬性是私有的會如何?測試以後,發現只有包含public 的set屬性才可以映射成功,甚至欄位都不行。再測試Wpf的雙向綁定,也基本如此。所以把Id的set屬性設為私有,將導致實體在表現層無法直接使用,需要通過Dto或ViewModel進行中轉。

  所以你需要在封裝性和易用性上作出權衡,如果你希望更高的健壯性,那就把Id的set屬性隱藏起來,否則直接把Id暴露出來,通過約定告訴大家不要在創建了實體之後修改Id的值。由於準備演示Dto的用法,所以會把Id setter隱藏起來,並通過Dto來轉換。如果你需要更方便,請刪除Id setter上的private。

  現在Id類型為int,如果要使用Guid類型的實體,我們需要創建另一個實體基類。

namespace Util.Domains {
    /// <summary>
    /// 領域實體
    /// </summary>
    public abstract class EntityBase{
        /// <summary>
        /// 初始化領域實體
        /// </summary>
        /// <param name="id">標識</param>
        protected EntityBase( Guid id ) {
            Id = id;
        } 

        /// <summary>
        /// 標識
        /// </summary>
        public Guid Id { get; private set; }
    }
}

   它們的唯一變化是Id數據類型不同,我們可以把Id類型設為object,從而支持所有類型。

namespace Util.Domains {
    /// <summary>
    /// 領域實體
    /// </summary>
    public abstract class EntityBase{
        /// <summary>
        /// 初始化領域實體
        /// </summary>
        /// <param name="id">標識</param>
        protected EntityBase( object id ) {
            Id = id;
        } 

        /// <summary>
        /// 標識
        /// </summary>
        public object Id { get; private set; }
    }
}

   但弱類型的object將導致裝箱和拆箱,另外也不太易用,這時候是泛型準備登場的時候了。

namespace Util.Domains {
    /// <summary>
    /// 領域實體
    /// </summary>
    /// <typeparam name="TKey">標識類型</typeparam>
    public abstract class EntityBase<TKey> {
        /// <summary>
        /// 初始化領域實體
        /// </summary>
        /// <param name="id">標識</param>
        protected EntityBase( TKey id ) {
            Id = id;
        } 

        /// <summary>
        /// 標識
        /// </summary>
        [Required]
        public TKey Id { get; private set; }
    }
}

   將標識類型通過泛型參數TKey傳進來,由於標識類型可以任意,所以不需要進行泛型約束。另外在Id上方加了一個Required特性,當Id為字元串或其它引用類型的時候,就能派上用場了。

  下麵要解決的問題是實體對象相等性比較,需要重寫Equals,GetHashCode方法,另外需要重寫==和!=兩個操作符重載。

/// <summary>
/// 相等運算
/// </summary>
public override bool Equals( object entity ) 
{
    if ( entity == null )
       return false;
    if ( !( entity is EntityBase<TKey> ) )
       return false;
    return this == (EntityBase<TKey>)entity;
}

/// <summary>
/// 獲取哈希
/// </summary>
public override int GetHashCode() 
{
     return Id.GetHashCode();
}

/// <summary>
/// 相等比較
/// </summary>
/// <param name="entity1">領域實體1</param>
/// <param name="entity2">領域實體2</param>
public static bool operator ==( EntityBase<TKey> entity1, EntityBase<TKey> entity2 ) 
{
    if ( (object)entity1 == null && (object)entity2 == null )
        return true;
    if ( (object)entity1 == null || (object)entity2 == null )
        return false;
    if ( entity1.Id == null )
        return false;
    if ( entity1.Id.Equals( default( TKey ) ) )
        return false;
    return entity1.Id.Equals( entity2.Id );
}

/// <summary>
/// 不相等比較
/// </summary>
/// <param name="entity1">領域實體1</param>
/// <param name="entity2">領域實體2</param>
public static bool operator !=( EntityBase<TKey> entity1, EntityBase<TKey> entity2 ) 
{
    return !( entity1 == entity2 );
}

  在操作符==的代碼中,有一句需要註意,entity1.Id.Equals( default( TKey ) ),比如,一個實體的標識為int類型,這個實體在剛創建的時候,Id預設為0,另外創建一個該類的實例,Id也為0,那麼這兩個實體是相等還是不等?從邏輯上它們是不相等的,屬於不同的實體, 只是標識目前還沒有創建,可能需要等到保存到資料庫中才能產生。這有什麼影響呢?當進行某些集合操作時,如果你發現操作N個實體,但只有一個實體操作成功,那很有可能是因為這些實體的標識是預設值,而你的相等比較沒有識別出來,這一句代碼能夠解決這個問題。

  考慮領域實體基類還能幫我們乾點什麼,其實還很多,比如狀態輸出、初始化、驗證、日誌等。下麵先來介紹一下狀態輸出。

  當我在操作每個實體的時候,我經常需要在日誌中記錄完整的實體狀態,即實體所有屬性名值對的列表。這樣方便我在查找問題的時候,可以瞭解某個實體當時是個什麼情況。

  要輸出實體的狀態,最方便的方法是重寫ToString,然後把實體狀態列表返回回來。這樣ToString方法將變得有意義,因為它輸出一個實體的類名基本沒什麼用。

  要輸出實體的全部屬性值,一個辦法是通過反射在基類中進行,但這可能會造成一點性能下降,由於通過代碼生成器可以輕鬆生成這個操作,所以我沒有採用反射的方法。

/// <summary>
/// 描述
/// </summary>
private StringBuilder _description;

/// <summary>
/// 輸出領域對象的狀態
/// </summary>
public override string ToString() {
    _description = new StringBuilder();
    AddDescriptions();
    return _description.ToString().TrimEnd().TrimEnd( ',' );
} 

/// <summary>
/// 添加描述
/// </summary>
protected virtual void AddDescriptions() {
}

/// <summary>
/// 添加描述
/// </summary>
protected void AddDescription( string description ) {
    if ( string.IsNullOrWhiteSpace( description ) )
        return;
    _description.Append( description );
} 

/// <summary>
/// 添加描述
/// </summary>
protected void AddDescription<T>( string name, T value ) {
    if ( string.IsNullOrWhiteSpace( value.ToStr() ) )
        return;
    _description.AppendFormat( "{0}:{1},", name, value );
}

  在子類中需要重寫AddDescriptions方法,併在該方法中調用AddDescription這個輔助方法來添加屬性名值對的描述。

  由於驗證和日誌等內容需要一些公共操作類提供幫助,所以放到後面幾篇進行介紹。

  為了使泛型的EntityBase<TKey>用起來更簡單一點,我創建了一個EntityBase,它從泛型EntityBase<Guid>派生,這是因為我現在主要使用Guid作為標識類型。

namespace Util.Domains 
{
    /// <summary>
    /// 領域實體基類
    /// </summary>
    public abstract class EntityBase : EntityBase<Guid> 
    {
        /// <summary>
        /// 初始化領域實體
        /// </summary>
        /// <param name="id">標識</param>
        protected EntityBase( Guid id )
            : base( id ) {
        }
    }
}

   完整單元測試代碼如下:

using System;

namespace Util.Domains.Tests.Samples {
    /// <summary>
    /// 測試實體
    /// </summary>
    public class Test : EntityBase {
        /// <summary>
        /// 初始化
        /// </summary>
        public Test()
            : this( Guid.NewGuid() ) {
        }

        /// <summary>
        /// 初始化員工
        /// </summary>
        /// <param name="id">員工編號</param>
        public Test( Guid id )
            : base( id ) {
        }

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 添加描述
        /// </summary>
        protected override void AddDescriptions() {
            AddDescription( "Id:"+ Id + "," );
            AddDescription( "姓名", Name );
        }
    }
}

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Util.Domains.Tests.Samples;

namespace Util.Domains.Tests {
    /// <summary>
    /// 實體基類測試
    /// </summary>
    [TestClass]
    public class EntityBaseTest {
        /// <summary>
        /// 測試實體1
        /// </summary>
        private Test _test1;
        /// <summary>
        /// 測試實體2
        /// </summary>
        private Test _test2;

        /// <summary>
        /// 測試初始化
        /// </summary>
        [TestInitialize]
        public void TestInit() {
            _test1 = new Test();
            _test2 = new Test();
        }

        /// <summary>
        /// 通過構造方法設置標識
        /// </summary>
        [TestMethod]
        public void TestId() {
            Guid id = Guid.NewGuid();
            _test1 = new Test( id );
            Assert.AreEqual( id, _test1.Id );
        }

        /// <summary>
        /// 新創建的實體不相等
        /// </summary>
        [TestMethod]
        public void TestNewEntityIsNotEquals() {
            Assert.IsFalse( _test1.Equals( _test2 ) );
            Assert.IsFalse( _test1.Equals( null ) );

            Assert.IsFalse( _test1 == _test2 );
            Assert.IsFalse( _test1 == null );
            Assert.IsFalse( null == _test2 );

            Assert.IsTrue( _test1 != _test2 );
            Assert.IsTrue( _test1 != null );
            Assert.IsTrue( null != _test2 );
        }

        /// <summary>
        /// 當兩個實體的標識相同,則實體相同
        /// </summary>
        [TestMethod]
        public void TestEntityEquals_IdEquals() {
            Guid id = Guid.NewGuid();
            _test1 = new Test( id );
            _test2 = new Test( id );
            Assert.IsTrue( _test1.Equals( _test2 ) );
            Assert.IsTrue( _test1 == _test2 );
            Assert.IsFalse( _test1 != _test2 );
        }

        /// <summary>
        /// 測試狀態輸出
        /// </summary>
        [TestMethod]
        public void TestToString() {
            _test1 = new Test { Name = "a" };
            Assert.AreEqual( string.Format( "Id:{0},姓名:a", _test1.Id ), _test1.ToString() );
        }
    }
}

單元測試代碼

   完整EntityBase代碼如下:

using System.ComponentModel.DataAnnotations;
using System.Text;

namespace Util.Domains {
    /// <summary>
    /// 領域實體
    /// </summary>
    /// <typeparam name="TKey">標識類型</typeparam>
    public abstract class EntityBase<TKey> {

        #region 構造方法

        /// <summary>
        /// 初始化領域實體
        /// </summary>
        /// <param name="id">標識</param>
        protected EntityBase( TKey id ) {
            Id = id;
        }

        #endregion

        #region 欄位

        /// <summary>
        /// 描述
        /// </summary>
        private StringBuilder _description;

        #endregion

        #region Id(標識)

        /// <summary>
        /// 標識
        /// </summary>
        [Required]
        public TKey Id { get; private set; }

        #endregion

        #region Equals(相等運算)

        /// <summary>
        /// 相等運算
        /// </summary>
        public override bool Equals( object entity ) {
            if ( entity == null )
                return false;
            if ( !( entity is EntityBase<TKey> ) )
                return false;
            return this == (EntityBase<TKey>)entity;
        }

        #endregion

        #region GetHashCode(獲取哈希)

        /// <summary>
        /// 獲取哈希
        /// </summary>
        public override int GetHashCode() {
            return Id.GetHashCode();
        }

        #endregion

        #region ==(相等比較)

        /// <summary>
        /// 相等比較
        /// </summary>
        /// <param name="entity1">領域實體1</param>
        /// <param name="entity2">領域實體2</param>
        public static bool operator ==( EntityBase<TKey> entity1, EntityBase<TKey> entity2 ) {
            if ( (object)entity1 == null && (object)entity2 == null )
                return true;
            if ( (object)entity1 == null || (object)entity2 == null )
                return false;
            if ( entity1.Id == null )
                return false;
            if ( entity1.Id.Equals( default( TKey ) ) )
                return false;
            return entity1.Id.Equals( entity2.Id );
        }

        #endregion

        #region !=(不相等比較)

        /// <summary>
        /// 不相等比較
        /// </summary>
        /// <param name="entity1">領域實體1</param>
        /// <param name="entity2">領域實體2</param>
        public static bool operator !=( EntityBase<TKey> entity1, EntityBase<TKey> entity2 ) {
            return !( entity1 == entity2 );
        }

        #endregion

        #region ToString(輸出領域對象的狀態)

        /// <summary>
        /// 輸出領域對象的狀態
        /// </summary>
        public override string ToString() {
            _description = new StringBuilder();
            AddDescriptions();
            return _description.ToString().TrimEnd().TrimEnd( ',' );
        }

        /// <summary>
        /// 添加描述
        /// </summary>
        protected virtual void AddDescriptions() {
        }

        /// <summary>
        /// 添加描述
        /// </summary>
        protected void AddDescription( string description ) {
            if ( string.IsNullOrWhiteSpace( description ) )
                return;
            _description.Append( description );
        }

        /// <summary>
        /// 添加描述
        /// </summary>
        protected void AddDescription<T>( string name, T value ) {
            if ( string.IsNullOrWhiteSpace( value.ToStr() ) )
                return;
            _description.AppendFormat( "{0}:{1},", name, value );
        }

        #endregion
    }
}

using System;

namespace Util.Domains {
    /// <summary>
    /// 領域實體基類
    /// </summary>
    public abstract class EntityBase : EntityBase<Guid> {
        /// <summary>
        /// 初始化領域實體
        /// </summary>
        /// <param name="id">標識</param>
        protected EntityBase( Guid id )
            : base( id ) {
        }
    }
}

EntityBase

 

 轉載鏈接:http://www.cnblogs.com/xiadao521/p/4104190.html


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

-Advertisement-
Play Games
更多相關文章
  • Paradise_追逐者的es6(二) 1.es6三種聲明方式 2.變數的解構賦值 3.擴展運算符和rest運算符 ...
  • 第一階段: HTML+CSS: HTML進階、CSS進階、div+css佈局、HTML+css整站開發、 JavaScript基礎: Js基礎教程、js內置對象常用方法、常見DOM樹操作大全、ECMAscript、DOM、BOM、定時器和焦點圖。 JS基本特效: 常見特效、例如:tab、導航、整頁滾 ...
  • 前言 最近在通過教學視頻學習angularjs,其中有gulp的教學部分,對其的介紹為可以對文件進行合併,壓縮,格式化,監聽,測試,檢查等操作時,看到前三種功能我的心理思想是,網上有很多線上壓縮,線上解壓,css格式化,js格式化類似的工具,為什麼還需要學習一項新技術呢。當學完了之後,被自己見識短淺 ...
  • 定寬高。 ...
  • 什麼是JavaScript? 我們可以從幾個方面去說JavaScript是什麼: 基於對象 javaScript中 內置了許多對象 供我們使用【String、Date、Array】等等 javaScript也 允許我們自己自定義對象 事件驅動 當用戶觸發執行某些動作的時候【滑鼠單機、滑鼠移動】, j ...
  • 一、HTML HTML中需要給div一個id 二、CSS 三、JS 1、面向過程 2、面向對象 1)封裝方法 2)調用方法 ...
  • Object新方法 Object.is()方法 在js中比較兩個值時,你可能會用相等運算符==或者嚴格相等運算符 ===。為了避免在比較時發生強制類型轉換,許多開發者更傾向於使用後者。 Object.is()接受兩個參數,並且會在二者的值相等時返回true,此時要求二者的數據類型相同並且值也相等。 ...
  • 技術交流的時候遇到了這樣的一個問題,被問及開發中用到的是不是Restful API,我說的是,我們現在用到的不屬於完全是Restful API。因為我瞭解到的Restful API,是 通過具體的URI定位符,找到對應的資源,然後以固定的格式返回數據,這樣的才是Restful API。然而在我模糊的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...