前言

至于为什么要搭建.Net Core 平台,这个网上的解释以及铺天盖地,想了想,还是感觉重要的一点,跨平台,嗯!没错,而且比.Net 更容易搭建,速度也更快,所有的包均由Nuget提供,不再像以前的单纯引入组件,

已经没有了之前的Assemblies和COM的引入,初次使用感觉会很别扭,不过使用多了,发现还是很方便的,所以你一定要会使用Nuget,真的很强大,这点儿设计思路感觉更像Linux了。

下边这三点,是先对 .net core  有一个初步的认识,看得懂或者看不懂都没有关系,以后大家肯定都会明白的:

1、.net core 框架性能测试

http://www.techempower.com/benchmarks/ 我们可以通过这个web框架性能测试来看看 aspcore 的性能

2、.net core 执行过程

3、中间件执行过程

启动的时候先执行该中间件类的构造函数,然后一路 Next() ;下去,返回的时候,正好是反向的,执行的是该类的逻辑部分:

4、AOP切面

5、整体框架结构与数据库表UML

一、创建第一个Core

说了从零开始,就得从零开始,老生常谈,开始。

1、SDK 安装

当然,前提是你得安装.Net Core 的 SDK 环境,这里只能使用 vs 2019 且版本在 16.3 以上。

下载 SDK 地址 :https://dotnet.microsoft.com/download

选择指定的平台即可安装:

这里说下,SDK 和 RunTime 的区别:1、SDK 是用来开发 NetCore 的,内部捆绑了 Runtime 运行时;2、但是如果只想运行 NetCore 项目的话,只需要在服务器中安装 Runtime 运行时即可;

怎么判断安装成功了呢?直接运行命令,如果有结果证明成功了:

2、新建项目

1、打开vs2019 ,创建一个新的项目,这里有很多模板,大家都可以试试:

2、点击下一步 Next。

3、然后创建模板:

这里要注意下,关于Https选项问题,有很多小伙伴在以后的接口调用中,勾选了这个,但是还是一直使用 http 协议去访问,导致找不到响应的接口地址。

1、是你的项目创建的时候,勾选了 Https 选项,如果你还没有创建,那就可以不要勾选那个 HTTPS选项。

2、如果你的项目已经创建好了,每次访问都是HTTPS的,但是你不想这么做,可以在 launthSettings.json 文件中,把sslPort 端口号改成0即可

3、删除IIS配置,默认使用 kestrel 服务器

3、项目整体结构分析

接下来咱们看看这个项目都包含了哪些东西:

这里默认,有一个 WeatherForecastController.cs ,打开后:

1、当前控制器继承了 ControllerBase 基类;

2、有一个路由特性,可以配置当前路由规则,比如当前的是[Route("[controller]")],表示 Url 路径为 :localhost:5000/weacherforecast

如果你配置 [Route("[controller]/[action]")] 表明 Url 路径为 :localhost:5000/weacherforecast/get

3、发现有一个默认的构造函数,里边已经注入了日志 ILogger,这个依赖注入相关内容,以后会讲到;

4、默认提供了一个 get 方法,随机生成了一个天气集合;

接下来点开appsettings.json文件,这里就是整个系统app的配置地址,更类似以前的web.config,以后大家会用到。

继续往下,打开Startup.cs 文件这里是整个项目的启动文件,所有的启动相关的都会在这里配置,比如 依赖注入,跨域请求,Redis缓存等,更多详情在以后的文章中都会有所提起

二、重要文件说明

1、Program.cs

namespace blog.core.test3._0{    public class Program    {        public static void Main(string[] args)        {            CreateHostBuilder(args).Build().Run();        }        public static IHostBuilder CreateHostBuilder(string[] args) =>            Host.CreateDefaultBuilder(args)                .ConfigureWebHostDefaults(webBuilder =>                {                    webBuilder.UseStartup<Startup>();                });    }}

下边的内容,如果不理解,也无所谓,以后会慢慢懂的,至少要读几遍,知道相关概念。

这个Program是程序的入口, 看起来很眼熟, 是因为asp.net core application实际就是控制台程序(console application).

它是一个调用asp.net core 相关库的console application.

Main方法里面的内容主要是用来配置和运行程序的。

因为我们的web程序需要一个宿主,所以CreateHostBuilder这个方法就创建了一个IHostBuilder. 而且我们还需要Web Server.

asp.net core 自带了两种http servers, 一个是WebListener, 它只能用于windows系统, 另一个是kestrel, 它是跨平台的.

