動手寫一個簡版 asp.net core

来源:https://www.cnblogs.com/weihanli/archive/2020/05/22/mini-aspnetcore.html

動手寫一個簡版 asp.net core Intro 之前看到過蔣金楠老師的一篇 200 行代碼帶你瞭解 asp.net core 框架,最近參考蔣老師和 Edison 的文章和代碼,結合自己對 asp.net core 的理解 ,最近自己寫了一個 MiniAspNetCore ,寫篇文章總結一下。 ...


動手寫一個簡版 asp.net core

Intro

之前看到過蔣金楠老師的一篇 200 行代碼帶你瞭解 asp.net core 框架,最近參考蔣老師和 Edison 的文章和代碼,結合自己對 asp.net core 的理解 ,最近自己寫了一個 MiniAspNetCore ,寫篇文章總結一下。

HttpContext

HttpContext 可能是最為常用的一個類了,HttpContext 是請求上下文,包含了所有的請求信息以及響應信息,以及一些自定義的用於在不同中間件中傳輸數據的信息

來看一下 HttpContext 的定義:

public class HttpContext
{
    public IServiceProvider RequestServices { get; set; }

    public HttpRequest Request { get; set; }

    public HttpResponse Response { get; set; }

    public IFeatureCollection Features { get; set; }

    public HttpContext(IFeatureCollection featureCollection)
    {
        Features = featureCollection;
        Request = new HttpRequest(featureCollection);
        Response = new HttpResponse(featureCollection);
    }
}

HttpRequest 即為請求信息對象,包含了所有請求相關的信息,

HttpResponse 為響應信息對象,包含了請求對應的響應信息

RequestServices 為 asp.net core 里的RequestServices,代表當前請求的服務提供者,可以使用它來獲取具體的服務實例

Features 為 asp.net core 里引入的對象,可以用來在不同中間件中傳遞信息和用來解耦合

,下麵我們就來看下 HttpRequestHttpResponse 是怎麼實現的

HttpRequest:

public class HttpRequest
{
    private readonly IRequestFeature _requestFeature;

    public HttpRequest(IFeatureCollection featureCollection)
    {
        _requestFeature = featureCollection.Get<IRequestFeature>();
    }

    public Uri Url => _requestFeature.Url;

    public NameValueCollection Headers => _requestFeature.Headers;

    public string Method => _requestFeature.Method;

    public string Host => _requestFeature.Url.Host;

    public Stream Body => _requestFeature.Body;
}

HttpResponse:

public class HttpResponse
{
    private readonly IResponseFeature _responseFeature;

    public HttpResponse(IFeatureCollection featureCollection)
    {
        _responseFeature = featureCollection.Get<IResponseFeature>();
    }

    public bool ResponseStarted => _responseFeature.Body.Length > 0;

    public int StatusCode
    {
        get => _responseFeature.StatusCode;
        set => _responseFeature.StatusCode = value;
    }

    public async Task WriteAsync(byte[] responseBytes)
    {
        if (_responseFeature.StatusCode <= 0)
        {
            _responseFeature.StatusCode = 200;
        }
        if (responseBytes != null && responseBytes.Length > 0)
        {
            await _responseFeature.Body.WriteAsync(responseBytes);
        }
    }
}

Features

上面我們提供我們可以使用 Features 在不同中間件中傳遞信息和解耦合

由上面 HttpRequest/HttpResponse 的代碼我們可以看出來,HttpRequestHttpResponse 其實就是在 IRequestFeatureIResponseFeature 的基礎上封裝了一層,真正的核心其實是 IRequestFeature/IResponseFeature ,而這裡使用介面就很好的實現瞭解耦,可以根據不同的 WebServer 使用不同的 RequestFeature/ResponseFeature,來看下 IRequestFeature/IResponseFeature 的實現

public interface IRequestFeature
{
    Uri Url { get; }

    string Method { get; }

    NameValueCollection Headers { get; }

    Stream Body { get; }
}

public interface IResponseFeature
{
    public int StatusCode { get; set; }

    NameValueCollection Headers { get; set; }

    public Stream Body { get; }
}

這裡的實現和 asp.net core 的實際的實現方式應該不同,asp.net core 里 Headers 同一個 Header 允許有多個值,asp.net core 里是 StringValues 來實現的,這裡簡單處理了,使用了一個 NameValueCollection 對象

