深度探索.NET Feature Management功能開關的魔法

来源:https://www.cnblogs.com/ruipeng/p/18098211
-Advertisement-
Play Games

前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...


前言

.NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功能,並根據需要快速調整和部署新功能。 Feature Management 還提供了一些方便的工具和 API,幫助開發人員更輕鬆地實現功能管理和控制。

安裝

  • .Net CLI
dotnet add package Microsoft.FeatureManagement.AspNetCore --version 4.0.0-preview2
  • Package Manager
NuGet\Install-Package Microsoft.FeatureManagement.AspNetCore -Version 4.0.0-preview2

或者 Vs Nuget 包管理 管理工具安裝等

依賴註入

.Net 功能管理器是通過框架的本機配置系統配置的,簡單來說只要是.Net 的配置系統支持的數據源都可以用做功能管理(FeatureManagement)的配置源

.NET 中的配置是使用一個或多個配置提供程式執行的。 配置提供程式使用各種配置源從鍵值對讀取配置數據:

  • 設置文件,例如 appsettings.json
  • 環境變數
  • Azure Key Vault
  • Azure 應用配置
  • 命令行參數
  • 已安裝或已創建的自定義提供程式
  • 目錄文件
  • 記憶體中的 .NET 對象
  • 第三方提供程式

.NET 中的配置提供程式

依賴註入:

service.AddFeatureManagement();

預設情況下,功能管理器從 .NET appsettings.json配置數據的 FeatureManagement Section 來獲取數據

  // Define feature flags in config file
  "FeatureManagement": {
    "sayHello": true, // On feature
    "todo": false // Off feature
  }

當然也可以自定義 Section

service.AddFeatureManagement(builder.Configuration.GetSection("CustomFeatureManagement"));
  // Define feature flags in config file
  "CustomFeatureManagement": {
    "sayHello": true, // On feature
    "todo": false // Off feature
  }

功能開關註冊成 Scoped

AddFeatureManagement 方法將特性管理服務作為單例添加到應用程式中,但有些情況下可能需要將特性管理服務添加為Scoped(作用域服務)。例如,我們可能希望使用 Scoped 以獲取上下文信息的功能過濾器。在這種情況下,應該使用 AddScopedFeatureManagement 方法, 這將確保功能管理服務(包括功能過濾器)被添加為 Scoped 服務。

//功能管理註冊 Scoped 作用域
service.AddScopedFeatureManagement();

功能管理的基本形式是檢查功能標誌是否已啟用,然後根據結果執行操作。這通過 IFeatureManagerIsEnabledAsync 方法來實現。

對我們上面的 FeatureManager 的配置來做一個驗證

  • sayhello 功能開關標誌測試
app.MapGet("/sayHello", async Task<IResult> ([FromServices] IFeatureManager manager, string name) =>
{
    if (await manager.IsEnabledAsync("sayHello"))
    {
        return TypedResults.Ok($"hello {name}");
    }
    return TypedResults.NotFound();

}).WithSummary("sayHello");

調用介面查看一下結果,在配置中我們的sayHello設置為true

image

狀態碼為 200,返回信息"hello Ruipeng",符合預期,功能開啟正常。

  • todo 功能開關標誌測試
app.MapGet("/todo", async Task<IResult> ([FromServices] IFeatureManager manager) =>
{
    if (await manager.IsEnabledAsync("todo"))
    {
        return TypedResults.Ok($"todo is enabled !");
    }
    return TypedResults.NotFound();

}).WithSummary("todo");

調用介面查看一下結果,狀態碼 404,返回信息 Not Found,符合預期,功能未開啟。

image

上面的示例簡單講解了一下功能開關的使用,接下來深入瞭解功能開關的配置

功能開關的定義

功能開關的標誌由兩部分組成:名稱和用於啟用功能的過濾器列表。

