前言
我正在寫FastGithub這個小麻雀項目,里面主要涉及了Pipeline模式和Factory+Provider模式,這兩種設計模式,讓這個項目在"ip掃描"和"ip查找"兩個核心功能上如魚得水,在此分享給大家。
Pipeline
Pipeline模式也叫管道模式或流水線模式。通過預先設定好的一系列的階段來處理輸入的數據,每個階段的輸出即是下一個階段的輸入,每個階段可以選擇是否繼續執行一下階段。
上下文對象
在實現上,我們把所需的所有數據封裝在上下文對象,每個階段可以共享到同一份上下文對象。
中間件
在實現上,我們把每個階段的處理封裝為中間件,一個中間件可以訪問到上下文對象和下一個階段的處理對象,在執行時可以訪問或修改上下文對象的數據。
實現詳解
完整的Pipeline構建代碼,詳見https://github.com/xljiulang/FastGithub/tree/master/FastGithub.Core
階段處理對象
/// <summary>
/// 表示所有中間件執行委托
/// </summary>
/// <typeparam name="TContext">中間件上下文類型</typeparam>
/// <param name="context">中間件上下文</param>
/// <returns></returns>
public delegate Task InvokeDelegate<TContext>(TContext context);
委托中間件
Func<InvokeDelegate<TContext>, InvokeDelegate<TContext>>
為一個委托中間件,第一個InvokeDelegate<TContext>
表示傳入的下一個處理階段,第二個InvokeDelegate<TContext>
表示當前處理階段。
/// <summary>
/// 定義中間件管道創建者的接口
/// </summary>
/// <typeparam name="TContext">中間件上下文</typeparam>
public interface IPipelineBuilder<TContext>
{
/// <summary>
/// 使用中間件
/// </summary>
/// <param name="middleware">中間件</param>
/// <returns></returns>
IPipelineBuilder<TContext> Use(Func<InvokeDelegate<TContext>, InvokeDelegate<TContext>> middleware);
/// <summary>
/// 創建所有中間件執行處理者
/// </summary>
/// <returns></returns>
InvokeDelegate<TContext> Build();
}
強類型中間件
我們可以把委托中間件,轉換為如下的強類型中間件,InvokeAsync方法是本處理階段,next參數,是委托中間件的下個階段包裝。
/// <summary>
/// 定義中間件的接口
/// </summary>
/// <typeparam name="TContext"></typeparam>
public interface IMiddleware<TContext>
{
/// <summary>
/// 執行中間件
/// </summary>
/// <param name="context">上下文</param>
/// <param name="next">下一個中間件</param>
/// <returns></returns>
Task InvokeAsync(TContext context, Func<Task> next);
}
使用詳解
掃描任務分為完整掃描和歷史結果掃描,使用的中間件有點差異,但都是把需要的中間件串起來就可以了。
/// <summary>
/// github掃描服務
/// </summary>
/// <param name="domainAddressFactory"></param>
/// <param name="scanResults"></param>
/// <param name="appService"></param>
/// <param name="logger"></param>
public GithubScanService(
DomainAddressFacotry domainAddressFactory,
GithubContextCollection scanResults,
IServiceProvider appService,
ILogger<GithubScanService> logger)
{
this.domainAddressFactory = domainAddressFactory;
this.scanResults = scanResults;
this.logger = logger;
this.fullScanDelegate = new PipelineBuilder<GithubContext>(appService, ctx => Task.CompletedTask)
.Use<ConcurrentMiddleware>()
.Use<StatisticsMiddleware>()
.Use<TcpScanMiddleware>()
.Use<HttpsScanMiddleware>()
.Build();
this.resultScanDelegate = new PipelineBuilder<GithubContext>(appService, ctx => Task.CompletedTask)
.Use<StatisticsMiddleware>()
.Use<HttpsScanMiddleware>()
.Build();
}
Factory+Provider
多個Provider可以使用不同手段獲取到github的ip,Factory再把各Provider得到的ip進行整合,他們都是得到相同的功能:拿到github的ip,只是各個Provider才是具體干活的,而且Provider之間沒有任何有關系。
IDomainAddressProvider
/// <summary>
/// 定義域名的ip提值者
/// </summary>
interface IDomainAddressProvider
{
/// <summary>
/// 創建域名與ip的關系
/// </summary>
/// <returns></returns>
Task<IEnumerable<DomainAddress>> CreateDomainAddressesAsync();
}
DomainAddressFacotry
/// <summary>
/// 域名與ip關系工廠
/// </summary>
[Service(ServiceLifetime.Singleton)]
sealed class DomainAddressFacotry
{
private readonly IEnumerable<IDomainAddressProvider> providers;
/// <summary>
/// 域名與ip關系工廠
/// </summary>
/// <param name="providers"></param>
public DomainAddressFacotry(IEnumerable<IDomainAddressProvider> providers)
{
this.providers = providers;
}
/// <summary>
/// 創建域名與ip的關系
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<DomainAddress>> CreateDomainAddressesAsync()
{
var hashSet = new HashSet<DomainAddress>();
foreach (var provider in this.providers)
{
var domainAddresses = await provider.CreateDomainAddressesAsync();
foreach (var item in domainAddresses)
{
hashSet.Add(item);
}
}
return hashSet;
}
}
模式優勢分析
FastGithub同時使用了上述兩種模式,其工作流程很簡單:使用DomainAddressFacotry創建要掃描的ip,然后使用pipeline創建得到的掃描委托進行掃描即可。想得到更多的ip,增加一個DomainAddressProvider即可,不影響到其它任何代碼流程,想在掃描過程中做其它掃描邏輯,增加一個掃描中間件並安裝到合適位置即可。