上面提到的 Features 是一個 IFeatureCollection 對象,相當於是一系列的 Feature 對象組成的,來看下 FeatureCollection 的定義:

public interface IFeatureCollection : IDictionary<Type, object> { }

public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection
{
}

這裡 IFeatureCollection 直接實現 IDictionary<Type, object> ,通過一個字典 Feature 類型為 Key,Feature 對象為 Value 的字典來保存

為了方便使用,可以定義兩個擴展方法來方便的Get/Set

public static class FeatureExtensions
{
    public static IFeatureCollection Set<TFeature>(this IFeatureCollection featureCollection, TFeature feature)
    {
        featureCollection[typeof(TFeature)] = feature;
        return featureCollection;
    }

    public static TFeature Get<TFeature>(this IFeatureCollection featureCollection)
    {
        var featureType = typeof(TFeature);
        return featureCollection.ContainsKey(featureType) ? (TFeature)featureCollection[featureType] : default(TFeature);
    }
}

Web伺服器

上面我們已經提到了 Web 伺服器通過 IRequestFeature/IResponseFeature 來實現不同 web 伺服器和應用程式的解耦,web 伺服器只需要提供自己的 RequestFeature/ResponseFeature 即可

為了抽象不同的 Web 伺服器,我們需要定義一個 IServer 的抽象介面,定義如下:

public interface IServer
{
    Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default);
}

IServer 定義了一個 StartAsync 方法,用來啟動 Web伺服器,

StartAsync 方法有兩個參數,一個是 requestHandler,是一個用來處理請求的委托,另一個是取消令牌用來停止 web 伺服器

示例使用了 HttpListener 來實現了一個簡單 Web 伺服器,HttpListenerServer 定義如下:

public class HttpListenerServer : IServer
{
    private readonly HttpListener _listener;
    private readonly IServiceProvider _serviceProvider;

    public HttpListenerServer(IServiceProvider serviceProvider, IConfiguration configuration)
    {
        _listener = new HttpListener();
        var urls = configuration.GetAppSetting("ASPNETCORE_URLS")?.Split(';');
        if (urls != null && urls.Length > 0)
        {
            foreach (var url in urls
                     .Where(u => u.IsNotNullOrEmpty())
                     .Select(u => u.Trim())
                     .Distinct()
                    )
            {
                // Prefixes must end in a forward slash ("/")
                // https://stackoverflow.com/questions/26157475/use-of-httplistener
                _listener.Prefixes.Add(url.EndsWith("/") ? url : $"{url}/");
            }
        }
        else
        {
            _listener.Prefixes.Add("http://localhost:5100/");
        }

        _serviceProvider = serviceProvider;
    }

    public async Task StartAsync(Func<HttpContext, Task> requestHandler, CancellationToken cancellationToken = default)
    {
        _listener.Start();
        if (_listener.IsListening)
        {
            Console.WriteLine("the server is listening on ");
            Console.WriteLine(_listener.Prefixes.StringJoin(","));
        }
        while (!cancellationToken.IsCancellationRequested)
        {
            var listenerContext = await _listener.GetContextAsync();

            var featureCollection = new FeatureCollection();
            featureCollection.Set(listenerContext.GetRequestFeature());
            featureCollection.Set(listenerContext.GetResponseFeature());

            using (var scope = _serviceProvider.CreateScope())
            {
                var httpContext = new HttpContext(featureCollection)
                {
                    RequestServices = scope.ServiceProvider,
                };

                await requestHandler(httpContext);
            }
            listenerContext.Response.Close();
        }
        _listener.Stop();
    }
}

HttpListenerServer 實現的 RequestFeature/ResponseFeatue

public class HttpListenerRequestFeature : IRequestFeature
{
    private readonly HttpListenerRequest _request;

    public HttpListenerRequestFeature(HttpListenerContext listenerContext)
    {
        _request = listenerContext.Request;
    }

    public Uri Url => _request.Url;
    public string Method => _request.HttpMethod;
    public NameValueCollection Headers => _request.Headers;
    public Stream Body => _request.InputStream;
}

public class HttpListenerResponseFeature : IResponseFeature
{
    private readonly HttpListenerResponse _response;

