自定義分散式RESTful API鑒權機制

来源:http://www.cnblogs.com/liemei/archive/2017/06/22/7063354.html
-Advertisement-
Play Games

微軟利用OAuth2為RESTful API提供了完整的鑒權機制,但是可能微軟保姆做的太完整了,在這個機制中指定了數據持久化的方法是用EF,而且對於用戶、許可權等已經進行了封裝,對於系統中已經有了自己的用戶表,和不是採用EF做持久化的系統來說限制太大,不自由,而且現實中很多情況下,授權伺服器和資源服務 ...


微軟利用OAuth2為RESTful API提供了完整的鑒權機制,但是可能微軟保姆做的太完整了,在這個機制中指定了數據持久化的方法是用EF,而且對於用戶、許可權等已經進行了封裝,對於系統中已經有了自己的用戶表,和不是採用EF做持久化的系統來說限制太大,不自由,而且現實中很多情況下,授權伺服器和資源伺服器並不是同一個伺服器,甚至資料庫用的都不是同一個資料庫,這種情況下直接利用微軟的授權機制會有一定的麻煩,基於這種情況不如自己實現一套完整的分散式的鑒權機制。

自定義的鑒權機制中利用redis來緩存授權令牌信息,結構大體如下:

從圖上可以看出,用戶登錄成功後的用戶信息緩存在redis,用戶帶著令牌訪問資源伺服器時再對令牌進行解密,獲取到key,然後就可以獲取到用戶信息,可以根據判斷redis中是否有這個key來校驗用戶是否經過授權,大體思路就是這些,下邊就是具體的代碼實現

redis的操作幫助類,幫助類中實現了redis的讀寫分離

  public class RedisBase
    {
#if DEBUG
        private static string[] ReadWriteHosts = { "127.0.0.1:6379" };
        private static string[] ReadOnlyHosts = { "127.0.0.1:6379" };
#endif
#if !DEBUG
         private static string[] ReadWriteHosts = System.Configuration.ConfigurationSettings.AppSettings["RedisWriteHosts"].Split(new char[] { ';' });
         private static string[] ReadOnlyHosts = System.Configuration.ConfigurationSettings.AppSettings["RedisReadOnlyHosts"].Split(new char[] { ';' });
#endif


        #region -- 連接信息 --
        public static PooledRedisClientManager prcm = CreateManager(ReadWriteHosts, ReadOnlyHosts);

        private static PooledRedisClientManager CreateManager(string[] readWriteHosts, string[] readOnlyHosts)
        {
            // 支持讀寫分離,均衡負載  
            return new PooledRedisClientManager(readWriteHosts, readOnlyHosts, new RedisClientManagerConfig
            {
                MaxWritePoolSize = 5, // “寫”鏈接池鏈接數  
                MaxReadPoolSize = 5, // “讀”鏈接池鏈接數  
                AutoStart = true,
            });
        }
#endregion
#region KEY
   
#endregion

#region -- Item String --
        /// <summary> 
        /// 設置單體 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <param name="timeSpan"></param> 
        /// <returns></returns> 
        public static bool Item_Set<T>(string key, T t)
        {
            try
            {
                using (IRedisClient redis = prcm.GetClient())
                {
                    return redis.Set<T>(key, t, new TimeSpan(1, 0, 0));
                }
            }
            catch (Exception ex)
            {
                // LogInfo 
            }
            return false;
        }
        public static int String_Append(string key, string value)
        {
            try
            {
                using (IRedisClient redis = prcm.GetClient())
                {
                    return redis.AppendToValue(key, value);
                }
            }
            catch (Exception ex) { }
            return 0;
        }
        /// <summary> 
        /// 獲取單體 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static T Item_Get<T>(string key) where T : class
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                return redis.Get<T>(key);
            }
        }

        /// <summary> 
        /// 移除單體 
        /// </summary> 
        /// <param name="key"></param> 
        public static bool Item_Remove(string key)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                return redis.Remove(key);
            }
        }
        /// <summary>
        /// 根據指定的Key,將值加1(僅整型有效)
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static long IncrementValue(string key)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                return redis.IncrementValue(key);
            }
        }
        /// <summary>
        /// 根據指定的Key,將值加上指定值(僅整型有效)
        /// </summary>
        /// <param name="key"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static long IncrementValueBy(string key,int count)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                return redis.IncrementValueBy(key,count);
            }
        }
        /// <summary>
        /// 設置過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="time"></param>
        /// <returns></returns>
        public static bool ExpireEntryIn(string key, TimeSpan time)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                return redis.ExpireEntryIn(key, time);
            }
        }
        /// <summary>
        /// 設置過期時間
        /// </summary>
        /// <param name="key"></param>
        /// <param name="expireAt"></param>
        /// <returns></returns>
        public static bool ExpireEntryAt(string key, DateTime expireAt)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                return redis.ExpireEntryAt(key, expireAt);
            }
        }
        /// <summary>
        /// 判斷key是否已存在
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool ContainsKey(string key)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                return redis.ContainsKey(key);
            }
        }