功能過濾器(Feature filters)定義了功能應何時啟用的場景。在評估特性是開啟還是關閉時,會遍歷其功能過濾器列表,直到其中一個過濾器決定啟用該特性。如果一個過濾器都沒有標識改功能應該開啟,那此功能標誌是關閉的狀態。

內置過濾器

  • AlwaysOn: 總是開啟
  • PercentageFilter:根據百分比隨機啟用/禁用功能。這個過濾器允許您基於一個百分比值來決定功能被啟用的概率,提供了一種簡單而靈活的機制來控制特性的曝光範圍。
  • TimeWindowFilter:在預定義的時間視窗內啟用特性。這個過濾器允許您指定特性的開始和結束時間,確保特性只在特定的時間段內可用。這對於限時活動或測試場景非常有用。
  • TargetingFilter:(這個主要是在Azure 用為目標受眾啟用功能的分階段推出針對特定用戶或用戶組啟用特性。這個過濾器允許您根據用戶屬性或標識來啟用特性,例如基於用戶 ID、角色、地區等。此外,對於此過濾器,您還可以設置一個百分比值,以進一步控制特性在目標用戶中的啟用概率。

詳細信息可以參考註冊功能篩選器 Docs

過濾器的配置指南

需要註意的是在功能標誌名稱中禁止使用冒號:,這是為了遵循一定的命名規範,避免與現有的或未來的功能管理系統產生衝突或造成解析錯誤。在定義功能標誌名稱時,請確保使用合法和合適的字元組合,以確保系統的穩定性和可維護性。
功能使用 EnabledFor 屬性來定義它們的功能過濾器


AlwaysOn 過濾器

  // Define feature flags in config file
  "FeatureManagement": {
    //始終啟用該功能
    "featureAlwaysOn": {
      "EnabledFor": [
        {
          "Name": "AlwaysOn"
        }
      ]
    }
  }
app.MapGet("/featureAlwaysOn", async Task<IResult> (IFeatureManager manager) =>
{
    if (await manager.IsEnabledAsync("featureAlwaysOn"))
    {
        return TypedResults.Ok($"featureAlwaysOn is enabled !");
    }
    return TypedResults.NotFound();
}).WithSummary("featureAlwaysOn");

調用介面查看測試結果,返回 200,符合預期

image


TimeWindow 過濾器

  "FeatureManagement": {
    "featureTimeWindow": {
      "EnabledFor": [
        {
          "Name": "TimeWindow",
          "Parameters": {
            "Start": "2024-03-26 13:30:00",
            "End": "2024-03-27 13:30:00"
          }
        }
      ]
    }
  }

指定了一個名為 TimeWindow 的功能過濾器。這是一個可配置的功能過濾,具有 Parameters 屬性,配置了功能活動的開始和結束時間 。

app.MapGet("/featureTimeWindow", async Task<IResult> (IFeatureManager manager) =>
{
    if (await manager.IsEnabledAsync("featureTimeWindow"))
    {
        return TypedResults.Ok($"featureTimeWindow is enabled !");
    }
    return TypedResults.NotFound();
}).WithSummary("TimeWindow 過濾器測試");

調用介面測試:返回 200 符合預期

image


Percentage 過濾器
百分比過濾器(Percentage Filter)它根據指定的百分比值隨機啟用或禁用某個特性。這種過濾器允許您控制特性的曝光率,以便在不同的用戶群體中測試特性的效果,或者在逐步推廣新特性時控制其影響範圍。

  "FeatureManagement": {
    "featurePercentage": {
      "EnabledFor": [
        {
          "Name": "Percentage",
          "Parameters": {
            "Value": "50"
          }
        }
      ]
    }
  },

app.MapGet("/featurePercentage", async Task<IResult> (IFeatureManager manager) =>
{
    if (await manager.IsEnabledAsync("featurePercentage"))
    {
        return TypedResults.Ok($"featurePercentage is enabled !");
    }
    return TypedResults.NotFound();
}).WithSummary("Percentage 過濾器測試");

連續測兩次

第一次測試結果: 返回 200
image

第二次測試結果:返回 404
image

通過測試結果可以看出有百分之五十的幾率成功,符合預期。

RequirementType

功能標誌的 RequirementType 屬性用於確定在評估功能狀態時,過濾器應該使用任何(Any)還是全部(All)邏輯。如果未指定 RequirementType,則預設值為 Any

  • Any 表示只需一個過濾器評估為 true,特性就會被啟用。
  • All 表示每個過濾器都必須評估為 true,特性才會被啟用。
    RequirementTypeAll 會改變遍歷方式。首先,如果沒有過濾器,則功能將被禁用。然後,遍歷特性過濾器,直到其中一個過濾器決定應將功能禁用。如果沒有過濾器指示應禁用功能,則該功能將被視為已啟用。
  "FeatureManagement": {
    "featureRequirementTypeAll": {
      "RequirementType": "All",
      "EnabledFor": [
        {
          "Name": "TimeWindow",
          "Parameters": {
            "Start": "2024-03-27 13:00:00",
            "End": "2024-05-01 13:00:00"
          }
        },
        {
          "Name": "Percentage",
          "Parameters": {
            "Value": "50"
          }
        }
      ]
    }
  },
app.MapGet("/featureRequirementTypeAll", async Task<IResult> (IFeatureManager manager) =>
{
    if (await manager.IsEnabledAsync("featureRequirementTypeAll"))
    {
        return TypedResults.Ok($"featureRequirementTypeAll is enabled !");
    }
    return TypedResults.NotFound();
}).WithSummary("RequirementTypeAll 多過濾器測試");

上面的實例設置為 all 之後此功能標誌的過濾器列表必須全部符合要求才能調用成功。

比如上面我設置的開始日期是2024-03-27 13:00:00當前時間小於這個日期
image

無論調用幾次還是還是 404,結果符合我們的預期。

自定義過濾器

要實現一個功能過濾器,必須要實現的是一個IFeatureFilter介面,介面包含了一個EvaluateAsync的方法。當功能標誌指定啟用該過濾器時,將調用 EvaluateAsync方法,如果方法返回的是true,則表示應該啟用功能。

定義一個中間件介面只對某個用戶組做開放,這個場景在 C 端的產品上比較常見,比如說部分功能的內測。

[FilterAlias("AuthenticatedGroup")]
public class AuthenticatedGroupFilter : IFeatureFilter, IFeatureFilterMetadata, IFilterParametersBinder
{
    public object BindParameters(IConfiguration parameters)
    {
        return parameters.Get<GroupSetting>() ?? new GroupSetting();
    }

    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext featureFilterContext)
    {
        GroupSetting filterSettings = ((GroupSetting)featureFilterContext.Settings) ?? ((GroupSetting)BindParameters(featureFilterContext.Parameters));
        // 假設您有一個方法來檢查用戶是否已通過身份驗證
        // 例如,這可能是一個從身份驗證服務或中間件中獲得的屬性或方法
        bool isAuthenticated = IsGroupAuthenticated(filterSettings);
        return Task.FromResult(isAuthenticated);
    }


    private bool IsGroupAuthenticated(GroupSetting groupSetting)
    {
        // 在這裡編寫您的身份驗證檢查邏輯
        // 這可能涉及到檢查HTTP請求的上下文、會話狀態、令牌等
        // 具體的實現將取決於您使用的身份驗證機制

        // 示例:返回一個硬編碼的值,表示用戶是否已通過身份驗證
        // 在實際應用中,您應該實現實際的檢查邏輯
        return true; // 或者 false,取決於用戶是否已通過身份驗證
    }
}