    public HttpListenerResponseFeature(HttpListenerContext httpListenerContext)
    {
        _response = httpListenerContext.Response;
    }

    public int StatusCode { get => _response.StatusCode; set => _response.StatusCode = value; }

    public NameValueCollection Headers
    {
        get => _response.Headers;
        set
        {
            _response.Headers = new WebHeaderCollection();
            foreach (var key in value.AllKeys)
                _response.Headers.Add(key, value[key]);
        }
    }

    public Stream Body => _response.OutputStream;
}

為了方便使用,為 HttpListenerContext 定義了兩個擴展方法,就是上面 HttpListenerServer 中的 GetRequestFeature/GetResponseFeature

public static class HttpListenerContextExtensions
{
    public static IRequestFeature GetRequestFeature(this HttpListenerContext context)
    {
        return new HttpListenerRequestFeature(context);
    }

    public static IResponseFeature GetResponseFeature(this HttpListenerContext context)
    {
        return new HttpListenerResponseFeature(context);
    }
}

RequestDelegate

在上面的 IServer 定義里有一個 requestHandler 的 對象,在 asp.net core 里是一個名稱為 RequestDelegate 的對象,而用來構建這個委托的在 asp.net core 里是 IApplicationBuilder,這些在蔣老師和 Edison 的文章和代碼里都可以看到,這裡我們只是簡單介紹下,我在 MiniAspNetCore 的示例中沒有使用這些對象,而是使用了自己抽象的 PipelineBuilder 和原始委托實現的

asp.net core 里 RequestDelegate 定義:

public delegate Task RequestDelegate(HttpContext context);

其實和我們上面定義用的 Func<HttpContext, Task> 是等價的

IApplicationBuilder 定義:

/// <summary>
/// Defines a class that provides the mechanisms to configure an application's request pipeline.
/// </summary>
public interface IApplicationBuilder
{
    /// <summary>
    /// Gets or sets the <see cref="T:System.IServiceProvider" /> that provides access to the application's service container.
    /// </summary>
    IServiceProvider ApplicationServices { get; set; }

    /// <summary>
    /// Gets the set of HTTP features the application's server provides.
    /// </summary>
    IFeatureCollection ServerFeatures { get; }

    /// <summary>
    /// Gets a key/value collection that can be used to share data between middleware.
    /// </summary>
    IDictionary<string, object> Properties { get; }

    /// <summary>
    /// Adds a middleware delegate to the application's request pipeline.
    /// </summary>
    /// <param name="middleware">The middleware delegate.</param>
    /// <returns>The <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns>
    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

    /// <summary>
    /// Creates a new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" /> that shares the <see cref="P:Microsoft.AspNetCore.Builder.IApplicationBuilder.Properties" /> of this
    /// <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.
    /// </summary>
    /// <returns>The new <see cref="T:Microsoft.AspNetCore.Builder.IApplicationBuilder" />.</returns>
    IApplicationBuilder New();

    /// <summary>
    /// Builds the delegate used by this application to process HTTP requests.
    /// </summary>
    /// <returns>The request handling delegate.</returns>
    RequestDelegate Build();
}

我們這裡沒有定義 IApplicationBuilder,使用了簡化抽象的 IAsyncPipelineBuilder,定義如下:

public interface IAsyncPipelineBuilder<TContext>
{
    IAsyncPipelineBuilder<TContext> Use(Func<Func<TContext, Task>, Func<TContext, Task>> middleware);

    Func<TContext, Task> Build();

    IAsyncPipelineBuilder<TContext> New();
}

對於 asp.net core 的中間件來說 ,上面的 TContext 就是 HttpContext,替換之後也就是下麵這樣的:

public interface IAsyncPipelineBuilder<HttpContext>
{
    IAsyncPipelineBuilder<HttpContext> Use(Func<Func<HttpContext, Task>, Func<HttpContext, Task>> middleware);

    Func<HttpContext, Task> Build();

    IAsyncPipelineBuilder<HttpContext> New();
}

是不是和 IApplicationBuilder 很像,如果不像可以進一步把 Func<HttpContext, Task> 使用 RequestDelegate 替換