#endregion

        #region -- List --

        public static void List_Add<T>(string key, T t)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                var redisTypedClient = redis.As<T>();
                redisTypedClient.AddItemToList(redisTypedClient.Lists[key], t);
            }
        }



        public static bool List_Remove<T>(string key, T t)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                var redisTypedClient = redis.As<T>();
                return redisTypedClient.RemoveItemFromList(redisTypedClient.Lists[key], t) > 0;
            }
        }
        public static void List_RemoveAll<T>(string key)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                var redisTypedClient = redis.As<T>();
                redisTypedClient.Lists[key].RemoveAll();
            }
        }

        public static long List_Count(string key)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                return redis.GetListCount(key);
            }
        }

        public static List<T> List_GetRange<T>(string key, int start, int count)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                var c = redis.As<T>();
                return c.Lists[key].GetRange(start, start + count - 1);
            }
        }


        public static List<T> List_GetList<T>(string key)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                var c = redis.As<T>();
                return c.Lists[key].GetRange(0, c.Lists[key].Count);
            }
        }

        public static List<T> List_GetList<T>(string key, int pageIndex, int pageSize)
        {
            int start = pageSize * (pageIndex - 1);
            return List_GetRange<T>(key, start, pageSize);
        }

        /// <summary> 
        /// 設置緩存過期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void List_SetExpire(string key, DateTime datetime)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                redis.ExpireEntryAt(key, datetime);
            }
        }
#endregion

#region -- Set --
        public static void Set_Add<T>(string key, T t)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                var redisTypedClient = redis.As<T>();
                redisTypedClient.Sets[key].Add(t);
            }
        }
        public static bool Set_Contains<T>(string key, T t)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                var redisTypedClient = redis.As<T>();
                return redisTypedClient.Sets[key].Contains(t);
            }
        }
        public static bool Set_Remove<T>(string key, T t)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                var redisTypedClient = redis.As<T>();
                return redisTypedClient.Sets[key].Remove(t);
            }
        }
#endregion

#region -- Hash --
        /// <summary> 
        /// 判斷某個數據是否已經被緩存 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Exist<T>(string key, string dataKey)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                return redis.HashContainsEntry(key, dataKey);
            }
        }

        /// <summary> 
        /// 存儲數據到hash表 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Set<T>(string key, string dataKey, T t)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.SetEntryInHash(key, dataKey, value);
            }
        }
        /// <summary> 
        /// 移除hash中的某值 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Remove(string key, string dataKey)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                return redis.RemoveEntryFromHash(key, dataKey);
            }
        }
        /// <summary> 
        /// 移除整個hash 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static bool Hash_Remove(string key)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                return redis.Remove(key);
            }
        }
        /// <summary> 
        /// 從hash表獲取數據 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="dataKey"></param> 
        /// <returns></returns> 
        public static T Hash_Get<T>(string key, string dataKey)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                string value = redis.GetValueFromHash(key, dataKey);
                return ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(value);
            }
        }
        /// <summary> 
        /// 獲取整個hash的數據 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static List<T> Hash_GetAll<T>(string key)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                var list = redis.GetHashValues(key);
                if (list != null && list.Count > 0)
                {
                    List<T> result = new List<T>();
                    foreach (var item in list)
                    {
                        var value = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(value);
                    }
                    return result;
                }
                return null;
            }
        }
        /// <summary> 
        /// 設置緩存過期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void Hash_SetExpire(string key, DateTime datetime)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                redis.ExpireEntryAt(key, datetime);
            }
        }
#endregion

