C# 軟體Licence應用實例

来源:https://www.cnblogs.com/hsiang/archive/2023/09/03/17675115.html
-Advertisement-
Play Games

我們在使用一些需要購買版權的軟體產品時,或者我們做的商業軟體需要進行售賣,為了收取費用,一般需要一個軟體使用許可證,然後輸入這個許可到軟體里就能夠使用軟體。簡單的是一串序列碼或者一個許可證文件,複雜的是一個定製化插件包。於是有的小伙伴就開始好奇這個許可是怎麼實現的,特別是在離線情況下它是怎麼給軟體授... ...


我們在使用一些需要購買版權的軟體產品時,或者我們做的商業軟體需要進行售賣,為了收取費用,一般需要一個軟體使用許可證,然後輸入這個許可到軟體里就能夠使用軟體。簡單的是一串序列碼或者一個許可證文件,複雜的是一個定製化插件包。於是有的小伙伴就開始好奇這個許可是怎麼實現的,特別是在離線情況下它是怎麼給軟體授權,同時又能避免被破解的。

 

License應用場景

 

本文主要介紹的是許可證形式的授權。

 

1. 如何控制只在指定設備上使用

 

如果不控制指定設備,那麼下發了許可證,只要把軟體複製多份安裝則可到處使用,不利於版權維護,每個設備都有唯一標識:mac地址,ip地址,主板序列號等,在許可證中指定唯一標識則只能指定設備使用。

 

2. 如何控制軟體使用期限

 

為了版權可持續性收益,對軟體使用設置期限,到期續費等,則需要在許可證中配置使用起止日期。

 

Licence實現方案

 

一、流程設計

 

  • 形式:許可證以文件形式下發,放在客戶端電腦指定位置
  • 內容:以上控制內容以dom節點形式放在文件中
  • 流程:將控制項加密後寫入license文件節點,部署到客戶機器,客戶機使用時再讀取license文件內容與客戶機實際參數進行匹配校驗

 

二、文件防破解

 

  • 防止篡改:文件內容加密,使用AES加密,但是AES加密解密都是使用同一個key;使用非對稱公私鑰(本文使用的RSA)對內容加密解密,但是對內容長度有限制;綜合方案,將AES的key(內部定義)用RSA加密,公鑰放在加密工具中,內部持有,私鑰放在解密工具中,引入軟體產品解密使用。
  • 防止修改系統時間繞過許可證使用時間:許可證帶上發佈時間戳,並定時修改運行時間記錄到文件,如果系統時間小於這個時間戳,就算大於許可證限制的起始時間也無法使用
  • 提高破解難度:懂技術的可以將代碼反編譯過來修改代碼文件直接繞過校驗,所以需要進行代碼混淆,有測試過xjar的混淆效果比較好。

 

Licence驗證流程圖

 

關於Licence驗證軟體合法性流程圖,如下所示:

 

核心源碼

 

本實例主要講解Licence的實際驗證過程,分為三部分:

  1. 測試客戶端【LicenceTest】,主要用於模擬客戶端驗證Licence的過程。
  2. 生成工具【LicenceTool】,主要用於根據客戶生成的電腦文件,生成對應的Licence。
  3. LicenceCommon,Licence公共通用類,主要實現電腦信息獲取,非對稱加密,文件保存等功能。

 

1. LicenceCommon

 

1.1 電腦信息獲取

 

主要通過ManagementClass進行獲取客戶端電腦硬體相關配置信息,如下所示:

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;

namespace DemoLicence.Common
{
    public class ComputerHelper
    {
        public static Dictionary<string,string> GetComputerInfo()
        {
            var info = new Dictionary<string,string>();
            string cpu = GetCPUInfo();
            string baseBoard = GetBaseBoardInfo();
            string bios = GetBIOSInfo();
            string mac = GetMACInfo();
            info.Add("cpu", cpu);
            info.Add("baseBoard", baseBoard);
            info.Add("bios", bios);
            info.Add("mac", mac);
            return info;
        }
        private static string GetCPUInfo()
        {
            string info = string.Empty;
            info = GetHardWareInfo("Win32_Processor", "ProcessorId");
            return info;
        }
        private static string GetBIOSInfo()
        {
            string info = string.Empty;
            info = GetHardWareInfo("Win32_BIOS", "SerialNumber");
            return info;
        }
        private static string GetBaseBoardInfo()
        {
            string info = string.Empty;
            info = GetHardWareInfo("Win32_BaseBoard", "SerialNumber");
            return info;
        }
        private static string GetMACInfo()
        {
            string info = string.Empty;
            info = GetMacAddress();//GetHardWareInfo("Win32_NetworkAdapterConfiguration", "MACAddress");
            return info;
        }

