[連載]《C#通訊(串口和網路)框架的設計與實現》- 14.序列號的設計,不重覆的實現一機一碼

来源:http://www.cnblogs.com/lsjwq/archive/2016/01/06/5105183.html
-Advertisement-
Play Games

目 錄第十四章 序列號的設計... 214.1 設計原則... 214.2 設計思想... 314.3 代碼實現... 414.4 代碼混淆... 1814.5 代碼破解... 1814.6 小結... 18第十四章 序列號的設計 序列號作為軟體使用授權方式之一,被廣泛使用在應用軟體方面。主要考慮到...


目       錄

第十四章     序列號的設計... 2

14.1        設計原則... 2

14.2        設計思想... 3

14.3        代碼實現... 4

14.4        代碼混淆... 18

14.5        代碼破解... 18

14.6        小結... 18

 

第十四章      序列號的設計

    序列號作為軟體使用授權方式之一,被廣泛使用在應用軟體方面。主要考慮到這幾方面:1.對知識產權的保護,畢竟付出來腦力勞動和體力勞動。2.商業競爭中增加防守的能力,防止被競爭對手盜取。3.增強合同的執行效力,防止另一方由於各種原因破壞合作機制。

    基於上述方面,從保護、防守思維模式角度考慮,增加序列號功能是有必要的。每個作者或公司設計序列號的方式不一樣,就是因為不一樣,所以才能達到我們增加該功能的效果。

14.1     設計原則

  1. 序列號長度儘可能短

    主要是從成本角度考慮的。例如用戶現場需要一個正版軟體的序列號,你把序列號信息通過什麼方式傳遞給用戶呢?假設我們用對稱或非對稱方式生成一個很長的序列號,如果口述告訴對方的話,那麼對方肯定要用紙和筆進行記錄,最後輸入到軟體後還不一定正確;如果把序列號以文件的方式通過網路傳遞給對方,那麼需要占用網路資源,另外對方的電腦不一定有網路環境。不管如何,很長的序列號在生成和傳遞的過程中可能涉及到的成本包括:材料成本、流量成本、人力成本和時間成本等。

     如果一個字元可以表達序列號所需要的完整信息,那麼是最理想的。但是,這是理想狀態,是不可能實現的,至少以我現在的能力是無法完成的。所以,要以最佳的長度表達出序列號的全部信息。

  1. 避免出現容易混淆的字元生成一個序列號發給了用戶,這個序列號包括:數字0和字母O,數字1和字母l。難道讓用戶一遍一遍的試嘛,這樣的用戶體驗太差了,雖然嘴上不說出來,至少感覺不太舒服。

14.2     設計思想

    設計的思想要看序列號要實現什麼樣的功能和具備什麼屬性。從功能角度考慮,包括:1.一個電腦一個序列號;2.儘管輸入的條件都一樣,每次生成的序列號都不一樣;3.對使用的時限進行驗證;4.序列號有註冊時限,超過規定的使用時間,序列號作廢,避免短時間多次註冊。從屬性角度考慮,包括:同樣的電腦、同樣的輸入條件生成的序列號都不一樣。

   我們把上述因素考慮進去,序列號長度為25位字元,序列號生成格式和元素信息如下圖:

 

 X01-X05:為電腦的特征碼,5位字元串,獲得機器某個部件的ID,這個部件可能為CPU、網卡、硬碟等信息,把ID進行MD5加密後取前5個字元作為特征碼,來實現一機一碼。這種方式,特征碼有可能有相同的情況,但是機率很小。

 X06-X13:為生成序列號的日期,8位字元串,格式為:yyyyMMdd。與後邊的使用時間限制配合使用,來驗證軟體的使用期限。

 X14-X15:為註冊時間限制,2位數字字元,從生成序列號日期算起,超過此註冊時間限制,序列號將無法正常進行註冊操作。

 X16-X20:為使用時間限制,5位數字字元,與生成序列號日期配合使用來驗證軟體使用期限。

 X21:為序列號的偏移量,1位字元,不管在什麼場景下,每次生成序列號的偏移量都不一樣。

X22-X25:為保留數據位,暫時不使用。自定義一個序列號字典信息,例如:_Dictionary ="JCB8EF2GH7K6MVP9QR3TXWY4",把容易混淆的字元去掉,這個可以自定義。序列號的每個部分都是通過隨機生成的偏移量(X21),對字典進行位移,根據輸入的數字信息對應字典的下標提取相應的字元作為序列號的一個字元。

   生成序列號的大概過程:

  1. 在字典信息的長度範圍內隨機生成一個偏移量數字。
  2. 根據偏移量數字對字典進行左或右的迴圈移動。
  3. 根據輸入的數字信息,例如:2015中的2,作為下標,從字典信息中提取出相應的字元。