#region -- SortedSet --
        /// <summary> 
        ///  添加數據到 SortedSet 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <param name="score"></param> 
        public static bool SortedSet_Add<T>(string key, T t, double score)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.AddItemToSortedSet(key, value, score);
            }
        }
        /// <summary>
        /// 為有序集 key 的成員 member 的 score 值加上增量 increment 
        /// 可以通過傳遞一個負數值 increment ,讓 score 減去相應的值
        /// 當 key 不存在,或 member 不是 key 的成員時, ZINCRBY key increment member 等同於 ZADD key increment member
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="t"></param>
        /// <param name="incrementBy"></param>
        /// <returns></returns>
        public static double SortedSet_Zincrby<T>(string key,T t,double incrementBy)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.IncrementItemInSortedSet(key, value, incrementBy);
            }
        }
        /// <summary>
        /// 返回有序集 key 中,成員 member 的 score 值。
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="t">member</param>
        /// <returns></returns>
        public static double SortedSet_ZSCORE<T>(string key,T t)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.GetItemScoreInSortedSet(key, value);
            }
        }
        /// <summary> 
        /// 移除數據從SortedSet 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="t"></param> 
        /// <returns></returns> 
        public static bool SortedSet_Remove<T>(string key, T t)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                string value = ServiceStack.Text.JsonSerializer.SerializeToString<T>(t);
                return redis.RemoveItemFromSortedSet(key, value);
            }
        }
        /// <summary> 
        /// 修剪SortedSet 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="size">保留的條數</param> 
        /// <returns></returns> 
        public static long SortedSet_Trim(string key, int size)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                return redis.RemoveRangeFromSortedSet(key, size, 9999999);
            }
        }
        /// <summary> 
        /// 獲取SortedSet的長度 
        /// </summary> 
        /// <param name="key"></param> 
        /// <returns></returns> 
        public static long SortedSet_Count(string key)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                return redis.GetSortedSetCount(key);
            }
        }

        /// <summary> 
        /// 按照由小到大排序獲取SortedSet的分頁數據 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="pageIndex"></param> 
        /// <param name="pageSize"></param> 
        /// <returns></returns> 
        public static List<T> SortedSet_GetList<T>(string key, int pageIndex, int pageSize)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                var list = redis.GetRangeFromSortedSet(key, (pageIndex - 1) * pageSize, pageIndex * pageSize - 1);
                if (list != null && list.Count > 0)
                {
                    List<T> result = new List<T>();
                    foreach (var item in list)
                    {
                        var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(data);
                    }
                    return result;
                }
            }
            return null;
        }
        /// <summary>
        /// 按照由大到小順序獲取SortedSet的分頁數據
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public static List<T> SortedSet_GetListDesc<T>(string key, int pageIndex, int pageSize)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                var list = redis.GetRangeFromSortedSetDesc(key, (pageIndex - 1) * pageSize, pageIndex * pageSize - 1);
                if (list != null && list.Count > 0)
                {
                    List<T> result = new List<T>();
                    foreach (var item in list)
                    {
                        var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(data);
                    }
                    return result;
                }
            }
            return null;
        }
        /// <summary>
        /// 按照由大到小的順序獲取SortedSet包含分數的分頁數據
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public static IDictionary<T,double> SortedSet_GetListWidthScoreDesc<T>(string key, int pageIndex, int pageSize)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                var list = redis.GetRangeWithScoresFromSortedSetDesc(key, (pageIndex - 1) * pageSize, pageIndex * pageSize - 1);
                if (list != null && list.Count > 0)
                {
                    IDictionary<T,double> result = new Dictionary<T,double>();
                    foreach (var item in list)
                    {
                        var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item.Key);
                        result[data]=item.Value;
                    }
                    return result;
                }
            }
            return null;
        }

        /// <summary> 
        /// 獲取SortedSet的全部數據 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="key"></param> 
        /// <param name="pageIndex"></param> 
        /// <param name="pageSize"></param> 
        /// <returns></returns> 
        public static List<T> SortedSet_GetListALL<T>(string key)
        {
            using (IRedisClient redis = prcm.GetReadOnlyClient())
            {
                var list = redis.GetRangeFromSortedSet(key, 0, 9999999);
                if (list != null && list.Count > 0)
                {
                    List<T> result = new List<T>();
                    foreach (var item in list)
                    {
                        var data = ServiceStack.Text.JsonSerializer.DeserializeFromString<T>(item);
                        result.Add(data);
                    }
                    return result;
                }
            }
            return null;
        }

        /// <summary> 
        /// 設置緩存過期 
        /// </summary> 
        /// <param name="key"></param> 
        /// <param name="datetime"></param> 
        public static void SortedSet_SetExpire(string key, DateTime datetime)
        {
            using (IRedisClient redis = prcm.GetClient())
            {
                redis.ExpireEntryAt(key, datetime);
            }
        }
#endregion
    }

 

用戶類UserInfo

public class UserInfo
{
}

 

用戶管理類,在asp.net web api中添加一個用戶管理類,用來管理用戶信息