FilterAlias是定義過濾器的別名,我們在配置文件中指定時需要用別名,IFeatureFilter介面返回的信息決定功能是否啟用,IFeatureFilterMetadata是一個空的標記介面,用於評估功能狀態的特征過濾器的標記介面,IFilterParametersBinder 介面用於參數綁定。

  • json 配置
  "FeatureManagement": {
    "featureAuthencatedGroup": {
      "EnabledFor": [
        {
          "Name": "AuthenticatedGroup",
          "Parameters": {
            "Groups": [ "AdminGroup", "GroupOne" ]
          }
        }
      ]
    }
  }
  • 依賴註入
services.AddFeatureManagement()
    .AddFeatureFilter<AuthenticatedGroupFilter>();

調用 AddFeatureFilter 方法可把自定義的過濾器註冊到功能管理器中。

app.MapGet("/featureAuthencatedGroup", async Task<IResult> (IFeatureManager manager) =>
{
    if (await manager.IsEnabledAsync("featureAuthencatedGroup"))
    {
        return TypedResults.Ok($"featureAuthencatedGroup is enabled !");
    }
    return TypedResults.NotFound();
}).WithSummary("AuthencatedGroup 自定義過濾器測試");

測試一下,返回 200 ,符合預期
image

一個小 tips;如果多個過濾器有同一個別名是,可以用命名空間加別名的方式來定義唯一一個過濾器,例如,Microsoft.Percentage 是一個完全限定的別名,它明確指出了 Percentage過濾器位於 Microsoft 命名空間下