        private static string GetMacAddress()
        {
            var mac = "";
            var mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
            var moc = mc.GetInstances();
            foreach (var o in moc)
            {
                var mo = (ManagementObject)o;
                if (!(bool)mo["IPEnabled"]) continue;
                mac = mo["MacAddress"].ToString();
                break;
            }
            return mac;
        }

        private static string GetHardWareInfo(string typePath, string key)
        {
            try
            {
                ManagementClass managementClass = new ManagementClass(typePath);
                ManagementObjectCollection mn = managementClass.GetInstances();
                PropertyDataCollection properties = managementClass.Properties;
                foreach (PropertyData property in properties)
                {
                    if (property.Name == key)
                    {
                        foreach (ManagementObject m in mn)
                        {
                            return m.Properties[property.Name].Value.ToString();
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                //這裡寫異常的處理
            }
            return string.Empty;
        }
    }
}

 

1.3 RSA非對稱加密

 

主要對客戶端提供的電腦信息及有效期等內容,進行非對稱加密,如下所示:

public class RSAHelper
{

	private static string keyContainerName = "star";

	private static string m_PriKey = string.Empty;

	private static string m_PubKey = string.Empty;


	public static string PriKey
	{
		get
		{
			return m_PriKey;
		}

		set
		{
			m_PriKey = value;
		}
	}

	public static string PubKey
	{
		get
		{
			return m_PubKey;
		}

		set
		{
			m_PubKey = value;
		}
	}

	public static string Encrypto(string source)
	{
		if (string.IsNullOrEmpty(m_PubKey) && string.IsNullOrEmpty(m_PriKey))
		{
			generateKey();
		}
		return getEncryptoInfoByRSA(source);
	}

	public static string Decrypto(string dest)
	{
		if (string.IsNullOrEmpty(m_PubKey) && string.IsNullOrEmpty(m_PriKey))
		{
			generateKey();
		}
		return getDecryptoInfoByRSA(dest);
	}

	public static void generateKey()
	{
		CspParameters m_CspParameters;
		m_CspParameters = new CspParameters();
		m_CspParameters.KeyContainerName = keyContainerName;
		RSACryptoServiceProvider asym = new RSACryptoServiceProvider(m_CspParameters);
		m_PriKey = asym.ToXmlString(true);
		m_PubKey = asym.ToXmlString(false);
		asym.PersistKeyInCsp = false;
		asym.Clear();
	}

	private static string getEncryptoInfoByRSA(string source)
	{
		byte[] plainByte = Encoding.ASCII.GetBytes(source);
		//初始化參數
		RSACryptoServiceProvider asym = new RSACryptoServiceProvider();
		asym.FromXmlString(m_PubKey);
		int keySize = asym.KeySize / 8;//非對稱加密,每次的長度不能太長,否則會報異常
		int bufferSize = keySize - 11;
		if (plainByte.Length > bufferSize)
		{
			throw new Exception("非對稱加密最多支持【" + bufferSize + "】位元組,實際長度【" + plainByte.Length + "】位元組。");
		}
		byte[] cryptoByte = asym.Encrypt(plainByte, false);
		return Convert.ToBase64String(cryptoByte);
	}

	private static string getDecryptoInfoByRSA(string dest)
	{
		byte[] btDest = Convert.FromBase64String(dest);
		//初始化參數
		RSACryptoServiceProvider asym = new RSACryptoServiceProvider();
		asym.FromXmlString(m_PriKey);
		int keySize = asym.KeySize / 8;//非對稱加密,每次的長度不能太長,否則會報異常
									   //int bufferSize = keySize - 11;
		if (btDest.Length > keySize)
		{
			throw new Exception("非對稱解密最多支持【" + keySize + "】位元組,實際長度【" + btDest.Length + "】位元組。");
		}
		byte[] cryptoByte = asym.Decrypt(btDest, false);
		return Encoding.ASCII.GetString(cryptoByte);
	}
}

 

1.3 生成文件

 

主要是加密後的信息,和解密秘鑰等內容,保存到文件中,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DemoLicence.Common
{
    public class RegistFileHelper
    {
        public static string ComputerInfofile = "ComputerInfo.key";
        public static string RegistInfofile = "Licence.key";
        public static void WriteRegistFile(string info,string keyFile)
        {
            string tmp = string.IsNullOrEmpty(keyFile)?RegistInfofile:keyFile;
            WriteFile(info, tmp);
        }
        public static void WriteComputerInfoFile(string info)
        {
            WriteFile(info, ComputerInfofile);
        }
        public static string ReadRegistFile(string keyFile)
        {
            string tmp = string.IsNullOrEmpty(keyFile) ? RegistInfofile : keyFile;
            return ReadFile(tmp);
        }
        public static string ReadComputerInfoFile(string file)
        {
            string tmp = string.IsNullOrEmpty(file) ? ComputerInfofile : file;
            return ReadFile(tmp);
        }

        private static void WriteFile(string info, string fileName)
        {
            try
            {
                using (StreamWriter sw = new StreamWriter(fileName, false))
                {
                    sw.Write(info);
                    sw.Close();
                }
            }
            catch (Exception ex)
            {
            }
        }
        private static string ReadFile(string fileName)
        {
            string info = string.Empty;
            try
            {
                using (StreamReader sr = new StreamReader(fileName))
                {
                    info = sr.ReadToEnd();
                    sr.Close();
                }
            }
            catch (Exception ex)
            {
            }
            return info;
        }
    }
}

以上這三部分,各個功能相互獨立,通過LicenceHelper相互調用,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace DemoLicence.Common
{
    public class LicenceHelper
    {
        /// <summary>
        /// 獲取電腦信息,並生成文件
        /// </summary>
        public static string GetComputerInfoAndGenerateFile()
        {
            string computerKeyFile = string.Empty;
            try
            {
                var info = GetComputerInfo();
                if (info != null && info.Count > 0)
                {
                    //獲取到電腦信息
                    var strInfo = new StringBuilder();
                    foreach (var computer in info)
                    {
                        strInfo.AppendLine($"{computer.Key}={computer.Value}");
                    }
                    RegistFileHelper.WriteComputerInfoFile(strInfo.ToString());
                    computerKeyFile = RegistFileHelper.ComputerInfofile;
                }
            }catch(Exception ex)
            {
                throw ex;
            }
            return computerKeyFile;
        }

        public static Dictionary<string,string> GetComputerInfo()
        {
            var info = ComputerHelper.GetComputerInfo();
            return info;
        }

        public static bool CheckLicenceKeyIsExists()
        {
            var keyFile = RegistFileHelper.RegistInfofile;
            if (File.Exists(keyFile))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public static string GetComputerInfo(string computerInfoFile)
        {
            return RegistFileHelper.ReadComputerInfoFile(computerInfoFile);
        }

        public static void GenerateLicenceKey(string info,string keyfile)
        {
            string encrypto = RSAHelper.Encrypto(info);
            string priKey = RSAHelper.PriKey;//公鑰加密,私鑰解密
            byte[] priKeyBytes = Encoding.ASCII.GetBytes(priKey);
            string priKeyBase64=Convert.ToBase64String(priKeyBytes);
            StringBuilder keyInfo= new StringBuilder();
            keyInfo.AppendLine($"prikey={priKeyBase64}");
            keyInfo.AppendLine($"encrypto={encrypto}");
            RegistFileHelper.WriteRegistFile(keyInfo.ToString(), keyfile);
        }

        public static string ReadLicenceKey(string keyfile)
        {
            var keyInfo = RegistFileHelper.ReadRegistFile(keyfile);
            if (keyInfo == null)
            {
                return string.Empty;
            }
            string[] keyInfoArr = keyInfo.Split("\r\n");
            var priKeyBase64 = keyInfoArr[0].Substring(keyInfoArr[0].IndexOf("=")+1);
            var encrypto = keyInfoArr[1].Substring(keyInfoArr[1].IndexOf("=")+1);
            var priKeyByte= Convert.FromBase64String(priKeyBase64);
            var priKey = Encoding.ASCII.GetString(priKeyByte);
            RSAHelper.PriKey= priKey;
            var info = RSAHelper.Decrypto(encrypto);
            return info;
        }

        public static string GetDefaultRegisterFileName()
        {
            return RegistFileHelper.RegistInfofile;
        }

        public static string GetDefaultComputerFileName()
        {
            return RegistFileHelper.ComputerInfofile;
        }
        
        public static string GetPublicKey()
        {
            if (string.IsNullOrEmpty(RSAHelper.PubKey))
            {
                RSAHelper.generateKey();
            }
            return RSAHelper.PubKey;
        }

        public static string GetPrivateKey()
        {
            if (string.IsNullOrEmpty(RSAHelper.PriKey))
            {
                RSAHelper.generateKey();
            }
            return RSAHelper.PriKey;
        }
    }
}

 

2. 客戶端LicenceTest

 

客戶端驗證Licence的有效性,當Licence有效時,正常使用軟體,當Licence無效時,則不能正常使用軟體。如下所示:

using DemoLicence.Common;

namespace LicenceTest
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            try
            {

                string info = string.Empty;
                string msg = string.Empty;
                //初始化載入
                if (LicenceHelper.CheckLicenceKeyIsExists())
                {
                    string keyFile = LicenceHelper.GetDefaultRegisterFileName();
                    info = LicenceHelper.ReadLicenceKey(keyFile);
                }
                else
                {
                    var dialogResult = MessageBox.Show("沒有找到預設首選文件,是否手動選擇授權文件?", "詢問", MessageBoxButtons.YesNo);
                    if (dialogResult == DialogResult.Yes)
                    {
                        OpenFileDialog openFileDialog = new OpenFileDialog();
                        openFileDialog.Title = "請選擇授權文件";
                        openFileDialog.FileName = LicenceHelper.GetDefaultRegisterFileName();
                        if (openFileDialog.ShowDialog() == DialogResult.OK)
                        {
                            var keyFile = openFileDialog.FileName;
                            info = LicenceHelper.ReadLicenceKey(keyFile);
                            //驗證成功後,將手動選擇的文件複製到程式根目錄,且修改為預設名稱
                            File.Copy(keyFile, LicenceHelper.GetDefaultRegisterFileName());

                        }
                        else
                        {
                            string computerFile = LicenceHelper.GetComputerInfoAndGenerateFile();
                            if (!string.IsNullOrEmpty(computerFile))
                            {
                                msg = $"您還沒有被授權,請將程式根目錄下的{computerFile}文件,發送到管理員,獲取Licence.";
                            }
                        }
                    }
                    else
                    {
                        string computerFile = LicenceHelper.GetComputerInfoAndGenerateFile();
                        if (!string.IsNullOrEmpty(computerFile))
                        {
                            msg = $"您還沒有被授權,請將程式根目錄下的{computerFile}文件,發送到管理員,獲取Licence.";
                        }
                    }
                }
                if (!string.IsNullOrEmpty(info) && string.IsNullOrEmpty(msg))
                {
                    string[] infos = info.Split("\r\n");
                    if (infos.Length > 0)
                    {
                        var dicInfo = new Dictionary<string, string>();
                        foreach (var info2 in infos)
                        {
                            if (string.IsNullOrEmpty(info2))
                            {
                                continue;
                            }
                            var info2Arr = info2.Split("=");
                            dicInfo.Add(info2Arr[0], info2Arr[1]);
                        }
                        if (dicInfo.Count > 0)
                        {
                            string localMacAddress = string.Empty;
                            var computerInfo = LicenceHelper.GetComputerInfo();
                            if (computerInfo != null)
                            {
                                localMacAddress = computerInfo["mac"];
                            }
                            //比較本地信息和Licence中的信息是否一致
                            if (localMacAddress == dicInfo["mac"])
                            {
                                var endTime = DateTime.Parse(dicInfo["endTime"]);
                                if (DateTime.Now < endTime)
                                {
                                    //在有效期內,可以使用
                                }
                                else
                                {
                                    msg = $"軟體授權使用時間範圍:[{endTime}之前],已過期";
                                }
                            }
                            else
                            {
                                msg = "軟體Licence不匹配";
                            }
                        }
                        else
                        {
                            msg = $"軟體Licence非法.";
                        }
                    }
                    else
                    {
                        msg = $"軟體Licence非法.";
                    }
                }
                if (!string.IsNullOrEmpty(msg))
                {
                    MessageBox.Show(msg);
                    foreach (var control in this.Controls)
                    {
                        (control as Control).Enabled = false;
                    }
                    return;
                }
            }
            catch (Exception ex)
            {
                string error = $"程式異常,請聯繫管理人員:{ex.Message}\r\n{ex.StackTrace}";
                MessageBox.Show(error);
                foreach (var control in this.Controls)
                {
                    (control as Control).Enabled = false;
                }
            }
        }
    }
}

 

3. Licence生成工具

 

LicenceTool主要根據客戶端提供的電腦信息,生成對應的Licence,然後再發送給客戶端,以此達到客戶端電腦的授權使用軟體的目的。如下所示:

using DemoLicence.Common;
using System.Text;

namespace LicenceTool
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }


        private void MainForm_Load(object sender, EventArgs e)
        {
            this.txtPublicKey.Text=LicenceHelper.GetPublicKey();
            this.txtPrivateKey.Text=LicenceHelper.GetPrivateKey();
        }



        private void btnBrowser_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "電腦信息文件|*.key";
            ofd.Multiselect = false;
            ofd.Title = "請選擇電腦信息文件";
            ofd.FileName=LicenceHelper.GetDefaultComputerFileName();
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                this.txtSourceFile.Text = ofd.FileName;
            }
        }

        private void btnGenerate_Click(object sender, EventArgs e)
        {

            try
            {
                if (string.IsNullOrEmpty(this.txtSourceFile.Text))
                {
                    MessageBox.Show("請先選擇電腦信息文件");
                    return;
                }
                if (File.Exists(this.txtSourceFile.Text))
                {
                    //讀取電腦文件
                    var info = LicenceHelper.GetComputerInfo(this.txtSourceFile.Text);
                    int days = GetLicenceDays();
                    var keyInfos = new StringBuilder(info);
                    var beginTime = DateTime.Now;
                    var endTime = DateTime.Now.AddDays(days);
                    //keyInfos.AppendLine($"beginTime={beginTime.ToString("yyyy-MM-dd HH:mm:ss")}");
                    keyInfos.AppendLine($"endTime={endTime.ToString("yyyy-MM-dd HH:mm:ss")}");
                    //
                    info = keyInfos.ToString();
                    SaveFileDialog saveFileDialog = new SaveFileDialog();
                    saveFileDialog.Title = "保存生成的Licence文件";
                    saveFileDialog.FileName = LicenceHelper.GetDefaultRegisterFileName();
                    if (saveFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        LicenceHelper.GenerateLicenceKey(info, saveFileDialog.FileName);
                        MessageBox.Show("生成成功");
                    }
                }
                else
                {
                    MessageBox.Show("電腦信息文件不存在!");
                    return;
                }
            }catch(Exception ex)
            {
                string error = $"生成出錯:{ex.Message}\r\n{ex.StackTrace}";
                MessageBox.Show(error);
            }
        }

        /// <summary>
        /// 獲取有效期天數
        /// </summary>
        /// <returns></returns>
        private int GetLicenceDays()
        {
            int days = 1;
            RadioButton[] rbtns = new RadioButton[] { this.rbtSeven, this.rbtnTen, this.rbtnFifteen, this.rbtnThirty, this.rbtnSixty, this.rbtnSixMonth, this.rbtnNinety, this.rbtnSixMonth, this.rbtnForver };
            foreach (RadioButton rb in rbtns)
            {
                if (rb.Checked)
                {
                    if (!int.TryParse(rb.Tag.ToString(), out days))
                    {
                        days = 0;
                    }
                    break;
                }
            }
            days = days == -1 ? 9999 : days;//永久要轉換一下
            return days;
        }
    }
}

 