反向解析大概過程類似,只需要根據X21字元,與字典的字元進行匹配,對應的下標作為偏移量,就可以反向解析出各項信息。

14.3     代碼實現

1.MD5操作類:

public class Safety
{
       public static string MD5(string str)
       {
              string strResult = "";
              MD5 md5 = System.Security.Cryptography.MD5.Create();
              byte[] bData = md5.ComputeHash(Encoding.Unicode.GetBytes(str));
              for (int i = 0; i < bData.Length; i++)
              {
                     strResult = strResult + bData[i].ToString("X");
              }
              return strResult;
       }
}

2.註冊信息類:

public class RegInfo
{
       public RegInfo()
       {
              KeySn = "";
              Date=DateTime.MinValue;
              RegLimitDays = 0;
              UseLimitDays = 0;
              Offset = 0;
       }
       public string KeySn { get; set; }
       public DateTime Date { get; set; }
       public int RegLimitDays { get; set; }
       public int UseLimitDays { get; set; }
       public int Offset { get; set; }
}

3.偏移操作類型:

internal enum OffsetType
{
       Left,
       Right
}

4.        序列號管理類

public class LicenseManage
    {
        /// <summary>
        /// 序列號字典,把數字和字母容易混淆的字元去掉。所產生的25位序列號從這個字典中產生。
        /// </summary>
        private static string _Dictionary = "JCB8EF2GH7K6MVP9QR3TXWY4";

        /// <summary>
        /// 可以自定義字典字元串
        /// </summary>
        public static string Dictionary
        {
            get { return _Dictionary; }
            set
            {
                if (value.Length < 9)
                {
                    throw new ArgumentOutOfRangeException("設置的字典長度不能小於9個字元");
                }

                _Dictionary = value;
            }
        }

        /// <summary>
        /// 生成序列號
        /// </summary>
        /// <param name="key">關鍵字,一般為CPU號、硬碟號、網卡號,用於與序列號綁定,實現一機一碼</param>
        /// <param name="now">現在的時間</param>
        /// <param name="regLimitDays">註冊天數限制,超過此天數,再進行註冊,序列號就失效了,不能再使用了</param>
        /// <param name="useLimitDays">使用天數限制,超過此天數,可以設置軟體停止運行等操作</param>
        /// <returns>返回序列號,例如:xxxxx-xxxxx-xxxxx-xxxxx-xxxxx</returns>
        public static string BuildSn(string key, DateTime now, int regLimitDays, int useLimitDays)
        {
            if (regLimitDays < 0 || regLimitDays > 9)
            {
                throw new ArgumentOutOfRangeException("註冊天數限制範圍為0-9");
            }

            if (useLimitDays < 0 || useLimitDays > 99999)
            {
                throw new ArgumentOutOfRangeException("使用天數限制範圍為0-99999");
            }

            /*
             *關鍵字用MD5加密後,取後5個字元作為序列號第1組字元
             */
            string md5 = Safety.MD5(key);
            string x1 = md5.Substring(md5.Length - 5);

            /*
             * 生成隨機偏移量
             */
            Random rand = new Random();
            int offset = rand.Next(1, Dictionary.Length - 1);

            /*
             * 第5組的第1個字元保存偏移量字元,其餘4個字元隨機生成,作為保留位
             */
            string x5 = Dictionary[offset].ToString();
            for (int i = 0; i < 4; i++)
            {
                x5 += Dictionary[rand.Next(0, Dictionary.Length - 1)].ToString();
            }

            /*
             * 以註冊時間(yyyyMMdd)和註冊時間限制生成第2組和第3組序列號,一共10位字元串
             */
            string dateSn = GetDateSn(now, offset);
            string regLimitSn = GetRegLimitSn(regLimitDays, offset);
            string x2 = dateSn.Substring(0, 5);
            string x3 = dateSn.Substring(dateSn.Length - 3) + regLimitSn;

            /*
             *以使用時間限制生成第4組序列號,一共5位字元串
             */
            string x4 = GetUseLimitSn(useLimitDays, offset);

            return String.Format("{0}-{1}-{2}-{3}-{4}", x1, x2, x3, x4, x5);
        }

        /// <summary>
        /// 註冊序列號
        /// </summary>
        /// <param name="key">關鍵字,一般為CPU號、硬碟號、網卡號,用於與序列號綁定,實現一機一碼</param>
        /// <param name="sn">序列號</param>
        /// <param name="desc">描述信息</param>
        /// <returns>註冊狀態,成功:0</returns>
        internal static int RegSn(string key, string sn, ref string desc)
        {
            if (String.IsNullOrEmpty(key) || String.IsNullOrEmpty(sn))
            {
                throw new ArgumentNullException("參數為空");
            }

            LicenseInfo regInfo = GetRegInfo(sn);
            string md5 = Safety.MD5(key);
            if (String.CompareOrdinal(md5.Substring(md5.Length - 5), regInfo.KeySn) != 0)
            {
                desc = "關鍵字與序列號不匹配";
                return -1;//關鍵字與序列號不匹配
            }

            if (regInfo.Date == DateTime.MaxValue || regInfo.Date == DateTime.MinValue || regInfo.Date > DateTime.Now.Date)
            {
                desc = "序列號時間有錯誤";
                return -2;//序列號時間有錯誤
            }

            TimeSpan ts = DateTime.Now.Date - regInfo.Date;
            if (ts.TotalDays > 9 || ts.TotalDays < 0)
            {
                desc = "序列號失效";
                return -3;//序列號失效
            }

            if (regInfo.UseLimitDays <= 0)
            {
                desc = "使用期限受限";
                return -4;//使用期限受限
            }

            Application.UserAppDataRegistry.SetValue("SN", sn);

            desc = "註冊成功";
            return 0;
        }

        /// <summary>
        /// 檢測序列號,試用於時鐘定時調用
        /// </summary>
        /// <param name="key">關鍵字,一般為CPU號、硬碟號、網卡號,用於與序列號綁定,實現一機一碼</param>
        /// <param name="desc">描述信息</param>
        /// <returns>檢測狀態,成功:0</returns>
        internal static int CheckSn(string key, ref string desc)
        {
            if (String.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("參數為空");
            }

            object val = Application.UserAppDataRegistry.GetValue("SN");

            if (val == null)
            {
                desc = "未檢測到本機的序列號";
                return -1;
            }

            string sn = val.ToString();

            LicenseInfo regInfo = GetRegInfo(sn);
            string md5 = Safety.MD5(key);
            if (String.CompareOrdinal(md5.Substring(md5.Length - 5), regInfo.KeySn) != 0)
            {
                desc = "關鍵字與序列號不匹配";
                return -2;//關鍵字與序列號不匹配
            }

            if ((DateTime.Now.Date - regInfo.Date).TotalDays > regInfo.UseLimitDays)
            {
                desc = "序列使用到期";
                return -3;//關鍵字與序列號不匹配
            }

            desc = "序列號可用";
            return 0;
        }

        /// <summary>
        /// 獲得剩餘天數
        /// </summary>
        /// <param name="key">關鍵字,一般為CPU號、硬碟號、網卡號,用於與序列號綁定,實現一機一碼</param>
        /// <returns>剩餘天數</returns>
        internal static int GetRemainDays(string key)
        {
            if (String.IsNullOrEmpty(key))
            {
                throw new ArgumentNullException("參數為空");
            }

            object val = Application.UserAppDataRegistry.GetValue("SN");

            if (val == null)
            {
                return 0;
            }

            string sn = val.ToString();

            LicenseInfo regInfo = GetRegInfo(sn);
            string md5 = Safety.MD5(key);
            if (String.CompareOrdinal(md5.Substring(md5.Length - 5), regInfo.KeySn) != 0)
            {
                return 0;//關鍵字與序列號不匹配,不能使用。
            }

            //<=0的情況,證明不可以使用。
            return regInfo.UseLimitDays - (int)(DateTime.Now.Date - regInfo.Date).TotalDays;
        }

        /// <summary>
        /// 根據序列號,反推註冊信息
        /// </summary>
        /// <param name="sn">序列號</param>
        /// <returns>註冊信息</returns>
        private static LicenseInfo GetRegInfo(string sn)
        {
            LicenseInfo reg = new LicenseInfo();
            string[] splitSn = sn.Split('-');
            if (splitSn.Length != 5)
            {
                throw new FormatException("序列號格式錯誤,應該帶有'-'字元");
            }

            reg.KeySn = splitSn[0];
            reg.Offset = Dictionary.IndexOf(splitSn[4][0]);
            reg.Date = GetDate(splitSn[1] + splitSn[2].Substring(0, 3), reg.Offset);
            reg.RegLimitDays = GetRegLimitDays(splitSn[2].Substring(3, 2), reg.Offset);
            reg.UseLimitDays = GetUseLimitDays(splitSn[3], reg.Offset);
            return reg;
        }

        /// <summary>
        /// 以當前時間和偏移量生成當前時間對應的字元串
        /// </summary>
        /// <param name="now">當前時間</param>
        /// <param name="offset">偏移量</param>
        /// <returns>返回日期對應的字元串,8位字元串</returns>
        private static string GetDateSn(DateTime now, int offset)
        {
            string dateSn = "";
            string date = now.ToString("yyyyMMdd");
            string newDic = Dictionary;
            for (int i = 0; i < date.Length; i++)
            {
                newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
                int num = int.Parse(date[i].ToString());
                dateSn += newDic[num].ToString();
            }
            return dateSn;
        }

        /// <summary>
        /// 根據註冊時間序列號反推註冊時間
        /// </summary>
        /// <param name="dateSn">時間字元串</param>
        /// <param name="offset">偏移量</param>
        /// <returns>時間</returns>
        private static DateTime GetDate(string dateSn, int offset)
        {
            string dateStr = "";
            string newDic = Dictionary;
            for (int i = 0; i < dateSn.Length; i++)
            {
                newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
                int num = newDic.IndexOf(dateSn[i]);
                dateStr += num;
            }
            return new DateTime(int.Parse(dateStr.Substring(0, 4)), int.Parse(dateStr.Substring(4, 2)), int.Parse(dateStr.Substring(6, 2)));
        }

        /// <summary>
        /// 以註冊時間限制和偏移量生成對應的字元串
        /// </summary>
        /// <param name="regLimitDays"></param>
        /// <param name="offset"></param>
        /// <returns>返回對應的註冊時間限制的字元串,2位字元串</returns>
        private static string GetRegLimitSn(int regLimitDays, int offset)
        {
            string regLimitSn = "";
            string regLimitStr = regLimitDays.ToString("00");
            string newDic = Dictionary;
            for (int i = 0; i < regLimitStr.Length; i++)
            {
                newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
                int num = int.Parse(regLimitStr[i].ToString());
                regLimitSn += newDic[num].ToString();
            }
            return regLimitSn;
        }

        /// <summary>
        /// 根據註冊時間限制字元串反推註冊時間限制
        /// </summary>
        /// <param name="regLimitSn">註冊時間限制字元串</param>
        /// <param name="offset">偏移量</param>
        /// <returns>註冊時間限制</returns>
        private static int GetRegLimitDays(string regLimitSn, int offset)
        {
            string regLimitStr = "";
            string newDic = Dictionary;
            for (int i = 0; i < regLimitSn.Length; i++)
            {
                newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
                int num = newDic.IndexOf(regLimitSn[i]);
                regLimitStr += num;
            }
            return int.Parse(regLimitStr);
        }

        /// <summary>
        /// 以使用時間限制和偏移量生成對應的字元串
        /// </summary>
        /// <param name="useLimitDays">使用時間限制</param>
        /// <param name="offset">偏移量</param>
        /// <returns>使用時間限制對應字元串,5位字元串</returns>
        private static string GetUseLimitSn(int useLimitDays, int offset)
        {
            string useLimitSn = "";
            string useLimitStr = useLimitDays.ToString("00000");
            string newDic = Dictionary;
            for (int i = 0; i < useLimitStr.Length; i++)
            {
                newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
                int num = int.Parse(useLimitStr[i].ToString());
                useLimitSn += newDic[num].ToString();
            }
            return useLimitSn;
        }

        /// <summary>
        /// 根據使用時間限制字元串反推使用時間限制
        /// </summary>
        /// <param name="regLimitSn">使用時間限制字元串</param>
        /// <param name="offset">偏移量</param>
        /// <returns>使用時間限制</returns>
        private static int GetUseLimitDays(string useLimitSn, int offset)
        {
            string useLimitStr = "";
            string newDic = Dictionary;
            for (int i = 0; i < useLimitSn.Length; i++)
            {
                newDic = GetNewDictionaryString(newDic, offset, LicenseOffset.Left);
                int num = newDic.IndexOf(useLimitSn[i]);
                useLimitStr += num;
            }
            return int.Parse(useLimitStr);
        }

        /// <summary>
        /// 根據字典、偏移量和偏移類型生成新的字典
        /// </summary>
        /// <param name="dic"></param>
        /// <param name="offset"></param>
        /// <param name="offsetType"></param>
        /// <returns></returns>
        private static string GetNewDictionaryString(string dic, int offset, LicenseOffset offsetType)
        {
            StringBuilder sb = new StringBuilder(dic);
            if (offsetType == LicenseOffset.Left)
            {
                for (int i = 0; i < offset; i++)
                {
                    string head = sb[0].ToString();
                    sb.Remove(0, 1);
                    sb.Append(head);
                }
            }
            else if (offsetType == LicenseOffset.Right)
            {
                for (int i = 0; i < offset; i++)
                {
                    string end = sb[dic.Length - 1].ToString();
                    sb.Remove(dic.Length - 1, 1);
                    sb.Insert(0, end);
                }
            }
            return sb.ToString();
        }
    }

 

