基於SqlSugar的開發框架循序漸進介紹(6)-- 在基類介面中註入用戶身份信息介面

来源:https://www.cnblogs.com/wuhuacong/archive/2022/05/24/16305022.html
-Advertisement-
Play Games

在基於SqlSugar的開發框架中,我們設計了一些系統服務層的基類,在基類中會有很多涉及到相關的數據處理操作的,如果需要跟蹤具體是那個用戶進行操作的,那麼就需要獲得當前用戶的身份信息,包括在Web API的控制器中也是一樣,需要獲得對應的用戶身份信息,才能進行相關的身份鑒別和處理操作。本篇隨筆介紹基... ...


在基於SqlSugar的開發框架中,我們設計了一些系統服務層的基類,在基類中會有很多涉及到相關的數據處理操作的,如果需要跟蹤具體是那個用戶進行操作的,那麼就需要獲得當前用戶的身份信息,包括在Web API的控制器中也是一樣,需要獲得對應的用戶身份信息,才能進行相關的身份鑒別和處理操作。本篇隨筆介紹基於Principal的用戶身份信息的存儲和讀取操作,以及在適用於Winform程式中的記憶體緩存的處理方式,從而通過在基類介面中註入用戶身份信息介面方式,獲得當前用戶的詳細身份信息。

1、用戶身份介面的定義和基類介面註入 

為了方便獲取用戶身份的信息,我們定義一個介面 IApiUserSession 如下所示。

   /// <summary>
    /// API介面授權獲取的用戶身份信息-介面
    /// </summary>
    public interface IApiUserSession
    {
        /// <summary>
        /// 用戶登錄來源渠道,0為網站,1為微信,2為安卓APP,3為蘋果APP   
        /// </summary>
        string Channel { get; }

        /// <summary>
        /// 用戶ID
        /// </summary>
        int? Id { get; }

        /// <summary>
        /// 用戶名稱
        /// </summary>
        string Name { get; }

        /// <summary>
        /// 用戶郵箱(可選)   
        /// </summary>
        string Email { get; }

        /// <summary>
        /// 用戶手機(可選)   
        /// </summary>
        string Mobile { get; }

        /// <summary>
        /// 用戶全名稱(可選)   
        /// </summary>
        string FullName { get; }

        /// <summary>
        /// 性別(可選)
        /// </summary>
        string Gender { get; }

        /// <summary>
        /// 所屬公司ID(可選)   
        /// </summary>
        string Company_ID { get; }

        /// <summary>
        /// 所屬公司名稱(可選)   
        /// </summary>
        string CompanyName { get; }

        /// <summary>
        /// 所屬部門ID(可選)   
        /// </summary>
        string Dept_ID { get; }

        /// <summary>
        /// 所屬部門名稱(可選)   
        /// </summary>
        string DeptName { get; }

        /// <summary>
        /// 把用戶信息設置到緩存中去
        /// </summary>
        /// <param name="info">用戶登陸信息</param>
        /// <param name="channel">預設為空,用戶登錄來源渠道:0為網站,1為微信,2為安卓APP,3為蘋果APP </param>
        void SetInfo(LoginUserInfo info, string channel = null);
    }

其中的SetInfo是為了在用戶身份登錄確認後,便於將用戶信息存儲起來的一個介面方法。其他屬性定義用戶相關的信息。

由於這個用戶身份信息的介面,我們提供給基類進行使用的,預設我們在基類定義一個介面對象,並通過提供預設的NullApiUserSession實現,便於引用對應的身份屬性信息。