測試驗證

 

啟動軟體時會進行校驗,在沒有Licence時,會有信息提示,且無法使用軟體,如下所示:

Lincence生成工具

根據客戶提供的電腦信息文件,生成對應的Licence,如下所示:

生成Licence放在客戶端預設目錄下,即可正常使用軟體,如下所示:

註意:非對稱加密每次生成的秘鑰都是不同的,所以需要將解密秘鑰一起保存到生成的Licence文件中,否則秘鑰不同,則無法解密。

生成的電腦信息文件ComputerInfo.key示例如下所示:

生成的Licence.key文件內容,如下所示:

 

源碼下載

 

源碼下載可以通過以下3種方式,

 

1. 公眾號關鍵詞回覆

 

關註個人公眾號,回覆關鍵字【Licence】獲取源碼,如下所示:

 

2. 通過gitee(碼雲)下載

 

本示例中相關源碼,已上傳至gitee(碼雲),鏈接如下:

 

3. 通過CSDN進行下載

 

通過CSDN上的資源進行付費下載,不貴不貴,也就一頓早餐的錢。

https://download.csdn.net/download/fengershishe/88294433?spm=1001.2014.3001.5501

以上就是軟體Licence應用實例的全部內容,希望可以拋磚引玉,一起學習,共同進步。學習編程,從關註【老碼識途】開始!!!