kestrel是默认的web server, 就是通过UseKestrel()这个方法来启用的.

但是我们开发的时候使用的是IIS Express, 调用UseIISIntegration()这个方法是启用IIS Express, 它作为Kestrel的Reverse Proxy server来用.

如果在windows服务器上部署的话, 就应该使用IIS作为Kestrel的反向代理服务器来管理和代理请求.

如果在linux上的话, 可以使用apache, nginx等等的作为kestrel的proxy server.

当然也可以单独使用kestrel作为web 服务器, 但是使用iis作为反向代理还是有很多有优点的: 例如,IIS可以过滤请求, 管理证书, 程序崩溃时自动重启等.

不过目前因为跨平台,所以使用较多的还是 nginx。

webBuilder.UseStartup<Startup>();, 这句话表示在程序启动的时候, 我们会调用Startup这个类.

Build()完之后返回一个实现了 IHost 接口的实例(IHostBuilder), 然后调用Run()就会运行Web程序, 并且阻止这个调用的线程, 直到程序关闭.

如果想要对AspNetCore源码进行研究,可以查看源码,这里提供两个方法:

1、F12,当然这个不能看到详细的,你需要安装一个组件,VS2017 Resharper

2、查看Github 开源源码 https://github.com/aspnet/MetaPackages/tree/master/src/Microsoft.AspNetCore

最后让大家看看我的 Blog.Core 项目最终的 Services :

