理解ASP.NET Core - [01] Startup
注:本文隶属于《理解ASP.NET Core》系列文章,请查看置顶博客或点击此处查看全文目录
准备工作:一份ASP.NET Core Web API应用程序
当我们来到一个陌生的环境,第一件事就是找到厕所在哪。
当我们接触一份新框架时,第一件事就是找到程序入口,即Main方法
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>();
});
}
代码很简单,典型的建造者模式:通过IHostBuilder创建一个通用主机(Generic Host),然后启动它(至于什么是通用主机,咱们后续的文章会说到)。咱们不要一上来就去研究CreateDefaultBuilder、ConfigureWebHostDefaults这些方法的源代码,应该去寻找能看的见、摸得着的,很明显,只有Startup。
Startup类
Startup类承担应用的启动任务,所以按照约定,起名为Startup,不过你可以修改为任意类名(强烈建议类名为Startup)。
默认的Startup结构很简单,包含:
- 构造函数
Configuration属性ConfigureServices方法Configure方法
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.
// 该方法由运行时调用,使用该方法向DI容器添加服务
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
// 该方法由运行时调用,使用该方法配置HTTP请求管道
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
}
Startup构造函数
当使用通用主机(Generic Host)时,Startup构造函数支持注入以下三种服务类型:
IConfigurationIWebHostEnvironmentIHostEnvironment
public Startup(
IConfiguration configuration,
IHostEnvironment hostEnvironment,
IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
HostEnvironment = hostEnvironment;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IHostEnvironment HostEnvironment { get; set; }
public IWebHostEnvironment WebHostEnvironment { get; set; }
这里你会发现
HostEnvironment和WebHostEnvironment的实例是同一个。别着急,后续文章我们聊到Host的时候,你就明白了。
ConfigureServices
- 该方法是可选的
- 该方法用于添加服务到DI容器中
- 该方法在
Configure方法之前被调用 - 该方法要么无参数,要么只能有一个参数且类型必须为
IServiceCollection - 该方法内的代码大多是形如
Add{Service}的扩展方法
常用的服务有(部分服务框架已默认注册):
AddControllers:注册Controller相关服务,内部调用了AddMvcCore、AddApiExplorer、AddAuthorization、AddCors、AddDataAnnotations、AddFormatterMappings等多个扩展方法AddOptions:注册Options相关服务,如IOptions<>、IOptionsSnapshot<>、IOptionsMonitor<>、IOptionsFactory<>、IOptionsMonitorCache<>等。很多服务都需要Options,所以很多服务注册的扩展方法会在内部调用AddOptionsAddRouting:注册路由相关服务,如IInlineConstraintResolver、LinkGenerator、IConfigureOptions<RouteOptions>、RoutePatternTransformer等AddAddLogging:注册Logging相关服务,如ILoggerFactory、ILogger<>、IConfigureOptions<LoggerFilterOptions>>等AddAuthentication:注册身份认证相关服务,以方便后续注册JwtBearer、Cookie等服务AddAuthorization:注册用户授权相关服务AddMvc:注册Mvc相关服务,比如Controllers、Views、RazorPages等AddHealthChecks:注册健康检查相关服务,如HealthCheckService、IHostedService等
Configure
- 该方法是必须的
- 该方法用于配置HTTP请求管道,通过向管道添加中间件,应用不同的响应方式。
- 该方法在
ConfigureServices方法之后被调用 - 该方法中的参数可以接受任何已注入到DI容器中的服务
- 该方法内的代码大多是形如
Use{Middleware}的扩展方法 - 该方法内中间件的注册顺序与代码的书写顺序是一致的,先注册的先执行,后注册的后执行
常用的中间件有
UseDeveloperExceptionPage:当发生异常时,展示开发人员异常信息页。如图

UseRouting:路由中间件,根据Url中的路径导航到对应的Endpoint。必须与UseEndpoints搭配使用。UseEndpoints:执行路由所选择的Endpoint对应的委托。UseAuthentication:身份认证中间件,用于对请求用户的身份进行认证。比如,早晨上班打卡时,管理员认出你是公司员工,那么才允许你进入公司。UseAuthorization:用户授权中间件,用于对请求用户进行授权。比如,虽然你是公司员工,但是你是一名.NET开发工程师,那么你只允许坐在.NET开发工程师区域的工位上,而不能坐在老总的办公室里。UseMvc:Mvc中间件。UseHealthChecks:健康检查中间件。UseMiddleware:用来添加匿名中间件的,通过该方法,可以方便的添加自定义中间件。
省略Startup类
另外,Startup类也可以省略,直接进行如下配置即可(虽然可以这样做,但是不推荐):
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// ConfigureServices 可以调用多次,最终会将结果聚合
webBuilder.ConfigureServices(services =>
{
})
// Configure 如果调用多次,则只有最后一次生效
.Configure(app =>
{
var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
});
});
IStartupFilter
public interface IStartupFilter
{
Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
}
有时,我们想要将一系列相关中间件的注册封装到一起,那么我们只需要通过实现IStartupFilter,并在Startup.ConfigureServices中配置IStartupFilter的依赖注入即可。
- 在
IStartupFilter中配置的中间件,总是比Startup类中Configure方法中的中间件先注册;对于多个IStartupFilter实现,执行顺序与服务注册时的顺序一致
我们可以通过一个例子来验证一下中间件的注册顺序。
首先是三个IStartupFilter的实现类:
public class FirstStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
=> app =>
{
app.Use((context, next) =>
{
Console.WriteLine("First");
return next();
});
next(app);
};
}
public class SecondStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
=> app =>
{
app.Use((context, next) =>
{
Console.WriteLine("Second");
return next();
});
next(app);
};
}
public class ThirdStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
=> app =>
{
app.Use((context, next) =>
{
Console.WriteLine("Third");
return next();
});
next(app);
};
}
接下来进行注册:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
// 第一个被注册
services.AddTransient<IStartupFilter, FirstStartupFilter>();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
// 第三个被注册
services.AddTransient<IStartupFilter, ThirdStartupFilter>();
});
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// 第二个被注册
services.AddTransient<IStartupFilter, SecondStartupFilter>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 第四个被注册
app.Use((context, next) =>
{
Console.WriteLine("Forth");
return next();
});
}
}
最后通过输出可以看到,执行顺序的确是这样子的。
First
Second
Third
Forth
IHostingStartup
与IStartupFilter不同的是,IHostingStartup可以在启动时通过外部程序集向应用增加更多功能。不过这要求必须调用ConfigureWebHostDefaults扩展方法
我们经常使用的Nuget包
SkyApm.Agent.AspNetCore就使用了该特性。
下面我们就来看一下该如何使用它。
HostingStartup 程序集
要创建HostingStartup程序集,可以通过创建类库项目或无入口点的控制台应用来实现。
接下来咱们还是看一下上面提到过的SkyApm.Agent.AspNetCore:
using SkyApm.Agent.AspNetCore;
[assembly: HostingStartup(typeof(SkyApmHostingStartup))]
namespace SkyApm.Agent.AspNetCore
{
internal class SkyApmHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()));
}
}
}
该HostingStartup类:
- 实现了
IHostingStartup接口 Configure方法中使用IWebHostBuilder来添加增强功能- 配置了
HostingStartup特性
HostingStartup 特性
HostingStartup特性用于标识哪个类是HostingStartup类,HostingStartup类需要实现IHostingStartup接口。
当程序启动时,会自动扫描入口程序集和配置的待激活的的程序集列表(参见下方:激活HostingStarup程序集),来找到所有的HostingStartup特性,并通过反射的方式创建Startup并调用Configure方法。
using SkyApm.Agent.AspNetCore;
[assembly: HostingStartup(typeof(SkyApmHostingStartup))]
namespace SkyApm.Agent.AspNetCore
{
internal class SkyApmHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()));
}
}
}
激活HostingStarup程序集
要激活HostingStarup程序集,我们有两种配置方式:
1.使用环境变量(推荐)
使用环境变量,无需侵入程序代码,所以我更推荐大家使用这种方式。
配置环境变量ASPNETCORE_HOSTINGSTARTUPASSEMBLIES,多个程序集使用分号(;)进行分隔,用于添加要激活的程序集。变量WebHostDefaults.HostingStartupAssembliesKey就是指代这个环境变量的Key。
另外,还有一个环境变量,叫做ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES,多个程序集使用分号(;)进行分隔,用于排除要激活的程序集。变量WebHostDefaults.HostingStartupExcludeAssembliesKey就是指代这个环境变量的Key。
我们在 launchSettings.json 中添加两个程序集:
"environmentVariables": {
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore;HostingStartupLibrary"
}
2.在程序中配置
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseSetting(
WebHostDefaults.HostingStartupAssembliesKey,
"SkyAPM.Agent.AspNetCore;HostingStartupLibrary")
.UseStartup<Startup>();
});
这样就配置完成了,很的一个功能点吧!
需要注意的是,无论使用哪种配置方式,当存在多个HostingStartup程序集时,将按配置这些程序集时的书写顺序执行 Configure方法。
多环境配置
一款软件,一般要经过需求分析、设计编码,单元测试、集成测试以及系统测试等一系列测试流程,验收,最终上线。那么,就至少需要4套环境来保证系统运行:
Development:开发环境,用于开发人员在本地对应用进行调试运行Test:测试环境,用于测试人员对应用进行测试Staging:预发布环境,用于在正式上线之前,对应用进行集成、测试和预览,或用于验收Production:生产环境,应用的正式线上环境
环境配置方式
通过环境变量ASPNETCORE_ENVIRONMENT指定运行环境
注意:如果未指定环境,默认情况下,为 Production
在项目的Properties文件夹里面,有一个“launchSettings.json”文件,该文件是用于配置VS中项目启动的。
接下来我们就在launchSettings.json中配置一下。
先解释一下该文件中出现的几个参数:
commandName:指定要启动的Web服务器,有三个可选值:- Project:启动 Kestrel
- IISExpress:启动IIS Express
- IIS:不启用任何Web服务器,使用IIS
dotnetRunMessages:bool字符串,指示当使用 dotnet run 命令时,终端能够及时响应并输出消息,具体参考stackoverflow和github issuelaunchBrowser:bool值,指示当程序启动后,是否打开浏览器launchUrl:默认启动路径applicationUrl:应用程序Url列表,多个URL之间使用分号(;)进行分隔。当launchBrowser为true时,将{applicationUrl}/{launchUrl}作为浏览器默认访问的UrlenvironmentVariables:环境变量集合,在该集合内配置环境变量
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
// 如果不指定profile,则默认选择第一个
// Development
"ASP.NET.WebAPI": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
// Test
"ASP.NET.WebAPI.Test": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Test"
}
},
// Staging
"ASP.NET.WebAPI.Staging": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
}
},
// Production
"ASP.NET.WebAPI.Production": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
},
// 用于测试在未指定环境时,默认是否为Production
"ASP.NET.WebAPI.Default": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5000"
}
}
}
配置完成后,就可以在VS上方工具栏中的项目启动处选择启动项了
基于环境的 Startup
Startup类支持针对不同环境进行个性化配置,有三种方式:
- 将
IWebHostEnvironment注入 Startup 类 - Startup 方法约定
- Startup 类约定
1.将IWebHostEnvironment注入 Startup 类
通过将IWebHostEnvironment注入 Startup 类,然后在方法中使用条件判断书写不同环境下的代码。该方式适用于多环境下,代码差异较少的情况。
public class Startup
{
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
public void ConfigureServices(IServiceCollection services)
{
if (WebHostEnvironment.IsDevelopment())
{
Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
}
else if (WebHostEnvironment.IsTest())
{
Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
}
else if (WebHostEnvironment.IsStaging())
{
Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
}
else if (WebHostEnvironment.IsProduction())
{
Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
}
}
public void Configure(IApplicationBuilder app)
{
if (WebHostEnvironment.IsDevelopment())
{
Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
}
else if (WebHostEnvironment.IsTest())
{
Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
}
else if (WebHostEnvironment.IsStaging())
{
Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
}
else if (WebHostEnvironment.IsProduction())
{
Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
}
}
}
public static class AppHostEnvironmentEnvExtensions
{
public static bool IsTest(this IHostEnvironment hostEnvironment)
{
if (hostEnvironment == null)
{
throw new ArgumentNullException(nameof(hostEnvironment));
}
return hostEnvironment.IsEnvironment(AppEnvironments.Test);
}
}
public static class AppEnvironments
{
public static readonly string Test = nameof(Test);
}
2.Startup 方法约定
上面的方式把不同环境的代码放在了同一个方法中,看起来比较混乱也不容易区分。因此我们希望ConfigureServices和Configure能够根据不同的环境进行代码拆分。
我们可以通过方法命名约定来解决,约定Configure{EnvironmentName}Services和Configure{EnvironmentName}Services来装载不同环境的代码。如果当前环境没有对应的方法,则使用原来的ConfigureServices和Configure方法。
我就只拿 Development 和 Production 举例了
public class Startup
{
// 我这里注入 IWebHostEnvironment,仅仅是为了打印出来当前环境信息
public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
#region ConfigureServices
private void StartupConfigureServices(IServiceCollection services)
{
Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
}
public void ConfigureDevelopmentServices(IServiceCollection services)
{
StartupConfigureServices(services);
}
public void ConfigureProductionServices(IServiceCollection services)
{
StartupConfigureServices(services);
}
public void ConfigureServices(IServiceCollection services)
{
StartupConfigureServices(services);
}
#endregion
#region Configure
private void StartupConfigure(IApplicationBuilder app)
{
Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
}
public void ConfigureDevelopment(IApplicationBuilder app)
{
StartupConfigure(app);
}
public void ConfigureProduction(IApplicationBuilder app)
{
StartupConfigure(app);
}
public void Configure(IApplicationBuilder app)
{
StartupConfigure(app);
}
#endregion
}
3.Startup 类约定
该方式适用于多环境下,代码差异较大的情况。
程序启动时,会优先寻找当前环境命名符合Startup{EnvironmentName}的 Startup 类,如果找不到,则使用名称为Startup的类
首先,CreateHostBuilder方法需要做一处修改
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
//webBuilder.UseStartup<Startup>();
webBuilder.UseStartup(typeof(Startup).GetTypeInfo().Assembly.FullName);
});
接下来,就是为各个环境定义 Startup 类了(我就只拿 Development 和 Production 举例了)
public class StartupDevelopment
{
// 我这里注入 IWebHostEnvironment,仅仅是为了打印出来当前环境信息
public StartupDevelopment(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
public void ConfigureServices(IServiceCollection services)
{
Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
}
public void Configure(IApplicationBuilder app)
{
Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
}
}
public class StartupProduction
{
public StartupProduction(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
{
Configuration = configuration;
WebHostEnvironment = webHostEnvironment;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment WebHostEnvironment { get; }
public void ConfigureServices(IServiceCollection services)
{
Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
}
public void Configure(IApplicationBuilder app)
{
Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
}
}
理解ASP.NET Core - [01] Startup的更多相关文章
- 目录-理解ASP.NET Core
<理解ASP.NET Core>基于.NET5进行整理,旨在帮助大家能够对ASP.NET Core框架有一个清晰的认识. 目录 [01] Startup [02] Middleware [ ...
- 理解ASP.NET Core - [04] Host
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 本文会涉及部分 Host 相关的源码,并会附上 github 源码地址,不过为了降低篇幅,我会 ...
- 理解ASP.NET Core - 路由(Routing)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 Routing Routing(路由):更准确的应该叫做Endpoint Routing,负责 ...
- 深入理解ASP.NET Core依赖注入
概述 ASP.NET Core可以说是处处皆注入,本文从基础角度理解一下原生DI容器,及介绍下怎么使用并且如何替换官方提供的默认依赖注入容器. 什么是依赖注入 百度百科中对 ...
- 理解ASP.NET Core - [02] Middleware
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 中间件 先借用微软官方文档的一张图: 可以看到,中间件实际上是一种配置在HTTP请求管道中,用 ...
- 理解ASP.NET Core - [03] Dependency Injection
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 依赖注入 什么是依赖注入 简单说,就是将对象的创建和销毁工作交给DI容器来进行,调用方只需要接 ...
- 理解ASP.NET Core - 配置(Configuration)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 配置提供程序 在.NET中,配置是通过多种配置提供程序来提供的,包括以下几种: 文件配置提供程 ...
- 理解ASP.NET Core - 选项(Options)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 Options绑定 上期我们已经聊过了配置(IConfiguration),今天我们来聊一聊O ...
- 理解ASP.NET Core - 文件服务器(File Server)
注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 提供静态文件 静态文件默认存放在 Web根目录(Web Root) 中,路径为 项目根目录(C ...
随机推荐
- github在不同电脑上协同开发
当我换了电脑后,开发自己的github项目遇到了一些问题. 首先,git clone 'repository url'拉取下来项目,开始开发项目发.修改了一些文件后,当要git commit, git ...
- Maven工程 报 Diamond types are not supported at language level '5'
Maven工程 报 Diamond types are not supported at language level '5' 出现这种信息,一般表示的是你的language level(IDEA下J ...
- 【LOJ 109 并查集】 并查集
题目描述 这是一道模板题. 维护一个 n 点的无向图,支持: 加入一条连接 u 和 v 的无向边 查询 u 和 v 的连通性 由于本题数据较大,因此输出的时候采用特殊的输出方式:用 0 或 1 代表每 ...
- Windows环境安装kafka
前言 注意事项: 需要有jdk,jdk8以上.配置好环境变量. 参看链接:https://blog.csdn.net/weixin_38004638/article/details/91893910 ...
- 破解加速乐-java
记录一哈自己遇到的简单站点的破解 Talk is cheap,show you the code! import com.google.gson.Gson; import com.google.gso ...
- Python的round()函数与数学的四舍五入的区别
print(round(0.5))>>0print(round(1.5))>>2print(round(2.5))>>2整数部分为偶 小数为0.5 向下取整 0也是 ...
- template.js模板工具案例
案例一 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset=&qu ...
- Android音视频开发(1):H264 基本原理
前言 H264 视频压缩算法现在无疑是所有视频压缩技术中使用最广泛,最流行的.随着 x264/openh264 以及 ffmpeg 等开源库的推出,大多数使用者无需再对H264的细节做过多的研究,这大 ...
- 记一次在Windows10桌面环境搭建Jekins的吐血经历
目录 写在前面 故事背景 踩坑详情 最后总结 写在前面 首先声明,除非万不得已,千万不要在Windows环境做这个事情,否则就等着各种坑吧. 本人一贯的立场都是坚持用正确的方法做事,显然在Window ...
- Qt项目简易开发原理及常见问题解决
一.资源下载地址 https://www.aliyundrive.com/s/jBU2wBS8poH 本项目路径:项目->免费->QtDev 注释:为了方便qt全功能开发,QtDev中包含 ...