自定義開啟中間件

  "FeatureManagement": {
    "featureMiddleWare": {
      "EnabledFor": [
        {
          "Name": "Percentage",
          "Parameters": {
            "Value": "50"
          }
        }
      ]
    }
  }

自定義中間件

public class FeatureMiddleWare(RequestDelegate next)
{
    public async Task Invoke(HttpContext context)
    {
        Console.WriteLine("FeatureMiddleWare管道執行之前~");
        await next(context);
        Console.WriteLine("FeatureMiddleWare管道執行之後~");
    }
}

添加擴展方法

//測試中間件的功能開啟
app.UseMiddlewareForFeature<FeatureMiddleWare>("featureMiddleWare");

隨便調用一個介面測試一下,可以看到管道根據百分比觸發成功
image

通過上述調用,應用程式添加了一個中間件組件,只有在特性“featureMiddleWare”被啟用時才會出現在請求管道中。如果在運行時啟用/禁用特性,中間件管道可以動態更改。

這是建立在基於特性對整個應用程式進行分支的更通用能力之上。

app.UseForFeature(featureName, appBuilder =>
{
appBuilder.UseMiddleware<T>();
});

MinimalApis 集成

在我們的 MVC 或者 Razor Pages 中有如下方案來啟用功能的開關,不過多介紹大家可以官方瀏覽學習。

FeatureManagement-Dotnet

services.AddMvc(o =>
{
    o.Filters.AddForFeature<SomeMvcFilter>("FeatureX");
});
[FeatureGate("FeatureX")]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

MinimalAps 中可以利用 endpoint filter來簡化公功能的開關,

  • 第一步創建最小 Api 的基類,所有的 MinimalApis 過濾器都要繼承它
public abstract class FeatureFlagEndpointFilter(IFeatureManager featureManager) : IEndpointFilter
{
    protected abstract string FeatureFlag { get; }

    private readonly IFeatureManager _featureManager = featureManager;