NullApiUserSession只是提供一個預設的實現,實際在使用的時候,我們會註入一個具體的介面實現來替代它的。

    /// <summary>
    /// 提供一個空白實現類,具體使用IApiUserSession的時候,會使用其他實現類
    /// </summary>
    public class NullApiUserSession : IApiUserSession
    {
        /// <summary>
        /// 單件實例
        /// </summary>
        public static NullApiUserSession Instance { get; } = new NullApiUserSession();

        public string Channel => null;

        public int? Id => null;

        public string Name => null;

..................

/// <summary> /// 設置信息(保留為空) /// </summary> public void SetInfo(LoginUserInfo info, string channel = null) { } }

在之前介紹的SqlSugar框架的時候,我們介紹到數據訪問操作的基類定義,如下所示。

    /// <summary>
    /// 基於SqlSugar的資料庫訪問操作的基類對象
    /// </summary>
    /// <typeparam name="TEntity">定義映射的實體類</typeparam>
    /// <typeparam name="TKey">主鍵的類型,如int,string等</typeparam>
    /// <typeparam name="TGetListInput">或者分頁信息的條件對象</typeparam>
    public abstract class MyCrudService<TEntity, TKey, TGetListInput> : 
        IMyCrudService<TEntity, TKey, TGetListInput>
        where TEntity : class, IEntity<TKey>, new()
        where TGetListInput : IPagedAndSortedResultRequest
    {
        /// <summary>
        /// 資料庫上下文信息
        /// </summary>
        protected DbContext dbContext;

/// <summary> /// 當前Api用戶信息 /// </summary> public IApiUserSession CurrentApiUser { get; set; } public MyCrudService() { dbContext = new DbContext(); CurrentApiUser = NullApiUserSession.Instance;//空實現 }

在最底層的操作基類中,我們就已經註入了用戶身份信息,這樣我們不管操作任何函數處理,都可以通過該用戶身份信息介面CurrentApiUser獲得對應的用戶屬性信息了。

在具體的業務服務層中,我們繼承該基類,並提供構造函數註入方式,讓基類獲得對應的 IApiUserSession介面的具體實例。

    /// <summary>
    /// 應用層服務介面實現
    /// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
    {
        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="currentApiUser">當前用戶介面</param>
        public CustomerService(IApiUserSession currentApiUser)
        {
            this.CurrentApiUser = currentApiUser;
        }
          
         ........

    }

如果有其他服務介面需要引入,那麼我們繼續增加其他介面註入即可。

    /// <summary>
    /// 角色信息 應用層服務介面實現
    /// </summary>
    public class RoleService : MyCrudService<RoleInfo,int, RolePagedDto>, IRoleService
    {
        private IOuService _ouService;
        private IUserService _userService;

        /// <summary>
        /// 預設構造函數
        /// </summary>
        /// <param name="currentApiUser">當前用戶介面</param>
        /// <param name="ouService">機構服務介面</param>
        /// <param name="userService">用戶服務介面</param>
        public RoleService(IApiUserSession currentApiUser, IOuService ouService, IUserService userService)
        {
            this.CurrentApiUser = currentApiUser;
            this._ouService = ouService;
            this._userService = userService;
        }

由於該介面是通過構造函數註入的,因此在系統運行前,我們需要往IOC容器中註冊對應的介面實現類(由於IApiUserSession 提供了多個介面實現,我們這裡不自動加入它的對應介面,而通過手工加入)。

在Winform或者控制台程式,啟動程式的時候,手工加入對應的介面到IOC容器中即可。

/// <summary>
/// 應用程式的主入口點。
/// </summary>
[STAThread]
static void Main()
{
    // IServiceCollection負責註冊
    IServiceCollection services = new ServiceCollection();
    //services.AddSingleton<IDictDataService, DictDataService>();
    
    //調用自定義的服務註冊
    ServiceInjection.ConfigureRepository(services);            
    
    //添加IApiUserSession實現類
    //services.AddSingleton<IApiUserSession, ApiUserCache>(); //緩存實現方式
    services.AddSingleton<IApiUserSession, ApiUserPrincipal>(); //CurrentPrincipal實現方式

如果是Web API或者asp.net core項目中加入,也是類似的處理方式。

var builder = WebApplication.CreateBuilder(args);
//配置依賴註入訪問資料庫
ServiceInjection.ConfigureRepository(builder.Services);

//添加IApiUserSession實現類
builder.Services.AddSingleton<IApiUserSession, ApiUserPrincipal>();

前面介紹了,IApiUserSession的一個空白實現,是預設的介面實現,我們具體會使用基於Principal或者緩存方式實現記錄用戶身份的信息實現,如下是它們的類關係。

在上面的代碼中,我們註入一個 ApiUserPrincipal 的用戶身份介面實現。

 

2、基於Principal的用戶身份信息的存儲和讀取操作

 ApiUserPrincipal 的用戶身份介面實現是可以實現Web及Winform的用戶身份信息的存儲的。

首先我們先定義一些存儲聲明信息的鍵,便於統一處理。

    /// <summary>
    /// 定義一些常用的ClaimType存儲鍵
    /// </summary>
    public class ApiUserClaimTypes
    {
        public const string Id = JwtClaimTypes.Id;
        public const string Name = JwtClaimTypes.Name;
        public const string NickName = JwtClaimTypes.NickName;
        public const string Email = JwtClaimTypes.Email;
        public const string PhoneNumber = JwtClaimTypes.PhoneNumber;
        public const string Gender = JwtClaimTypes.Gender;
        public const string FullName = "FullName";
        public const string Company_ID = "Company_ID";
        public const string CompanyName = "CompanyName";
        public const string Dept_ID = "Dept_ID";
        public const string DeptName = "DeptName";

        public const string Role = ClaimTypes.Role;
    }

 ApiUserPrincipal 用戶身份介面實現的定義如下代碼所示。

    /// <summary>
    /// 基於ClaimsPrincipal實現的用戶信息介面。
    /// </summary>
    [Serializable]
    public class ApiUserPrincipal : IApiUserSession
    {
        /// <summary>
        /// IHttpContextAccessor對象
        /// </summary>
        private readonly IHttpContextAccessor _httpContextAccessor;

        /// <summary>
        /// 如果IHttpContextAccessor.HttpContext?.User非空獲取HttpContext的ClaimsPrincipal,否則獲取線程的CurrentPrincipal
        /// </summary>
        protected ClaimsPrincipal Principal => _httpContextAccessor?.HttpContext?.User ?? (Thread.CurrentPrincipal as ClaimsPrincipal);

        /// <summary>
        /// 預設構造函數
        /// </summary>
        /// <param name="httpContextAccessor"></param>
        public ApiUserPrincipal(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        /// <summary>
        /// 預設構造函數
        /// </summary>
        public ApiUserPrincipal() { }

基於Web  API的時候,用戶身份信息是基於IHttpContextAccessor 註入的介面獲得 httpContextAccessor?.HttpContext?.User 的 ClaimsPrincipal 屬性操作的。

我們獲取用戶身份的屬性的時候,直接通過這個屬性判斷獲取即可。

        /// <summary>
        /// 用戶ID
        /// </summary>
        public int? Id => this.Principal?.FindFirst(ApiUserClaimTypes.Id)?.Value.ToInt32();

        /// <summary>
        /// 用戶名稱
        /// </summary>
        public string Name => this.Principal?.FindFirst(ApiUserClaimTypes.Name)?.Value;

而上面同時也提供了一個基於Windows的線程Principal 屬性(Thread.CurrentPrincipal )的聲明操作,操作模型和Web 的一樣的,因此Web和WinForm的操作是一樣的。

 

在用戶登錄介面處理的時候,我們需要統一設置一下用戶對應的聲明信息,存儲起來供查詢使用。

        /// <summary>
        /// 主要用於Winform寫入Principal的ClaimsIdentity
        /// </summary>
        public void SetInfo(LoginUserInfo info, string channel = null)
        {
            //new WindowsPrincipal(WindowsIdentity.GetCurrent());

            var claimIdentity = new ClaimsIdentity("login");
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.Id, info.ID ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.Name, info.UserName ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.Email, info.Email ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.PhoneNumber, info.MobilePhone ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.Gender, info.Gender ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.FullName, info.FullName ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.Company_ID, info.CompanyId ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.CompanyName, info.CompanyName ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.Dept_ID, info.DeptId ?? ""));
            claimIdentity.AddClaim(new Claim(ApiUserClaimTypes.DeptName, info.DeptName ?? ""));

            //此處不可以使用下麵註釋代碼
            //this.Principal?.AddIdentity(claimIdentity);

            //Thread.CurrentPrincipal設置會導致在非同步線程中設置的結果丟失
            //因此統一採用 AppDomain.CurrentDomain.SetThreadPrincipal中設置,確保進程中所有線程都會複製到信息
            IPrincipal principal = new GenericPrincipal(claimIdentity, null);
            AppDomain.CurrentDomain.SetThreadPrincipal(principal);
        }

在上面中,我特別聲明“Thread.CurrentPrincipal設置會導致在非同步線程中設置的結果丟失” ,這是我在反覆測試中發現,不能在非同步方法中設置Thread.CurrentPrincipal的屬性,否則屬性會丟失,因此主線程的Thread.CurrentPrincipal 會賦值替換掉非同步線程中的Thread.CurrentPrincipal屬性。

而.net 提供了一個程式域的方式設置CurrentPrincipal的方法,可以或者各個線程中統一的信息。

AppDomain.CurrentDomain.SetThreadPrincipal(principal);

基於WInform的程式,我們在登錄界面中處理用戶登錄操作

 但用戶確認登錄的時候,測試用戶的賬號密碼,成功則在本地設置用戶的身份信息。

        /// <summary>
        /// 統一設置登陸用戶相關的信息
        /// </summary>
        /// <param name="info">當前用戶信息</param>
        public async Task SetLoginInfo(LoginResult loginResult)
        {
            var info = loginResult.UserInfo; //用戶信息

            //獲取用戶的角色集合
            var roles = await BLLFactory<IRoleService>.Instance.GetRolesByUser(info.Id);
            //判斷用戶是否超級管理員||公司管理員
            var isAdmin = roles.Any(r => r.Name == RoleInfo.SuperAdminName || r.Name == RoleInfo.CompanyAdminName);

            //初始化許可權用戶信息
            Portal.gc.UserInfo = info; //登陸用戶
            Portal.gc.RoleList = roles;//用戶的角色集合
            Portal.gc.IsUserAdmin = isAdmin;//是否超級管理員或公司管理員
            Portal.gc.LoginUserInfo = this.ConvertToLoginUser(info); //轉換為窗體可以緩存的對象

            //設置身份信息到共用對象中(Principal或者Cache) 
            BLLFactory<IApiUserSession>.Instance.SetInfo(Portal.gc.LoginUserInfo);

            await Task.CompletedTask;
        }

通過SetInfo,我們把當前用戶的信息設置到了域的Principal中,進程內的所有線程共用這份用戶信息數據。

跟蹤介面的調用,我們可以查看到對應的用戶身份信息了。

可以看到,這個介面已經註入到了服務類中,並且獲得了相應的用戶身份信息了。

同樣在Web API的登錄處理的時候,會生成相關的JWT token的信息的。

           var loginResult = await this._userService.VerifyUser(dto.LoginName, dto.Password, ip);
            if (loginResult != null && loginResult.UserInfo != null)
            {
                var userInfo = loginResult.UserInfo;

                authResult.AccessToken = GenerateToken(userInfo); //令牌
                authResult.Expires = expiredDays * 24 * 3600; //失效秒數
                authResult.Succes = true;//成功

                //設置緩存用戶信息
                //SetUserCache(userInfo);
            }
            else
            {
                authResult.Error = loginResult?.ErrorMessage;
            }

其中生成的JWT token的邏輯如下所示。

        /// <summary>
        /// 生成JWT用戶令牌
        /// </summary>
        /// <returns></returns>
        private string GenerateToken(UserInfo userInfo)
        {
            var claims = new List<Claim>
            {
                new Claim(ApiUserClaimTypes.Id, userInfo.Id.ToString()),
                new Claim(ApiUserClaimTypes.Email, userInfo.Email),
                new Claim(ApiUserClaimTypes.Name, userInfo.Name),
                new Claim(ApiUserClaimTypes.NickName, userInfo.Nickname),
                new Claim(ApiUserClaimTypes.PhoneNumber, userInfo.MobilePhone),
                new Claim(ApiUserClaimTypes.Gender, userInfo.Gender),

                new Claim(ApiUserClaimTypes.FullName, userInfo.FullName),
                new Claim(ApiUserClaimTypes.Company_ID, userInfo.Company_ID),
                new Claim(ApiUserClaimTypes.CompanyName, userInfo.CompanyName),
                new Claim(ApiUserClaimTypes.Dept_ID, userInfo.Dept_ID),
                new Claim(ApiUserClaimTypes.DeptName, userInfo.DeptName),
            };

            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Secret"]));
            var jwt = new JwtSecurityToken
            (
                issuer: _configuration["Jwt:Issuer"],
                audience: _configuration["Jwt:Audience"],
                claims: claims,
                expires: DateTime.Now.AddDays(expiredDays),//有效時間
                signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
             );
            var token = new JwtSecurityTokenHandler().WriteToken(jwt);
            return token;
        }

說生成的一系列字元串,我們可以通過解碼工具,可以解析出來對應的信息的。

在登錄授權的這個時候,控制器會把相關的Claim信息寫入到token中的,我們在客戶端發起對控制器方法的調用的時候,這些身份信息會轉換成對象信息。

我們調試控制器的方法入口,如可以通過Fiddler的測試介面的調用情況。

 

可以看到CurrentApiUser的信息就是我們發起用戶身份信息,如下圖所示。

在監視視窗中查看IApiUserSession對象,可以查看到對應的信息。

 

  

3、基於記憶體緩存的用戶身份介面實現處理方式

 在前面介紹的IApiUserSession的介面實現的時候,我們也提供了另外一個基於MemoryCache的緩存實現方式,和基於Principal憑證信息處理不同,我們這個是基於MemoryCache的存儲方式。

它的實現方法也是類似的,我們這裡也一併介紹一下。

    /// <summary>
    /// 基於MemeoryCache實現的用戶信息介面
    /// </summary>
    public class ApiUserCache : IApiUserSession
    {
        /// <summary>
        /// 記憶體緩存對象
        /// </summary>
        private static readonly ObjectCache Cache = MemoryCache.Default;

        /// <summary>
        /// 預設構造函數
        /// </summary>
        public ApiUserCache()
        {
        }

        /// <summary>
        /// 把用戶信息設置到緩存中去
        /// </summary>
        /// <param name="info">用戶登陸信息</param>
        public void SetInfo(LoginUserInfo info, string channel = null)
        {
            SetItem(ApiUserClaimTypes.Id, info.ID);
            SetItem(ApiUserClaimTypes.Name, info.UserName);
            SetItem(ApiUserClaimTypes.Email, info.Email);
            SetItem(ApiUserClaimTypes.PhoneNumber, info.MobilePhone);
            SetItem(ApiUserClaimTypes.Gender, info.Gender);
            SetItem(ApiUserClaimTypes.FullName, info.FullName);
            SetItem(ApiUserClaimTypes.Company_ID, info.CompanyId);
            SetItem(ApiUserClaimTypes.CompanyName, info.CompanyName);
            SetItem(ApiUserClaimTypes.Dept_ID, info.DeptId);
            SetItem(ApiUserClaimTypes.DeptName, info.DeptName);
        }

        /// <summary>
        /// 設置某個屬性對象
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void SetItem(string key, object value)
        {
            if (!string.IsNullOrEmpty(key))
            {
                Cache.Set(key, value ?? "", DateTimeOffset.MaxValue, null);
            }
        }

        /// <summary>
        /// 用戶ID
        /// </summary>
        public int? Id => (Cache.Get(ApiUserClaimTypes.Id) as string)?.ToInt32();

        /// <summary>
        /// 用戶名稱
        /// </summary>
        public string Name => Cache.Get(ApiUserClaimTypes.Name) as string;

        /// <summary>
        /// 用戶郵箱(可選)   
        /// </summary>
        public string Email => Cache.Get(ApiUserClaimTypes.Email) as string;

         ..............
    }

我們通過 MemoryCache.Default 構造一個記憶體緩存的對象,然後在設置信息的時候,把用戶信息按照鍵值方式設置即可。在Winform中我們可以採用記憶體緩存的方式存儲用戶身份信息,而基於Web方式的,則會存在併發多個用戶的情況,不能用緩存來處理。

一般情況下,我們採用 ApiUserPrincipal 來處理用戶身份信息就很好了。

4、單元測試的用戶身份處理

在做單元測試的時候,我們如果需要設置測試介面的用戶身份信息,那麼就需要在初始化函數裡面設置好用戶信息,如下所示。

    [TestClass]
    public class UnitTest1
    {
        private static IServiceProvider Provider = null;

        /*
        帶有[ClassInitialize()] 特性的方法在執行類中第一個測試之前調用。
        帶有[TestInitialize()] 特性的方法在執行每個測試前都會被調用,一般用來初始化環境,為單元測試配置一個特定已知的狀態。
        帶有[ClassCleanup()] 特性的方法將在類中所有的測試運行完後執行。
        */
        //[TestInitialize] //每個測試前調用
        [ClassInitialize] //測試類第一次調用
        public static void Setup(TestContext context)
        {
            // IServiceCollection負責註冊
            IServiceCollection services = new ServiceCollection();
            //調用自定義的服務註冊
            ServiceInjection.ConfigureRepository(services);

            //註入當前Api用戶信息處理實現,服務對象可以通過IApiUserSession獲得用戶信息
            //services.AddSingleton<IApiUserSession, ApiUserCache>(); //緩存實現方式
            services.AddSingleton<IApiUserSession, ApiUserPrincipal>(); //CurrentPrincipal實現方式

            // IServiceProvider負責提供實例
            Provider = services.BuildServiceProvider();

            //模擬寫入登錄用戶信息
            WriteLoginInfo();
        }

        /// <summary>
        /// 寫入用戶登陸信息,IApiUserSession介面才可使用獲取身份
        /// </summary>
        static void WriteLoginInfo()
        {
            var mockUserInfo = new LoginUserInfo()
            {
                ID = "1",
                Email = "[email protected]	   

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

-Advertisement-
Play Games
更多相關文章
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 每日一句 軍人天生就捨棄了戰鬥的意義! 概述 RabitMQ 發佈確認,保證消息在磁碟上。 前提條件 1。隊列必須持久化 隊列持久化 2。隊列中的消息必須持久化 消息持久化 使用 三種發佈確認的方式: 1。單個發佈確認 2。批量發佈確認 3。非同步批量發佈確認 開啟發佈確認的方法 //創建一個連接工廠 ...
  • 使用FFmpeg庫做的項目,調試項目的時候發現,連續解視頻進行播放,會有明顯記憶體增加問題。連續工作10個小時後就會 被linux 內核kill掉。 通過逐步註掉代碼和網上查閱資料。最後發現記憶體泄漏有一些幾個地方: 一、av_read_frame的問題 從網上查閱大神們的經驗,主要是av_read_f ...
  • 在Java 9中又新增了一些API來幫助便捷的創建不可變集合,以減少代碼複雜度。 本期配套視頻:Java 9 新特性:快速定義不可變集合 常規寫法 以往我們創建一些不可變集合的時候,通常是這樣寫的: // 不可變的Set Set<String> set = new HashSet<>(); set. ...
  • 反向代理(2022/03/31) 簡單記錄 Nginx 反向代理相關的一些配置文件,描述不足之處請自行查閱相關資料。 1. HTTP 配置 upstream web { server domain.com:80; } server { # 監聽 tcp4 listen 80; # 監聽 tcp6 l ...
  • 爬蟲代理 IP 池及隧道代理 日常開發中,偶爾會遇到爬取網頁數據的需求,為了隱藏本機真實 IP,常常會用到代理 IP 池,本文將基於 openresty 與代理 IP 池搭建更為易用的隧道代理。 1. 代理 IP 池 1.1 簡介 代理 IP 池即在資料庫中維護一個可用的 IP 代理隊列,一般實現思 ...
  • 來源:www.cnblogs.com/keyyang/p/4128424.html 我們可能經常會用到 Thread.Sleep 函數來使線程掛起一段時間。那麼你有沒有正確的理解這個函數的用法呢?思考下麵這兩個問題: 假設現在是 2008-4-7 12:00:00.000,如果我調用一下 Threa ...
  • 1.Docker基本介紹? Docker就是虛擬化的一種輕量級替代技術,基於Go語言的開源應用容器引擎。Docker的容器技術不依賴任何語言、框架或系統,可以將應用程式變成一種標準化的、可移植的、自管理的組件,並脫離伺服器硬體在任何主流系統中開發、調試和運行。 光看這個介紹還不足以知道Docker是 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 插件化的需求主要源於對軟體架構靈活性的追求,特別是在開發大型、複雜或需要不斷更新的軟體系統時,插件化可以提高軟體系統的可擴展性、可定製性、隔離性、安全性、可維護性、模塊化、易於升級和更新以及支持第三方開發等方面的能力,從而滿足不斷變化的業務需求和技術挑戰。 一、插件化探索 在WPF中我們想要開 ...
  • 歡迎ReaLTaiizor是一個用戶友好的、以設計為中心的.NET WinForms項目控制項庫,包含廣泛的組件。您可以使用不同的主題選項對項目進行個性化設置,並自定義用戶控制項,以使您的應用程式更加專業。 項目地址:https://github.com/Taiizor/ReaLTaiizor 步驟1: ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • Channel 是乾什麼的 The System.Threading.Channels namespace provides a set of synchronization data structures for passing data between producers and consume ...
  • efcore如何優雅的實現按年分庫按月分表 介紹 本文ShardinfCore版本 本期主角: ShardingCore 一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學習成本、零業務代碼入侵適配 距離上次發文.net相關的已經有很久了,期間一直在從事java相關的 ...
  • 前言 Spacesniffer 是一個免費的文件掃描工具,通過使用樹狀圖可視化佈局,可以立即瞭解大文件夾的位置,幫助用戶處理找到這些文件夾 當前系統C盤空間 清理後系統C盤空間 下載 Spacesniffer 下載地址:https://spacesniffer.en.softonic.com/dow ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 一、ReZero簡介 ReZero是一款.NET中間件 : 全網唯一開源界面操作就能生成API , 可以集成到任何.NET6+ API項目,無破壞性,也可讓非.NET用戶使用exe文件 免費開源:MIT最寬鬆協議 , 一直從事開源事業十年,一直堅持開源 1.1 純ReZero開發 適合.Net Co ...
  • 一:背景 1. 講故事 停了一個月沒有更新文章了,主要是忙於寫 C#內功修煉系列的PPT,現在基本上接近尾聲,可以回頭繼續更新這段時間分析dump的一些事故報告,有朋友微信上找到我,說他們的系統出現了大量的http超時,程式不響應處理了,讓我幫忙看下怎麼回事,dump也抓到了。 二:WinDbg分析 ...
  • 開始做項目管理了(本人3年java,來到這邊之後真沒想到...),天天開會溝通整理需求,他們講話的時候忙裡偷閑整理一下常用的方法,其實語言還是有共通性的,基本上看到方法名就大概能猜出來用法。出去打水的時候看到外面太陽好好,真想在外面坐著曬太陽,回來的時候好兄弟三年前送給我的鍵盤D鍵不靈了,在打"等待 ...