public interface IAsyncPipelineBuilder<HttpContext>
{
    IAsyncPipelineBuilder<HttpContext> Use(Func<RequestDelegate, RequestDelegate> middleware);

    RequestDelegate Build();

    IAsyncPipelineBuilder<HttpContext> New();
}

最後再將介面名稱替換一下:

public interface IApplicationBuilder1
{
    IApplicationBuilder1 Use(Func<RequestDelegate, RequestDelegate> middleware);

    RequestDelegate Build();

    IApplicationBuilder1 New();
}

至此,就完全可以看出來了,這 IAsyncPipelineBuilder<HttpContext> 就是一個簡版的 IApplicationBuilder

IAsyncPipelineBuilderIApplicationBuilder 的作用是將註冊的多個中間件構建成一個請求處理的委托

中間件處理流程:

更多關於 PipelineBuilder 構建中間件的信息可以查看 讓 .NET 輕鬆構建中間件模式代碼 瞭解更多

WebHost

通過除了 Web 伺服器之外,還有一個 Web Host 的概念,可以簡單的這樣理解,一個 Web 伺服器上可以有多個 Web Host,就像 IIS/nginx (Web Server) 可以 host 多個站點

可以說 WebHost 離我們的應用更近,所以我們還需要 IHost 來托管應用

public interface IHost
{
    Task RunAsync(CancellationToken cancellationToken = default);
}

WebHost 定義:

public class WebHost : IHost
{
    private readonly Func<HttpContext, Task> _requestDelegate;
    private readonly IServer _server;

    public WebHost(IServiceProvider serviceProvider, Func<HttpContext, Task> requestDelegate)
    {
        _requestDelegate = requestDelegate;
        _server = serviceProvider.GetRequiredService<IServer>();
    }

    public async Task RunAsync(CancellationToken cancellationToken = default)
    {
        await _server.StartAsync(_requestDelegate, cancellationToken).ConfigureAwait(false);
    }
}

為了方便的構建 Host對象,引入了 HostBuilder 來方便的構建一個 Host,定義如下:

public interface IHostBuilder
{
    IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction);

    IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction);

    IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction);

    IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction);

    IHost Build();
}

WebHostBuilder

public class WebHostBuilder : IHostBuilder
{
    private readonly IConfigurationBuilder _configurationBuilder = new ConfigurationBuilder();
    private readonly IServiceCollection _serviceCollection = new ServiceCollection();

    private Action<IConfiguration, IServiceProvider> _initAction = null;

    private readonly IAsyncPipelineBuilder<HttpContext> _requestPipeline = PipelineBuilder.CreateAsync<HttpContext>(context =>
    {
        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    });

    public IHostBuilder ConfigureConfiguration(Action<IConfigurationBuilder> configAction)
    {
        configAction?.Invoke(_configurationBuilder);
        return this;
    }

    public IHostBuilder ConfigureServices(Action<IConfiguration, IServiceCollection> configureAction)
    {
        if (null != configureAction)
        {
            var configuration = _configurationBuilder.Build();
            configureAction.Invoke(configuration, _serviceCollection);
        }

        return this;
    }

    public IHostBuilder ConfigureApplication(Action<IConfiguration, IAsyncPipelineBuilder<HttpContext>> configureAction)
    {
        if (null != configureAction)
        {
            var configuration = _configurationBuilder.Build();
            configureAction.Invoke(configuration, _requestPipeline);
        }
        return this;
    }

    public IHostBuilder Initialize(Action<IConfiguration, IServiceProvider> initAction)
    {
        if (null != initAction)
        {
            _initAction = initAction;
        }

        return this;
    }

    public IHost Build()
    {
        var configuration = _configurationBuilder.Build();
        _serviceCollection.AddSingleton<IConfiguration>(configuration);
        var serviceProvider = _serviceCollection.BuildServiceProvider();

        _initAction?.Invoke(configuration, serviceProvider);

        return new WebHost(serviceProvider, _requestPipeline.Build());
    }

    public static WebHostBuilder CreateDefault(string[] args)
    {
        var webHostBuilder = new WebHostBuilder();
        webHostBuilder
            .ConfigureConfiguration(builder => builder.AddJsonFile("appsettings.json", true, true))
            .UseHttpListenerServer()
            ;

        return webHostBuilder;
    }
}