14.4     代碼混淆

   從安全形度來講,.NET程式如果不加混淆的話,很容易被反編譯出源代碼的。從專業角度來講,即使增加了序列號功能,也無濟於事,專業的人員分分鐘可以破解掉,儘管這樣乾的人很少,但是存在這種可能性。如果一個軟體人員想瞭解一個很好的軟體,第一反映可能就是反編譯。

   對於公司或商業使用的軟體來講,增加混淆還是有必要的,儘管現在開源很流行。

14.5     代碼破解

    不管.NET程式如何進行混淆,理論上都是可以破解的,理論的東西就不贅述了。通常接觸過的破解方式有兩種:註冊機方式和暴力方式。

    註冊機的方式,需要通過軟體的驗證序列號的過程和機制反向推算出序列號的生成演算法,根據反推的演算法開發一個小軟體,用於生成脫離作者授權生成序列號。這種方式不會破壞程式本身的代碼,相對溫和。暴力的方式,就是找到序列號驗證部分的代碼,通過刪除或繞過驗證代碼等方式不讓代碼有效執行。這種方式會對程式本身的代碼進行改動,所以也存在一些風險。

14.6     小結

     實現序列號有多種方式,上述方式不一定最好,但是希望對開發者有一定幫助。

 

最終實現效果圖如下:

作者:唯笑志在

Email:[email protected]

QQ:504547114

.NET開發技術聯盟:54256083

文檔下載:http://pan.baidu.com/s/1pJ7lZWf

官方網址:http://www.bmpj.net


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

-Advertisement-
Play Games
更多相關文章
  • MyEclipse 快捷鍵1(CTRL)-------------------------------------Ctrl+1 快速修複Ctrl+D: 刪除當前行Ctrl+Q定位到最後編輯的地方Ctrl+L定位在某行Ctrl+O快速顯示 OutLineCtrl+T快速顯示當前類的繼承結構Ctrl+W...
  • 在上篇博客《基於C#的MongoDB資料庫開發應用(1)--MongoDB資料庫的基礎知識和使用》裡面,我總結了MongoDB資料庫的一些基礎信息,併在最後面部分簡單介紹了資料庫C#驅動的開發 ,本文繼續這個主體,重點介紹MongoDB資料庫C#方面的使用和封裝處理過程,利用泛型和基類對象針對數據訪...
  • 用過Reshaprer一段時間發現這個Visual Studio插件確實是個好東東,特別是神級快捷鍵Alt+Enter更是好用至極,可以解決大部分代碼問題,不過會發現裝上Reshaprer後VS自帶的快捷鍵就被打亂了,特別是喜歡的Ctrl+E,D設置文檔格式等,為了找回VS預設的快捷鍵盤於是重置了下...
  • 本文是深入淺出OOP第二篇,主要說說繼承的話題。深入理解OOP(一):多態和繼承(初期綁定和編譯時多態)深入理解OOP(二):多態和繼承(繼承)深入理解OOP(三):多態和繼承(動態綁定和運行時多態)深入理解OOP(四):多態和繼承(C#中的抽象類)深入理解OOP(五):C#中的訪問修飾符(Publ...
  • 由於總忘記,先寫一下。Console.WriteLine("輸出轉換空結果 ={0}", Convert.ToInt32("")); //Convert.ToInt32是不允許轉換“”空值的,會報錯Console.WriteLine("輸出轉換null結果={0}", Convert.ToInt32...
  • 俗話說:"溫故而知新,可以為師矣".那麼就讓我們回顧一下之前的知識點吧! 01.常用的聚合函數有哪些?Avg():求平均值Sum():求和Max();求最大值Min():求最小值Count():求總的記錄數,count(1)和count(*)等價,一般認為count(1)效率高。0...
  • JavaScript中一個重要的概念就是閉包,閉包在JavaScript中有大量的應用,但是你知道麽?C#也可以創建Closure。
  • 有如下列表數據:0-PRODUCT1-10120001782-10126109763-10160000223-10160101784-10610000155-10620000025-10620000026-10620000027-10620000028-10135022309-10610101801...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...