• net Core 入门实战


    Asp.net Core 入门实战

     

    Asp.Net Core 是开源,跨平台,模块化,快速而简单的Web框架.

    Asp.net Core官网的一个源码合集,方便一次性Clone

    目录

    持续更新,也可以通过我的网站访问,欢迎探讨交流

    快速入门

    安装

    下载安装 .NET SDK

    查看dotnet sdk 版本

    $ dotnet --version`
    2.1.4

    创建项目目录

    $ mkdir study
    $ cd study

    使用 dotnet new 命令创建项目

    $ dotnet new console -n Demo
    $ cd Demo

    在 VS Code中打开Demo文件夹

    一个最小的应用

    打开 Program.cs

    using System;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    
    namespace Demo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var host = new WebHostBuilder()
                    .UseKestrel()
                    .UseStartup<Startup>()
                    .Build();
                host.Run();
            }
        }
        
        class Startup
        {
            public void Configure(IApplicationBuilder app)
            {
                app.Run(async context =>
                {
                    await context.Response.WriteAsync("Hello, World!");
                });
            }
        }
    }

    安装相关的Nuget包

    $ dotnet add package Microsoft.AspNetCore.Hosting --version 2.0.1
    $ dotnet add package Microsoft.AspNetCore.Server.Kestrel --version 2.0.1

    下载依赖项

    $ dotnet restore

    运行

    $ dotnet run

    输出结果:

    Hosting environment: Production
    Content root path: C:
    etcorestudyDemoinDebug
    etcoreapp2.0
    Now listening on: http://localhost:5000
    Application started. Press Ctrl+C to shut down.

    项目模板

    根据指定的模板,创建新的项目、配置文件或解决方案。

    $ dotnet new -h
    使用情况: new [选项]
    
    选项:
      -h, --help          显示有关此命令的帮助。
      -l, --list          列出包含指定名称的模板。如果未指定名称,请列出所有模板。
      -n, --name          正在创建输出的名称。如果未指定任何名称,将使用当前目录的名                                                                                                                                                                                               称。
      -o, --output        要放置生成的输出的位置。
      -i, --install       安装源或模板包。
      -u, --uninstall     卸载一个源或模板包。
      --type              基于可用的类型筛选模板。预定义的值为 "project"、"item" 或                                                                                                                                                                                                "other"。
      --force             强制生成内容,即使该内容会更改现有文件。
      -lang, --language   指定要创建的模板的语言。
    
    模板                                                短名称              语言                                                                                                                                                                                                               标记
    --------------------------------------------------------------------------------                                                                                                                                                                                               ------------------------
    Console Application                               console          [C#], F#, VB                                                                                                                                                                                                     Common/Console
    Class library                                     classlib         [C#], F#, VB                                                                                                                                                                                                     Common/Library
    Unit Test Project                                 mstest           [C#], F#, VB                                                                                                                                                                                                     Test/MSTest
    xUnit Test Project                                xunit            [C#], F#, VB                                                                                                                                                                                                     Test/xUnit
    ASP.NET Core Empty                                web              [C#], F#                                                                                                                                                                                                         Web/Empty
    ASP.NET Core Web App (Model-View-Controller)      mvc              [C#], F#                                                                                                                                                                                                         Web/MVC
    ASP.NET Core Web App                              razor            [C#]                                                                                                                                                                                                             Web/MVC/Razor Pages
    ASP.NET Core with Angular                         angular          [C#]                                                                                                                                                                                                             Web/MVC/SPA
    ASP.NET Core with React.js                        react            [C#]                                                                                                                                                                                                             Web/MVC/SPA
    ASP.NET Core with React.js and Redux              reactredux       [C#]                                                                                                                                                                                                             Web/MVC/SPA
    ASP.NET Core Web API                              webapi           [C#], F#                                                                                                                                                                                                         Web/WebAPI
    global.json file                                  globaljson                                                                                                                                                                                                                        Config
    NuGet Config                                      nugetconfig                                                                                                                                                                                                                       Config
    Web Config                                        webconfig                                                                                                                                                                                                                         Config
    Solution File                                     sln                                                                                                                                                                                                                               Solution
    Razor Page                                        page                                                                                                                                                                                                                              Web/ASP.NET
    MVC ViewImports                                   viewimports                                                                                                                                                                                                                       Web/ASP.NET
    MVC ViewStart                                     viewstart                                                                                                                                                                                                                         Web/ASP.NET
    
    Examples:
        dotnet new mvc --auth Individual
        dotnet new page --namespace
        dotnet new --help
    

    使用dotnet new 选择 console 模板 创建项目

    $ dotnet new console -n Demo
    $ cd Demo

    创建后的项目文档结构是

    .
    ├── Demo
    │   ├── Demo.csproj
    │   └── Program.cs

    路由

    启用,配置路由

    using System;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace Demo {
    
        class Startup {
            public void ConfigureServices (IServiceCollection services) {
                //启用路由
                services.AddRouting ();
            }
            public void Configure (IApplicationBuilder app) {
                //配置路由
                app.UseRouter (routes => {
                    //根路由 (/)
                    routes.MapGet("", context =>
                    {
                        return context.Response.WriteAsync("Hello, World!");
                    });
                    //在根路由 (/) 上,配置 /hello Get请求的路由
                    routes.MapGet ("hello", context => {
                        return context.Response.WriteAsync ("Got a Get request at /hello");
                    });
                });
            }
        }
    }

    安装相关的Nuget包

    $ dotnet add package Microsoft.AspNetCore.Routing --version 2.0.1

    下载依赖项

    $ dotnet restore

    运行

    $ dotnet run

    访问 http://localhost:5000/hello ,输出结果:

    Got a Get request at /hello

    对 /user 路由的 POST 请求进行响应:

    routes.MapPost("hello", context => {
        return context.Response.WriteAsync ("Got a Post request at /hello");
    })

    对 /user 路由的 PUT 请求进行响应:

    routes.MapPut("hello", context => {
        return context.Response.WriteAsync ("Got a Put request at /hello");
    })

    对 /user 路由的 DELETE 请求进行响应:

    routes.MapDelete("hello", context => {
        return context.Response.WriteAsync ("Got a Dlete request at /hello");
    })

    静态文件

    启用,配置静态资源

    创建 wwwroot文件夹

    $ mkdir wwwroot
    $ cd wwwroot

    复制 index.html 到 wwwroot目录

    $ mkdir images
    $ cd images

    复制 favicon.png 到 images文件夹下

    项目结构图如下:
    .
    ├── Demo
    │   ├── wwwroot
    │   │   ├── images
    │   │   │   ├── favicon.png
    │   │   ├── index.html
    │   ├── Demo.csproj
    │   └── Program.cs

    更改 Program.cs,设置ContentRoot路径为当前项目根目录,启用静态资源

    using System;
    using System.IO;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace Demo {
        class Program {
            static void Main (string[] args) {
                Console.WriteLine (AppContext.BaseDirectory);
                var host = new WebHostBuilder ()
                    .UseKestrel ()
                    //设置 ContentRoot, ContentRoot是任何资源的根路径,比如页面和静态资源
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseStartup<Startup>()
                    .Build ();
                host.Run ();
            }
        }
    
        class Startup {
            public void Configure (IApplicationBuilder app) {
                //使用静态资源,默认的目录是 /wwwroot
                app.UseStaticFiles ();
            }
        }
    }

    运行

    http://localhost:5000/index.html

    http://localhost:5000/images/favicon.png

    配置其他文件夹

    新建文件夹myfiles

    $ mkdir myfiles
    $ cd myfiles

    创建一个新的 index.html到myfiles文件夹,配置并使用myfiles文件夹

    app.UseStaticFiles (new StaticFileOptions {
        FileProvider = new PhysicalFileProvider (
                Path.Combine (Directory.GetCurrentDirectory (), "myfiles")),
            RequestPath = "/myfiles"
    });

    运行

    http://localhost:5000/myfiles/index.html

    页面渲染

    Razor模板引擎

    Asp.net Core自带的模板引擎是 Razor模板引擎,传统的Razor页面以cshtml结尾, 阅读Razor语法
    遗憾的是Razor模板引擎在Asp.net Core中封装在了MVC模块之中,需要做一些扩展才可以单独拿出来使用

    创建模板页面

    创建 views文件夹

    $ mkdir views
    $ cd views

    创建 index.cshtml 页面

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <ul>
            @for (int i = 0; i < 5; i++)
            {
                <li>第@(i + 1)行</li>
            }
        </ul>
    </body>
    </html>

    使用模板

    using System;
    using System.IO;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace Demo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(AppContext.BaseDirectory);
                var host = new WebHostBuilder()
                    .UseKestrel()
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseStartup<Startup>()
                    .Build();
                host.Run();
            }
        }
    
        class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                //启用Mvc
                services.AddMvc();
    
                //扩展类
                services.AddSingleton<RazorViewRenderer>();
    
            }
            public void Configure(IApplicationBuilder app)
            {
                app.UseRouter(routes =>
                {
                    //根路径 /
                    routes.MapGet("", context =>
                    {
                        return context.Render("/views/index.cshtml");
                    }
                });
            }
    
        }
    }

    安装相关的Nuget包

    $ dotnet add package Microsoft.AspNetCore.Mvc --version 2.0.2

    下载依赖项

    $ dotnet restore

    运行

    $ dotnet run

    结果

    第1行
    第2行
    第3行
    第4行
    第5行

    渲染数据

    新增实体类 UserInfo

    public class UserInfo
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    新增user.cshtml页面

    @using Demo;
    @model UserInfo
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <ul>
            <li>姓名:@Model.Name</li>
            <li>年龄:@Model.Age</li>
        </ul>
    </body>
    </html>

    更改Program.cs

    app.UseRouter(routes =>
    {
        //根路径 /
        routes.MapGet("", context =>
        {
            return context.Render("/views/index.cshtml");
        }
        routes.MapGet("/user", context =>
        {
            return context.Render("/views/user.cshtml", new UserInfo() { Name = "张三", Age = 18 });
        });
    });

    运行

    $ dotnet run

    结果

    姓名:张三
    年龄:18

    请求数据

    QueryString

    var queryCollection = context.Request.Query;
    foreach (var item in queryCollection)
    {
        Console.WriteLine(item.Key + ":" + item.Value);
    }

    Form

    if (context.Request.ContentType.ToLower().Contains("application/x-www-form-urlencoded")
    {
        var formCollection = context.Request.Form;
        foreach (var item in formCollection)
        {
            Console.WriteLine(item.Key + ":" + item.Value);
        }
    }

    Files

    var fileCollections = context.Request.Form.Files;
    var rootPath = context.RequestServices.GetService<IHostingEnvironment>().ContentRootPath;
    foreach (var item in fileCollections)
    {
        
        var path = Path.Combine(rootPath,item.FileNa
        using (var stream = new FileStream(path, FileMode.Create))
        {
            await item.CopyToAsync(stream);
        }
        Console.WriteLine(item.FileName + ":" + item.Length);
    }
    var headerCollection = context.Request.Headers;
    foreach (var item in headerCollection)
    {
        Console.WriteLine(item.Key + ":" + item.Value);
    }

    Body

    StreamReader reader = new StreamReader(context.Request.Body);
    string text = reader.ReadToEnd();

    Cookies

    var cookieCollection=context.Request.Cookies;
    foreach (var item in cookieCollection)
    {
        Console.WriteLine(item.Key + ":" + item.Value);
    }

    错误和重定向

    重定向

    context.Response.Redirect("path")

    状态代码页

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Diagnostics;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.IO;
    using System.Text.Encodings.Web;
    
    namespace Demo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(AppContext.BaseDirectory);
                var host = new WebHostBuilder()
                    .UseKestrel()
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseStartup<Startup>()
                    .Build();
                host.Run();
            }
        }
    
        class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                //启用路由
                services.AddRouting();
                services.AddMvc();
                services.AddSingleton<RazorViewRenderer>();
    
            }
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                //启用状态码页
                app.UseStatusCodePages();
    
                app.UseRouter(routes =>
                {
                    //根路径 /
                    routes.MapGet("", context =>
                    {
                        return context.Render("/views/index.cshtml");
                    }
                });
            }
    
        }
    }

    自定义状态码页

    新增 404.cshtml

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <h1> Oops! Page Not Found ! </h1>
    </body>
    </html>

    配置和使用自定义的状态码页

    app.UseStatusCodePagesWithRedirects("/status/{0}");
    
    app.UseRouter(routes =>
    {
        //""
        routes.MapGet("", context =>
        {
            return context.Response.WriteAsync("root path");
        });
    
        routes.MapGet("status/{code}", (request, response, routeData) =>
        {
            var statusCodePagesFeature = request.HttpContext.Features.Get<IStatusCodePagesFeature>();
            var code = routeData.Values["code"];
            if (statusCodePagesFeature != null && code!=null && code.ToString() == "404")
            {
                //跳转到自定义的404页面
                return request.HttpContext.Render("/views/404.cshtml");
            }
            else
            {
                return response.WriteAsync(HtmlEncoder.Default.Encode("状态码:"+ routeData.Values["code"]));
            }
        });
    });

    异常错误页

    开发异常页面,Asp.net Core自带

    app.UseDeveloperExceptionPage();

    新增测试路由,抛出空指针异常

    env.EnvironmentName = EnvironmentName.Development;
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseRouter(routes =>
    {
        //""
        routes.MapGet("", context =>
        {
            return context.Response.WriteAsync("root path");
        }
    
        //test
        routes.MapGet("test", context =>
        {
            throw new Exception("空指针异常!!!");
        });
    });

    访问http://localhost:57393/test,得到下面的页面
    nullexception

    自定义异常页面

    env.EnvironmentName = EnvironmentName.Production;
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        //自定义异常错误页面
        app.UseExceptionHandler("/error");
    }
    
    app.UseRouter(routes =>
    {
        //""
        routes.MapGet("", context =>
        {
            return context.Response.WriteAsync("root path");
        }
    
        //test
        routes.MapGet("test", context =>
        {
            throw new Exception("空指针异常!!!");
        });
        
        //自定义异常错误页面
        routes.MapGet("error", context =>
        {
            context.Response.StatusCode = 500;
            context.Response.ContentType = "text/html";
            var error = context.Features.Get<IExceptionHandlerFeature>();
            if (error != null)
            {
                return context.Response.WriteAsync("<h1>" + HtmlEncoder.Default.Encode("发生了错误: " + error.Error.Messa + "</h1>");
            }
            else
            {
                return context.Response.WriteAsync("<h1>" + HtmlEncoder.Default.Encode("Oops! 发生了错误,请联系管理员")"</h1>");
            }
        });
    });

    关于响应

    会话

    Session

    新增登陆页面login.cshtml

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <form action="/login" method="post">
            <div>
                <label>用户名:</label>
                <input  type="text" name="UserName" />
            </div>
            <div>
                <label>密码:</label>
                <input type="password" name="PassWord" />
            </div>
            <div>
                <input type="submit" value="登陆" />
            </div>
        </form>
    </body>
    </html>

    添加并启用Session,输入用户名和密码,点击登陆,保持Session数据,登陆成功跳转到/home路由页面,访问Session数据,显示当前登陆的用户名

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.IO;
    using System.Threading.Tasks;
    
    namespace Demo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine(AppContext.BaseDirectory);
                var host = new WebHostBuilder()
                    .UseKestrel()
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseStartup<Startup>()
                    .Build();
                host.Run();
            }
        }
    
        class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                //启用路由
                services.AddRouting();
                services.AddMvc();
                services.AddSingleton<RazorViewRenderer>();
                //增加Session
                services.AddSession();
            }
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                app.UseDeveloperExceptionPage();
    
                // 启用Session
                app.UseSession();
    
                app.UseRouter(routes =>
                {
                    routes.MapGet("", (request, response, routeData) =>
                    {
                        return request.HttpContext.Render("/views/login.cshtml");
                    });
    
                    routes.MapGet("home", context =>
                    {
                        string userName = context.Session.GetString("userName");
                        if (!string.IsNullOrEmpty(userName))
                        {
                            return context.Response.WriteAsync("User:" + userName);
                        }
                        else
                        {
                            context.Response.Redirect("/");
                            return Task.CompletedTask;
                        }
                    });
    
                    routes.MapPost("login", context =>
                    {
                        var userName = context.Request.Form["UserName"].ToString();
                        var password = context.Request.Form["PassWord"].ToString();
                        if (userName.Equals("admin") && password.Equals("123456"))
                        {
                            context.Session.SetString("userName", password);
                            context.Response.Redirect("/home");
                            return Task.CompletedTask;
                        }
                        else
                        {
                            throw new Exception("用户名或密码错误");
                        }
                    });
    
    
                });
            }
    
        }
    }

    日志

    安装Nuget包

    $ dotnet add package Microsoft.Extensions.Logging --version 2.0.0
    $ dotnet add package Microsoft.Extensions.Logging.Console --version 2.0.0

    下载依赖项

    $ dotnet restore

    添加日志模块

    //启用日志
    services.AddLogging(builder=> {
        //使用控制台日志提供程序
        builder.AddConsole();
    });

    创建ILogger实例

    var logger = context.RequestServices.GetService<ILogger<Startup>> ();
    //记录日志
    logger.LogInformation("before hello world");

    日志分类

    日志的类别,可以是任意字符串,默认是完全限定的类名,例如: Demo.Startup

    日志级别

    logger.LogTrace();
    logger.LogDebug();
    logger.LogInformation();
    logger.LogWarning();
    logger.LogError();
    logger.LogCritical();

    日志过滤

    给日志分类为 Demo.Startup的日志类别限定最小的日志级别是LogLevel.Information

    services.AddLogging(builder=> {
        builder.AddFilter("Demo.Startup", LogLevel.Information);
        builder.AddConsole();
    });

    访问更多关于日志类别,级别,模板,过滤,作用域的说明日志类别

    配置

    添加配置

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Threading.Tasks;
    
    namespace Demo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var configBuilder = new ConfigurationBuilder();
                configBuilder.AddInMemoryCollection(new Dictionary<string, string>() {
                    { "name","zhangsan"},
                    { "age","18"}
                });
                var config = configBuilder.Build();
    
                var host = new WebHostBuilder()
                    .UseKestrel()
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .UseConfiguration(config)
                    .UseStartup<Startup>()
                    .Build();
                host.Run();
            }
        }
    }

    读取配置

    app.UseRouter(routes =>
    {
        routes.MapGet("", context =>
        {
            var config = context.RequestServices.GetService<IConfiguration>();
            string name = config["name"];
            string age = config["age"];
            return context.Response.WriteAsync("name:"+name+",Age:"+age);
        });
    });

    国际化

    添加依赖的Nuget包

    $ dotnet add package Microsoft.Extensions.Localization --version 2.0.1

    下载依赖项

    $ dotnet restore

    资源文件命名规则

    命名规则 {分类名称}.{语言区域名称}.resx

    • 分类名称:默认的分类名称就是类型的FullName,在AssemblyName后的相对路径,比如Demo.Startup,程序集的名称Demo,就剩下了Startup
    • 语言区域名称: 语言区域名称有zh-CN,en-US,更多

    特殊条件下你也可以将资源文件和类定义文件放在同一个目录,这样资源文件的分类名称就是{类名称}.{语言区域名称}.resx

    添加资源文件

    • 新建 myresources文件夹
    • 新建 Startup.en-US.resx 新增项 name是sayhi,value是 HelloWorld
    • 新建 Startup.zh-CN.resx 新增项 name是sayhi,value是 世界你好

    添加本地化

    services.AddLocalization(options =>
        options.ResourcesPath = "myresources";
    });

    启用本地化

    var cultureList = new List<CultureInfo>() {
             new CultureInfo("en-US"),
             new CultureInfo("zh-CN")
    };
    var options = new RequestLocalizationOptions()
    {
        SupportedCultures = cultureList,
        SupportedUICultures = cultureList,
        DefaultRequestCulture = new RequestCulture("zh-CN")
    };
    //新建基于Query String的多语言提供程序
    var provider = new QueryStringRequestCultureProvider();
    provider.QueryStringKey = "culture";
    provider.UIQueryStringKey = "uiculture";
    //删除所有多语言提供程序
    options.RequestCultureProviders.Clear();
    options.RequestCultureProviders.Add(provider);

    资源文件读取

    var stringLocalizer = context.RequestServices.GetService<IStringLocalizer<Startup>>();
    string sayhi = stringLocalizer["sayhi"];

    访问 http://localhost:57393/?culture=en-US 切换为英文版本,默认的语言是中文

    多语言提供程序

    • 通过Query String,[默认自带]
    • 通过Cookie,[默认自带]
    • 通过Accept-Language header,[默认自带]
    • 通过RouteData

    模板格式化

    string username="张三";
    string localizedString = _localizer["Hello {0}!", username];

     

     
     
    标签: asp.net core
  • 相关阅读:
    php中数组排序的基本方法
    mysql的锁--行锁,表锁,乐观锁,悲观锁
    php数组内数据个数统计函数
    《自我介绍》
    结对编项目作业
    《结对-英文词频检测程序-需求分析》
    对软件工程课程的期望
    挑战图像处理100问(4)——Otsu
    挑战图像处理100问(3)——二值化
    挑战图像处理100问(2)——灰度化
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/8414750.html
Copyright © 2020-2023  润新知