這裡的示例我在 IHostBuilder 里增加了一個 Initialize 的方法來做一些初始化的操作,我覺得有些數據初始化配置初始化等操作應該在這裡操作,而不應該在 StartupConfigure 方法里處理,這樣 Configure 方法可以更純粹一些,只配置 asp.net core 的請求管道,這純屬個人意見,沒有對錯之分

這裡 Host 的實現和 asp.net core 的實現不同,有需要的可以深究源碼,在 asp.net core 2.x 的版本里是有一個 IWebHost 的,在 asp.net core 3.x 以及 .net 5 里是沒有 IWebHost 的取而代之的是通用主機 IHost, 通過實現了一個 IHostedService 來實現 WebHost

Run

運行示例代碼:

public class Program
{
    private static readonly CancellationTokenSource Cts = new CancellationTokenSource();

    public static async Task Main(string[] args)
    {
        Console.CancelKeyPress += OnExit;

        var host = WebHostBuilder.CreateDefault(args)
            .ConfigureServices((configuration, services) =>
            {
            })
            .ConfigureApplication((configuration, app) =>
            {
                app.When(context => context.Request.Url.PathAndQuery.StartsWith("/favicon.ico"), pipeline => { });

                app.When(context => context.Request.Url.PathAndQuery.Contains("test"),
                    p => { p.Run(context => context.Response.WriteAsync("test")); });
                app
                    .Use(async (context, next) =>
                    {
                        await context.Response.WriteLineAsync($"middleware1, requestPath:{context.Request.Url.AbsolutePath}");
                        await next();
                    })
                    .Use(async (context, next) =>
                    {
                        await context.Response.WriteLineAsync($"middleware2, requestPath:{context.Request.Url.AbsolutePath}");
                        await next();
                    })
                    .Use(async (context, next) =>
                    {
                        await context.Response.WriteLineAsync($"middleware3, requestPath:{context.Request.Url.AbsolutePath}");
                        await next();
                    })
                    ;
                app.Run(context => context.Response.WriteAsync("Hello Mini Asp.Net Core"));
            })
            .Initialize((configuration, services) =>
            {
            })
            .Build();
        await host.RunAsync(Cts.Token);
    }

    private static void OnExit(object sender, EventArgs e)
    {
        Console.WriteLine("exiting ...");
        Cts.Cancel();
    }
}

在示例項目目錄下執行 dotnet run,並訪問 http://localhost:5100/:

仔細觀察瀏覽器 consolenetwork 的話,會發現還有一個請求,瀏覽器會預設請求 /favicon.ico 獲取網站的圖標

因為我們針對這個請求沒有任何中間件的處理,所以直接返回了 404

在訪問 /test,可以看到和剛纔的輸出完全不同,因為這個請求走了另外一個分支,相當於 asp.net core 里 Map/MapWhen 的效果,另外 Run 代表裡中間件的中斷,不會執行後續的中間件

More

上面的實現只是我在嘗試寫一個簡版的 asp.net core 框架時的實現,和 asp.net core 的實現並不完全一樣,如果需要請參考源碼,上面的實現僅供參考,上面實現的源碼可以在 Github 上獲取 https://github.com/WeihanLi/SamplesInPractice/tree/master/MiniAspNetCore

asp.net core 源碼:https://github.com/dotnet/aspnetcore

Reference


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