    public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var isEnabled = await _featureManager.IsEnabledAsync(FeatureFlag);
        if (!isEnabled)
        {
            return TypedResults.NotFound();
        }
        return await next(context);
    }
}
  • 定義目標 Json 配置
  "FeatureManagement": {
    "featureUserApi": {
      "EnabledFor": [
        {
          "Name": "Percentage",
          "Parameters": {
            "Value": "50"
          }
        }
      ]
    }
  • 定義最小 Api 過濾器
public class UserApiFeatureFilter(IFeatureManager featureManager) : FeatureFlagEndpointFilter(featureManager)
{
    protected override string FeatureFlag => "featureUserApi";
}

  • 定義 Api 介面測試
//最小Api分組功能添加
{
    var userGroup = app.MapGroup("User").WithTags("User").AddEndpointFilter<UserApiFeatureFilter>(); ;

    userGroup.MapGet("/featureUserApi", IResult (IFeatureManager manager) =>
    {
        return TypedResults.Ok($"featureUserApi is enabled !");

    }).WithSummary("featureUserApi 最小Api過濾器測試");
}

調用測試,可以看出我們配置的百分比過濾器成功。

image

通過對 IEndpointFilter 的封裝藉助最小 ApiMapGroup 可以對一組相關的 Api 進行功能管理,簡化了我們一個個 Api 註冊。

最後

在本文中,我們深入探討了.NET Feature Management 庫的安裝、配置和使用方法,以及如何利用功能開關來動態管理應用程式的功能。以下是關鍵點的總結和提煉:

  • 安裝與依賴註入:通過.NET CLINuGet Package Manager 安裝等方式 Microsoft.FeatureManagement.AspNetCore 庫,併在應用程式中添加功能管理服務的依賴註入。

  • 功能定義與配置:通過.NET 的配置系統,在 appsettings.json 中定義功能標誌,指定功能的啟用和禁用狀態,以及可選的功能過濾器配置。

  • 自定義功能過濾器:實現 IFeatureFilter 介面來定義自定義功能過濾器,根據特定條件決定功能是否啟用,例如基於用戶組、時間視窗或百分比等條件。

  • 功能開關的使用:利用 IFeatureManagerIsEnabledAsync 方法檢查功能是否啟用,根據不同的功能狀態執行相應的邏輯,實現功能的動態控制。

  • RequirementType 設置:可以通過 RequirementType 屬性指定功能過濾器的邏輯要求,是 Any 還是 All,決定多個過濾器的組合邏輯。

  • 自定義中間件的動態切換:通過自定義功能過濾器和中間件,可以根據功能狀態動態調整請求管道,實現功能開關對中間件的控制。

  • 最小 API 集成:在 Minimal APIs 中,利用 IEndpointFilter 介面來簡化功能開關的應用,將功能管理應用到最小 API 的端點上,實現對一組相關 API 的功能管理。

通過以上總結和提煉,您可以更好地瞭解和應用.NET Feature Management 庫,實現靈活的功能管理和動態控制應用程式的功能。

有條件的富哥可以體驗一下在 Azure 應用程式配置中管理功能標誌

更多詳細的內容請瀏覽FeatureManagement-Dotnet

本文測試完整源代碼

本文來自博客園,作者:董瑞鵬,轉載請註明原文鏈接:https://www.cnblogs.com/ruipeng/p/18098211


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

-Advertisement-
Play Games
更多相關文章
  • 本文介紹瞭如何快速搭建一個基於大型語言模型(LLM)的混元聊天應用。強調了開發速度的重要性,並指出了使用Streamlit這一工具的優勢,特別是對於不熟悉前端代碼的開發者來說,Streamlit提供了一種快速構建聊天應用的方法。 ...
  • 前言 aardio中有些經常使用的庫,換個項目總需要複製一下,還不便於修改。雖然可以直接把它放到aardio\lib目錄下,也是不便於共用給其他人使用。 最近偶然翻到編輯器里的工具->開發環境->擴展庫發佈工具,就想著可以像官方一樣,發佈自己的擴展庫,也便於分享給大家使用,最好能像官方擴展庫一樣線上 ...
  • 隨著汽車的普及和使用頻率的增加,車輛的維修保養成為了車主們經常需要面對的問題。為了提供更好的服務,挖數據平臺提供了一個維修保養記錄統計介面,讓用戶可以方便地查詢車輛的保養記錄和維修記錄。本文將對該介面進行詳細解析,並介紹其使用方法和應用場景。 首先,我們來看一下該介面的具體功能。該介面可以查詢車輛的 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...