2、Startup.cs

    public class Startup    {        public Startup(IConfiguration configuration)        {            Configuration = configuration;        }        public IConfiguration Configuration { get; }        // This method gets called by the runtime. Use this method to add services to the container.        public void ConfigureServices(IServiceCollection services)        {            services.AddControllers();        }        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)        {            if (env.IsDevelopment())            {                app.UseDeveloperExceptionPage();            }            app.UseRouting();            app.UseAuthorization();            app.UseEndpoints(endpoints =>            {                endpoints.MapControllers();            });        }    }

其实Startup算是程序真正的切入点,是配置服务和中间件的启动类。

Startup 默认构造函数,注入了配置项 IConfiguration。

ConfigureServices方法是用来把services(各种服务, 例如identity, ef, mvc等等包括第三方的, 或者自己写的)加入(register)到container(asp.net core的容器)中去, 并配置这些services. 这个container是用来进行dependency injection的(依赖注入). 所有注入的services(此外还包括一些框架已经注册好的services) 在以后写代码的时候, 都可以将它们注入(inject)进去. 例如上面的Configure方法的参数, app, env, loggerFactory都是注入进去的services.

Configure 方法是asp.net core程序用来具体指定如何处理每个http请求的, 例如我们可以让这个程序知道我使用mvc来处理http请求, 那就调用 app.UseEndpoints 这个方法就行,这个是一个短路中间件,表示 http 请求到了这里就不往下走了.

看一看我们项目的最后,Configure方法是如何配置的,这个现在看看就行,以后肯定会让大家都慢慢掌握:

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IBlogArticleServices _blogArticleServices)        {            #region ReuestResponseLog            if (Appsettings.app("AppSettings", "Middleware_RequestResponse", "Enabled").ObjToBool())            {                app.UseReuestResponseLog();//记录请求与返回数据             }            #endregion            #region Environment            if (env.IsDevelopment())            {                // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。                app.UseDeveloperExceptionPage();                //app.Use(async (context, next) =>                //{                //    //这里会多次调用,这里测试一下就行,不要打开注释                //    //var blogs =await _blogArticleServices.GetBlogs();                //    var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName;                //    Console.WriteLine(processName);                //    await next();                //});            }            else            {                app.UseExceptionHandler("/Error");                // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。                // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection                //app.UseHsts();            }            #endregion            #region Swagger            app.UseSwagger();            app.UseSwaggerUI(c =>            {                //根据版本名称倒序 遍历展示                typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version =>                {                    c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}");                });                // 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:解决方案名.index.html                c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.index.html");//这里是配合MiniProfiler进行性能监控的,《文章:完美基于AOP的接口性能分析》,如果你不需要,可以暂时先注释掉,不影响大局。                c.RoutePrefix = ""; //路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉,如果你想换一个路径,直接写名字即可,比如直接写c.RoutePrefix = "doc";            });            #endregion            #region MiniProfiler            app.UseMiniProfiler();            #endregion                        #region CORS            //跨域第二种方法,使用策略,详细策略信息在ConfigureService中            app.UseCors("LimitRequests");//将 CORS 中间件添加到 web 应用程序管线中, 以允许跨域请求。            #region 跨域第一种版本            //跨域第一种版本,请要ConfigureService中配置服务 services.AddCors();            //    app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader()            //.AllowAnyMethod());              #endregion            #endregion            // 跳转https            //app.UseHttpsRedirection();            // 使用静态文件            app.UseStaticFiles();            // 使用cookie            app.UseCookiePolicy();            // 返回错误码            app.UseStatusCodePages();//把错误码返回前台,比如是404            app.UseRouting();            #region 第三步:开启认证中间件            //此授权认证方法已经放弃,请使用下边的官方验证方法。但是如果你还想传User的全局变量,还是可以继续使用中间件,第二种写法//app.UseMiddleware<JwtTokenAuth>();             //app.UseJwtTokenAuth();            //如果你想使用官方认证,必须在上边ConfigureService 中,配置JWT的认证服务 (.AddAuthentication 和 .AddJwtBearer 二者缺一不可)            app.UseAuthentication();            #endregion            app.UseAuthorization();            app.UseEndpoints(endpoints =>            {                endpoints.MapControllerRoute(                    name: "default",                    pattern: "{controller=Home}/{action=Index}/{id?}");                endpoints.MapHub<ChatHub>("/api2/chatHub");            });        }

3、运行方法

.net core 调试的两种方法

1、通过IIS调试

2、项目自带的Kestrel web应用调式

三、注册并使用MVC

首先,在 ConfigureServices里添加控制器服务,因为我们这里是api项目,所以只需要添加 AddController() 即可,如果是 MVC,可以使用 AddControllersWithViews();

然后在 Configure 中,配置一下中间件:

 app.UseEndpoints(endpoints => {     endpoints.MapControllers(); });

这样就达到了目的,这个不仅仅是 mvc 的开发过程,其他的也都是这样的,以后我们开发其他的,都需要按照这个过程开发即可。

四、核心知识点

1、Routing 路由

路由有两种方式: Convention-based (按约定), attribute-based(基于路由属性配置的).

其中convention-based (基于约定的) 主要用于MVC (返回View或者Razor Page那种的).

Web api 推荐使用attribute-based.

这种基于属性配置的路由可以配置Controller或者Action级别, uri会根据Http method然后被匹配到一个controller里具体的action上.

常用的Http Method有:

  • Get, 查询, Attribute: HttpGet, 例如: '/api/product', '/api/product/1'

  • POST, 创建, HttpPost, '/api/product'

  • PUT 整体修改更新 HttpPut, '/api/product/1'

  • PATCH 部分更新, HttpPatch, '/api/product/1'

  • DELETE 删除, HttpDelete, '/api/product/1

还有一个Route属性(attribute)也可以用于Controller层, 它可以控制action级的URI前缀.

//以下不是本系列教程,就看思路即可,不用敲代码namespace CoreBackend.Api.Controllers{    //[Route("api/product")]    [Route("api/[controller]")]    public class ProductController: Controller    {        [HttpGet]        public JsonResult GetProducts()        {            return new JsonResult(new List<Product>            {                new Product                {                    Id = 1,                    Name = "牛奶",                    Price = 2.5f                },                new Product                {                    Id = 2,                    Name = "面包",                    Price = 4.5f                }            });        }    }}

使用[Route("api/[controller]")], 它使得整个Controller下面所有action的uri前缀变成了"/api/product", 其中[controller]表示XxxController.cs中的Xxx(其实是小写).

也可以具体指定, [Route("api/product")], 这样做的好处是, 如果ProductController重构以后改名了, 只要不改Route里面的内容, 那么请求的地址不会发生变化.

然后在GetProducts方法上面, 写上HttpGet, 也可以写HttpGet(). 它里面还可以加参数,例如: HttpGet("all"), 那么这个Action的请求的地址就变成了 "/api/product/All".

2、内容协商 Content Negotiation

如果 web api提供了多种内容格式, 那么可以通过Accept Header来选择最好的内容返回格式: 例如:

application/json, application/xml等等

如果设定的格式在web api里面没有, 那么web api就会使用默认的格式.

asp.net core 默认提供的是json格式, 也可以配置xml等格式.

目前只考虑 Output formatter, 就是返回的内容格式.

如果想输出xml格式,就配置这里:

3、创建Post Action

以下不是本系列,就看思路即可,不用敲代码

//以下不是本系列教程,就看思路即可,不用敲代码    [Route("{id}", Name = "GetProduct")]        public IActionResult GetProduct(int id)        {            var product = ProductService...(x => x.Id == id);            if (product == null)            {                return NotFound();            }            return Ok(product);        }        [HttpPost]        public IActionResult Post([FromBody] ProductCreation product)        {            if (product == null)            {                return BadRequest();            }            var maxId = ProductService.Max(x => x.Id);            var newProduct = new Product            {                Id = ++maxId,                Name = product.Name,                Price = product.Price            };            ProductService.Add(newProduct);            return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct);        }

[HttpPost] 表示请求的谓词是Post. 加上Controller的Route前缀, 那么访问这个Action的地址就应该是: 'api/product'

后边也可以跟着自定义的路由地址, 例如 [HttpPost("create")], 那么这个Action的路由地址就应该是: 'api/product/create'.

[FromBody] , 请求的body里面包含着方法需要的实体数据, 方法需要把这个数据Deserialize成ProductCreation, [FromBody]就是干这些活的.

客户端程序可能会发起一个Bad的Request, 导致数据不能被Deserialize, 这时候参数product就会变成null. 所以这是一个客户端发生的错误, 程序为让客户端知道是它引起了错误, 就应该返回一个Bad Request 400 (Bad Request表示客户端引起的错误)的 Status Code.

传递进来的model类型是 ProductCreation, 而我们最终操作的类型是Product, 所以需要进行一个Map操作, 目前还是挨个属性写代码进行Map吧, 以后会改成Automapper.

返回 CreatedAtRoute: 对于POST, 建议的返回Status Code 是 201 (Created), 可以使用CreatedAtRoute这个内置的Helper Method. 它可以返回一个带有地址Header的Response, 这个Location Header将会包含一个URI, 通过这个URI可以找到我们新创建的实体数据. 这里就是指之前写的GetProduct(int id)这个方法. 但是这个Action必须有一个路由的名字才可以引用它, 所以在GetProduct方法上的Route这个attribute里面加上Name="GetProduct", 然后在CreatedAtRoute方法第一个参数写上这个名字就可以了, 尽管进行了引用, 但是Post方法走完的时候并不会调用GetProduct方法. CreatedAtRoute第二个参数就是对应着GetProduct的参数列表, 使用匿名类即可, 最后一个参数是我们刚刚创建的数据实体.

运行程序试验一下, 注意需要在Headers里面设置Content-Type: application/json.

4、Validation 验证

针对上面的Post方法,  如果请求没有Body, 参数product就会是null, 这个我们已经判断了; 如果body里面的数据所包含的属性在product中不存在, 那么这个属性就会被忽略.

但是如果body数据的属性有问题, 比如说name没有填写, 或者name太长, 那么在执行action方法的时候就会报错, 这时候框架会自动抛出500异常, 表示是服务器的错误, 这是不对的. 这种错误是由客户端引起的, 所以需要返回400 Bad Request错误.

验证Model/实体, asp.net core 内置可以使用 Data Annotations进行:

//以下不是本系列教程,就看思路即可,不用敲代码using System;using System.ComponentModel.DataAnnotations;namespace CoreBackend.Api.Dtos{    public class ProductCreation    {        [Display(Name = "产品名称")]        [Required(ErrorMessage = "{0}是必填项")]        // [MinLength(2, ErrorMessage = "{0}的最小长度是{1}")]        // [MaxLength(10, ErrorMessage = "{0}的长度不可以超过{1}")]     [StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的长度应该不小于{2}, 不大于{1}")]        public string Name { get; set; }        [Display(Name = "价格")]        [Range(0, Double.MaxValue, ErrorMessage = "{0}的值必须大于{1}")]        public float Price { get; set; }    }}

这些Data Annotation (理解为用于验证的注解), 可以在System.ComponentModel.DataAnnotation找到, 例如[Required]表示必填, [MinLength]表示最小长度, [StringLength]可以同时验证最小和最大长度, [Range]表示数值的范围等等很多.

[Display(Name="xxx")]的用处是, 给属性起一个比较友好的名字.

其他的验证注解都有一个属性叫做 ErrorMessage (string), 表示如果验证失败, 就会把ErrorMessage的内容添加到错误结果里面去. 这个ErrorMessage可以使用参数, {0}表示Display的Name属性, {1}表示当前注解的第一个变量, {2}表示当前注解的第二个变量.

在Controller里面添加验证逻辑:

//以下不是本系列教程,就看思路即可,不用敲代码    [HttpPost]        public IActionResult Post([FromBody] ProductCreation product)        {            if (product == null)            {                return BadRequest();            }            if (!ModelState.IsValid)            {                return BadRequest(ModelState);            }            var maxId = ProductService.Max(x => x.Id);            var newProduct = new Product            {                Id = ++maxId,                Name = product.Name,                Price = product.Price            };            ProductService.Add(newProduct);            return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct);        } 

ModelState: 是一个Dictionary, 它里面是请求提交到Action的Name和Value的对们, 一个name对应着model的一个属性, 它也包含了一个针对每个提交的属性的错误信息的集合.

每次请求进到Action的时候, 我们在ProductCreationModel添加的那些注解的验证, 就会被检查. 只要其中有一个验证没通过, 那么ModelState.IsValid属性就是False. 可以设置断点查看ModelState里面都有哪些东西.

如果有错误的话, 我们可以把ModelState当作 Bad Request的参数一起返回到前台.

5、PUT请求

put应该用于对model进行完整的更新.

首先最好还是单独为Put写一个Dto Model, 尽管属性可能都是一样的, 但是也建议这样写, 实在不想写也可以.

ProducModification.cs

    public class ProductModification    {        [Display(Name = "产品名称")]        [Required(ErrorMessage = "{0}是必填项")]        [StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的长度应该不小于{2}, 不大于{1}")]        public string Name { get; set; }        [Display(Name = "价格")]        [Range(0, Double.MaxValue, ErrorMessage = "{0}的值必须大于{1}")]        public float Price { get; set; }    }

然后编写Controller的方法:

//以下不是本系列教程,就看思路即可,不用敲代码    [HttpPut("{id}")]        public IActionResult Put(int id, [FromBody] ProductModification product)        {            if (product == null)            {                return BadRequest();            }            if (product.Name == "产品")            {                ModelState.AddModelError("Name", "产品的名称不可以是'产品'二字");            }            if (!ModelState.IsValid)            {                return BadRequest(ModelState);            }            var model = ProductService.SingleOrDefault(x => x.Id == id);            if (model == null)            {                return NotFound();            }            model.Name = product.Name;            model.Price = product.Price;            // return Ok(model);            return NoContent();        } 

按照Http Put的约定, 需要一个id这样的参数, 用于查找现有的model.

由于Put做的是完整的更新, 所以把ProducModification整个Model作为参数.

进来之后, 进行了一套和POST一模一样的验证, 这地方肯定可以改进, 如果验证逻辑比较复杂的话, 到处写同样验证逻辑肯定是不好的, 所以建议使用FluentValidation.

然后, 把ProductModification的属性都映射查询找到给Product, 这个以后用AutoMapper来映射.

返回: PUT建议返回NoContent(), 因为更新是客户端发起的, 客户端已经有了最新的值, 无须服务器再给它传递一次, 当然了, 如果有些值是在后台更新的, 那么也可以使用Ok(xxx)然后把更新后的model作为参数一起传到前台.


五、结语

好啦,项目搭建就这么愉快的解决了,而且你也应该简单了解了.Net Core API是如何安装,创建,各个文件的意义以及如何运作,如何配置等,但是既然是接口,那一定是要前后端一起进行配置,使用,交流的平台,从上文看出,每次都特别麻烦,而且不直观,UI 不友好,怎么办呢?

下一节我们就使用一个神器 Swagger,一个快速,轻量级的项目RESTFUL接口的文档在线自动生成+功能测试功能软件。

Github && Gitee

https://github.com/anjoy8/Blog.Core.git

NOTE

如何不会使用Git,可以参考

https://www.jianshu.com/p/2b666a08a3b5

一起学习,一起进步   QQ群:867095512

【 .NET Core 3.0 】框架之二 || 后端项目搭建相关推荐

  1. MVVM框架的搭建(二)——项目搭建

    介绍完背景以及初衷之后,我们开始搭建MVVM的框架,这一部分我们进行简单的搭建,了解MVVM架构的基本结构. MVVM框架的搭建(一)--背景 MVVM框架的搭建(二)--项目搭建 MVVM框架的搭建 ...

  2. 001-云E办_后端项目搭建

    001-云E办_后端项目介绍 一.项目介绍: 二.项目搭建 1.创建数据库 2.创建父项目 3.创建子项目:yeb-server 4.更改yeb-server 1.在xml文件中,添加依赖于父工程. ...

  3. 将Entity Framework Core v5.0移至单独的项目

    目录 介绍 平台 创建解决方案 1. 创建解决方案 2. 将Web应用程序命名为"EFCore5App" 3. 附加信息 4. 构建和运行解决方案 5. Web应用程序现在将包含使 ...

  4. React框架课时二认识项目的结构目录一

    1react项目的目录的内容信息 ├── README.md 使用方法的文档 ├── node_modules 所有的依赖安装的目录 ├── package-lock.json 锁定安装时的包的版本号 ...

  5. 从零开始建站(四) - 后端项目搭建

    简介: 这节聊一聊项目的后端,之前也讲过,因为对前端不熟悉,所以在前端花了太多的时间,导致到后端开发的时候搞的人有点疲,所以很多东西从简了,很多细节东西没有考虑,只想着把基本功能做出来就好了.框架选择 ...

  6. Java Spring 后端项目搭建

    参考了几位同行的Blogs和StackOverflow上的许多问答,搭建了此后端项目,替换原来的node.js后端,和前一篇中搭建的Vue Web App项目配合使用,后端准备只提供服务,不包含后端装 ...

  7. 0基础学web----小鲜项目搭建(拓展)

    01-CSS精灵-作用 02-CSS精灵-使用 <!DOCTYPE html> <html lang="en"> <head><meta ...

  8. 移动安全--44--MobSF-v3.0框架安装与开发环境搭建

    背景 由于3.0改动较大,相较于1.0已经很多用法都不一样了,因此删除了之前写的讲解1.0版本的内容,按新版本的特性重新写了本文. 环境 MacOS MobSF简介 移动安全框架(MobSF)是一种自 ...

  9. .Net Core 3.0 控制台 WebAPI 开发 基础环境搭建.

    首先要创建项目,然后,引入nuget包 Install-Package Microsoft.AspNetCore -Version 2.2.0 Install-Package Microsoft.As ...

最新文章

  1. 全球及中国停车场建设产业十四五盈利模式与建设现状分析报告2022版
  2. 周志华《机器学习》课后习题解析(第四章):决策树
  3. apiexample.c例子教我们如何利用FFMPEG库中的API函数来编写自己的编解码程序
  4. WPF遍历当前容器中某种控件的方法
  5. 洛谷 P1122 最大子树和-求树的最大子树权值和
  6. 新来个专家吐槽我们:连qps都不懂,靠谱吗?
  7. selenium 保持窗口一直开启_Python+selenium自动化测试
  8. Android开发学习之电话、短信、联系人
  9. SolidWorks中最好用功能最强的BOM汇总工具:DDBOM2010
  10. 手机上什么App能连接mysql_这款APP让你的手机瞬间变身服务器,php+mysql,太硬核了!...
  11. 多用组合少用继承的设计模式JAVA_结合设计模式理解多用组合少用继承的原则(转)...
  12. Transition组件
  13. reflections歌词翻译_花木兰主题曲Reflection翻译成中文的准确歌词
  14. 利用APPInventor开发手机APP,实现OBLOQ-IOT与Arduino设备通信
  15. 兴华永恒公司CSO仙果:Flash之殇—漏洞之王Flash Player的末路
  16. 令人惋惜:Sigfox撑不下去了!这回,可不能怪NB-IoT、LoRa......
  17. Java将OFD文件转PDF文件
  18. linux进程管道通信缺点,Linux 进程间通信(1) -- 管道
  19. 计算机音乐安顺学院教务网络管理系统,安顺学院教务网络管理系统http://jwxt.asu.edu.cn/jwweb/...
  20. (复习)android 调用系统相机 图库 裁剪-图片上传-客服端-服务器

热门文章

  1. 宝宝的成长脚印6/15
  2. jest忽略如何添加忽略_大多数人忽略的基本家庭维护任务
  3. vue element form中input等组件不能输入值
  4. 洛谷P5055 【模板】可持久化文艺平衡树(FHQ Treap)
  5. 使用CADisplayLink实现UILabel动画特效
  6. WPF实现物理效果 拉一个小球
  7. 10分钟学会vue滚动行为
  8. SD卡读写扇区注意事项(转)
  9. Json.Net系列教程 3.Json.Net序列化和反序列化设置
  10. 手把手教你学Dapr - 5. 状态管理