更多相關文章
  • 環境 雖說就發郵件這麼個小事,很容易相容Python2, Python3, 但是大家還是擁抱Python3吧, 我這裡沒有做python2的相容寫法,所以需要python3以上。 很多人學習python,不知道從何學起。很多人學習python,掌握了基本語法過後,不知道在哪裡尋找案例上手。很多已經做 ...
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 面試題55 I. 二叉樹的深度 與以下題目相同 前往:Leet ...
  • 我的LeetCode:https://leetcode cn.com/u/ituring/ 我的LeetCode刷題源碼[GitHub]:https://github.com/izhoujie/Algorithmcii LeetCode 104. 二叉樹的最大深度 題目 給定一個二叉樹,找出其最大深 ...
  • 0. 前言 前言,暫時揮別NHibernate(雖然我突然發現這玩意還挺有意思的,不過看得人不多)。大步進入了有很多小伙伴向我安利的SQLSugar,嗯,我一直叫SugarSQL,好像是這個吧? 這是一個由國內開發者開發的ORM框架,是一個輕量級框架(最新版的sqlSugarCore大概只有290k ...
  • 需求:資料庫數據都是縱向的,呈現的時候要求是橫向(列轉行)同時看到行頭(行轉列)。 分析:很多報表呈現都要求要這樣顯示,用類是可以實現,但是代碼多,又需要建很多dto。發下Excel有轉置功能,但又不想牽扯Excel這一套組件。就使用DataTable來實現,代碼也不多。 先看看示例數據3列10行: ...
  • 同步版本示例: namespace SyncSample { class MyDownloadString { Stopwatch sw = new Stopwatch(); public void DoRun() { const int LargeNumber = 6000000; sw.Star ...
  • 在Linux上搭建基於開源技術的nuget私人保密倉庫 前言 在Linux上搭建nuget私人倉庫一直是一個老大難的問題,主要涉及到以下難點: nuget.org官方使用的Nuget.Server基於.NET Framework的ASP.NET,而不是ASP.NET Core,因此是Windows ...
  • 網上有很多Socket框架,但是我想,C#既然有Socket類,難道不是給人用的嗎? 寫了一個SocketServerHelper和SocketClientHelper,分別隻有5、6百行代碼,比不上大神寫的,和業務代碼耦合也比較重,但對新手非常友好,容易看懂。 支持返回值或回調,支持不定長度的數據 ...
一周排行
  • 比如要拆分“呵呵呵90909086676喝喝999”,下麵當type=0返回的是中文字元串“呵呵呵,喝喝”,type=1返回的是數字字元串“90909086676,999”, private string GetStrings(string str,int type=0) { IList<strin ...
  • Swagger一個優秀的Api介面文檔生成工具。Swagger可以可以動態生成Api介面文檔,有效的降低前後端人員關於Api介面的溝通成本,促進項目高效開發。 1、使用NuGet安裝最新的包:Swashbuckle.AspNetCore。 2、編輯項目文件(NetCoreTemplate.Web.c ...
  • 2020 年 7 月 30 日, 由.NET基金會和微軟 將舉辦一個線上和為期一天的活動,包括 微軟 .NET 團隊的演講者以及社區的演講者。本次線上大會 專註.NET框架構建微服務,演講者分享構建和部署雲原生應用程式的最佳實踐、模式、提示和技巧。有關更多信息和隨時瞭解情況:https://focu... ...
  • #abp框架Excel導出——基於vue #1.技術棧 ##1.1 前端採用vue,官方提供 UI套件用的是iview ##1.2 後臺是abp——aspnetboilerplate 即abp v1,https://github.com/aspnetboilerplate/aspnetboilerp ...
  • 前言 本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理。 作者:碧茂大數據 PS:如有需要Python學習資料的小伙伴可以加下方的群去找免費管理員領取 input()輸入 Python提供了 input() 內置函數從標準輸入讀入一 ...
  • 從12年到20年,python以肉眼可見的趨勢超過了java,成為了當今It界人人皆知的編程語言。 python為什麼這麼火? 網路編程語言搜索指數 適合初學者 Python具有語法簡單、語句清晰的特點,這就讓初學者在學習階段可以把精力集中在編程對象和思維方法上。 大佬都在用 Google,YouT ...
  • 在社會上存在一種普遍的對培訓機構的學生一種歧視的現象,具體表現在,比如:當你去公司面試的時候,一旦你說了你是培訓機構出來的,那麼基本上你就涼了,那麼你瞞著不說,然後又通過了面試成功入職,但是以後一旦在公司被髮現有培訓經歷,可能會面臨被降薪,甚至被辭退,培訓機構出來的學生,在用人單位眼裡就是能力低下的 ...
  • from typing import List# 這道題看了大佬寫的代碼,經過自己的理解寫出來了。# 從最外圍的四周找有沒有為O的,如果有的話就進入深搜函數,然後深搜遍歷# 判斷上下左右的位置是否為Oclass Solution: def solve(self, board: List[List[s ...
  • import requests; import re; import os; # 1.請求網頁 header = { "user-agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, li ...
  • import requests; import re; import os; import parsel; 1.請求網頁 header = { "user-agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537. ...