public class ApiUserManager
    {
        private HttpActionContext actionContext;
        public ApiUserManager(HttpActionContext actionContext)
        {
            this.actionContext = actionContext;
        }
        

        private UserInfo _User;
        /// <summary>
        /// 當前用戶
        /// </summary>
        public UserInfo User
        {
            get
            {
                if (_User==null)
                {
                    string key = GetKey();
                    if (!string.IsNullOrEmpty(key) && RedisBase.ContainsKey(key))
                    {
                        _User = RedisBase.Item_Get<UserInfo>(key);
                    }
                }
                return _User;
            }
        }

        string GetKey()
        {
            if (actionContext.Request.Headers.Contains("Authorization"))
            {
                string base64Code = actionContext.Request.Headers.GetValues("Authorization").FirstOrDefault();
                //code結構為:userid-UserAgent.MD5()-隨機數-時間戳
                string code = EncryptUtil.UnBase64(base64Code);
                string[] para = code.Split(new[] { "-" }, StringSplitOptions.RemoveEmptyEntries);
                string key = (para[0] + para[1] + para[3]).MD5();
                return key;
            }
            return string.Empty;
        }
        /// <summary>
        /// 用戶是否已經登錄
        /// </summary>
        /// <returns></returns>
        public bool ExistsLogin()
        {
            string base64Code = string.Empty;
            if (actionContext.Request.Headers.Contains("Authorization"))
            {
                base64Code = actionContext.Request.Headers.GetValues("Authorization").FirstOrDefault();
            }
            if (base64Code.IsNull())
            {
                return false;
            }
            //code結構為:userid-UserAgent.MD5()-隨機數-時間戳
            string code = EncryptUtil.UnBase64(base64Code);
            string[] para = code.Split(new[] { "-" }, StringSplitOptions.RemoveEmptyEntries);
            if (para.Length != 4)
            {
                return false;
            }
            string key = (para[0] + para[1] + para[3]).MD5();
            if (!RedisBase.ContainsKey(key))
            {
                return false;
            }
            return true;
        }
        /// <summary>
        /// 用戶登錄返回令牌
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public string GetUserToken(UserInfo user)
        {
            string uagin = actionContext.Request.Headers.UserAgent.TryToString().MD5();
            string rm = Utils.GenPsw(11,11);
            long time = Utils.GetUnixTime();
            string code = string.Format(<

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

-Advertisement-
Play Games
更多相關文章
  • 一些介紹 CodeFirst是EntityFrameworks的一種開發模式,即代碼優先,它以業務代碼為主,通過代碼來生成資料庫,並且加上migration的強大數據表比對功能來生成資料庫版本,讓程式開發人員不用維護資料庫的變更,而直接維護migration即可,在它裡面有你當前版本和過去歷史版本的 ...
  • // 1. asp頁面使用EasyUI框架需要的Css樣式和JS <script src="../script/jquery-easyui-1.4.5/jquery.min.js" type="text/javascript" charset = "utf-8"></script> <script ...
  • 把一個 dic綁定到了listview上,有時候下拉列表會報這個異常。因為直接使用了itemssource = dic,而dic在另一個線程上不定期更新,這樣如果直接綁定的話就會報這個錯誤,原因是直接綁定的話會把itemssource的記憶體地址直接指向dic的記憶體地址,當dic更新後,會導致記憶體地址 ...
  • 訂單系統設計 總體設計 1.每次下單時間少於3秒 2.庫存驗證不存在多買的情況 3.訂單能夠按照不同供應商進程拆分 4. 物流信息能夠回傳 訂單狀態機設計 1.待系統審核 2.待支付 3.待發貨 4.待簽收 5.已完成 6.訂單關閉 訂單狀態流轉如下圖示: 1)審核失敗 2)未支付(待支付24小時) ...
  • 1.根據單個分隔字元用split截取 例如 即可得到sArray[0]="GT123",sArray[1]="1"; 2.利用多個字元來分隔字元串 例如 得到sArray[0]="GTAZB",sArray[1]="Jiang",sArray[2]="Ben",sArray[3]="123"; 3根 ...
  • 在前端 UI 開發中,有時,我們會遇到這樣的需求:在一個 ScrollViewer 中有很多內容,而我們需要實現在執行某個操作後能夠定位到其中指定的控制項處;這很像在 HTML 頁面中點擊一個鏈接後定位到當前網頁上的某個 anchor。 要實現它,首先我們需要看 ScrollViewer 為我們提供的 ...
  • TypeInfo,PropertyInfo,MethodInfo,FieldInfo ...
  • 在這篇博客中,我們將介紹如下內容: ==運算符與基元類型 ==運算符與引用類型 ==運算符與String類型 ==運算符與值類型 ==運算符與泛型 ==運算符與基元類型 我們分別用兩種方式比較兩個整數,第一個使用的是Equals(int)方法,每二個使用的是==運算符: 運行上面的示例,兩個語句出的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...