作者:小六公子
出處:http://www.cnblogs.com/hsiang/
本文版權歸作者和博客園共有,寫文不易,支持原創,歡迎轉載【點贊】,轉載請保留此段聲明,且在文章頁面明顯位置給出原文連接,謝謝。
關註個人公眾號,定時同步更新技術及職場文章


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

-Advertisement-
Play Games
更多相關文章
  • acwing學習筆記,記錄容易忘記的知識點和難題。數組實現單鏈表、雙鏈表、棧、單調棧、隊列、單調隊列、KMP、字典樹 Trie、並查集、數組實現堆、哈希表(拉鏈法、開放定址法、字元串首碼哈希法)、STL常用容器 ...
  • ## 1、使用互斥量 在C++中,我們通過構造`std::mutex`的實例來創建互斥量,調用成員函數`lock()`對其加鎖,調用`unlock()`解鎖。但通常更推薦的做法是使用標準庫提供的類模板`std::lock_guard`,它針對互斥量實現了RAII手法:在構造時給互斥量加鎖,析構時解鎖 ...
  • ## 預編譯頭文件 在 Visual Studio 中創建新項目時,會在項目中添加一個名為 pch.h 的“預編譯標頭文件”。 (在 Visual Studio 2017 及更高版本中,該文件名為 stdafx.h)此文件的目的是加快生成過程。 應在此處包含任何穩定的標頭文件,例如標準庫標頭(如 ) ...
  • 集合是一種無序、可變的數據結構,它也是一種變數類型,集合用於存儲唯一的元素。集合中的元素不能重覆,並且沒有固定的順序。在Python 提供了內置的 `set` 類型來表示集合,所以關鍵字`set`就是集合的意思。 你可以使用大括弧 `{}` 或者 `set()` 函數來創建一個集合。 ```pyth ...
  • `Matplotlib`的**坐標軸**是用於在繪圖中表示數據的位置的工具。 坐標軸是圖像中的水平和垂直線,它們通常表示為 x 軸和 y 軸。坐標軸的作用是幫助觀察者瞭解圖像中數據的位置和大小,通常標有數字或標簽,以指示特定的值在圖像中的位置。 # 1. 坐標軸範圍 `Matplotlib`繪製圖形 ...
  • 本文主要介紹 RocketMQ 的安裝部署,文中所使用到的軟體版本:RocketMQ 5.1.3、CentOS 7.9.2009。 1、RocketMQ 部署模型 1.1、部署模型說明 Apache RocketMQ 部署架構上主要分為四部分: A、生產者 Producer 發佈消息的角色。Prod ...
  • > 講解Go語言從編譯到執行全周期流程,每一部分都會包含豐富的技術細節和實際的代碼示例,幫助大家理解。 > 關註微信公眾號【TechLeadCloud】,分享互聯網架構、雲服務技術的全維度知識。作者擁有10+年互聯網服務架構、AI產品研發經驗、團隊管理經驗,同濟本復旦碩,復旦機器人智能實驗室成員,阿 ...
  • 最近在公司的項目中,編寫了幾個自定義的 Exception 類。提交 PR 的時候,sonarqube 提示這幾個自定義異常不符合 ISerializable patten. 花了點時間稍微研究了一下,把這個問題解了。今天在此記錄一下,可能大家都會幫助到大家。 ## 自定義異常 編寫一個自定義的異常 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...