前言 PMP是什么梗? 项目管理专业人士资格认证。它是由美国项目管理协会(Project Management Institute(PMI)发起的,严格评估项目管理人员知识技能是否具有高品质的资格认证考试。其目的是为了给项目管理人员提供统一的行业标准。目前,美国项目管理协会建立的认证考试有:PMP(项目管理师)和CAPM(项目管理助理师)已在全世界190多个国家和地区设立了认证考试机构。 可能有一部分程序员伙伴不了解PMP是什么?但应该没有撸码的不知道项目经理这个称谓吧?记得在学校时,老师给我们灌输这样一种思想,你做个两三年,当上一个公司的项目经理,就不用敲代码的,天天布置任务给别人做就可以了。听到这等好事,我当时两眼直冒绿光….. 如标题所示,小弟于今天中午已经结束了几个月的备考期,那首先来说下PMP的考试内容 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ PMP考试内容主要包括项目管理五个过程: 启动:确立一个项目或一个项目阶段。 规划:为完成项目,制定和维护一个可操作的计划。 执行:协调人力和其他资源以执行计划。 监控:通过监控和进度测量及必要时采取纠正措施以确保项目目标的实现。 收尾:正式验收项目或项目阶段并使其有条不紊地圆满结束。 PMP考试采用客观的选择题形式,共计200题,其中25题不计成绩。答题时间:9:00~13:00,共计4个小时。从2005年9月30日开始,报名PMP考试的新生的及格线为61%(200道单选题中抽查25道不计分,即答对106道题) 有小伙伴看到这里不禁发出轻蔑的奸笑,搞了半天原来都是选择题,那太Soesay了.额。。。先别急着下结论,让我们先看看考PMP的最重要的一本书(这里说的是最重要的,意思就是还有其他辅助书籍,你懂得) (PMP考试主教材) 那要小伙伴就比较好奇了,看这本书的侧面貌似有些厚度,那这本书里面都有哪些内容呢? 恩,这个问题问的好~ 项目管理知识体系指南(以下简称PMBOK)把项目管理过程分为五类: 1) 启动。成立项目组开始项目或进入项目的新阶段。启动是一种认可过程,用来正式认可一个新项目或新阶段的存在。 2) 计划。定义和评估项目目标,选择实现项目目标的最佳策略,制定项目计划。 3) 执行。调动资源,执行项目计划。 4) 控制。监控和评估项目偏差,必要时采取纠正行动,保证项目计划的执行,实现项目目标。 5) 结束。正式验收项目或阶段,使其按程序结束。 PMBOK将项目管理划分为9个知识领域: 1、项目整体管理(Project Integration Management) 项目整体管理是为了正确地协调项目所有各组成部分而进行的各个过程的集成, 是一个综合性过程。 其核心就是在多个互相冲突的目标和方案之间作出权衡, 以便满足项目利害关系者的要求。 2、项目范围管理(Project Scope Management) 项目范围管理就是确保项目不但完成全部规定要做的, 而且也仅仅是完成规定要做的工作,最终成功地达到项目的目的。基本内容是定义和控制列入或未列入项目的事项。 3、项目时间管理(Project Time Management) 其作用是保证在规定时间内完成项目。 4、项目费用管理(Project Cost Management) 项目费用管理, 是为了保证在批准的预算内完成项目所必需的诸过程的全体。 5、项目质量管理(Project Quality Management) 项目质量管理, 是为了保证项目能够满足原来设定的各种要求。 6、项目人力资源管理(Project Human Resource Management) 项目人力资源管理, 是为了保证最有效地使用参加项目者的个别能力。 7、项目沟通管理(Project Communications Management) 项目沟通管理, 是在人、思想和信息之间建立联系, 这些联系对于取得成功是必不可少的。参与项目的每一个人都必须准备用项目“语言”进行沟通, 并且要明白, 他们个人所参与的沟通将会如何影响到项目的整体。 项目沟通管理是保证项目信息及时、准确地提取、收集、传播、存贮以及最终进行处置。 8、项目风险管理(Project Risk Management) 项目风险管理, 需要的过程有识别、分析不确定的因素, 并对这些因素采取应对措施。 项目风险管理要把有利事件的积极结果尽量扩大, 而把不利事件的后果降低到最低程度。 9、项目采购管理(Project Procurement Management) 项目采购管理, 需要进行的过程都是为了从项目组织外部获取货物或服务。 10、项目干系人管理(stakeholder management) […]
View Details症状:验证码上只有背景图没有文字 Warning: imagettftext() [function.imagettftext]: Could not find/open font in ***.php on line ** 解决方案:处理掉中文目录!! 网上搜索到的答案大多是字体文件目录不对或者相对或绝对路径方面的问题,写下这段文字给后面需要填坑的朋友。 from:https://xiumu.org/note/php-imagettftext-could-not-find-open-the-font-errors.shtml
View Detailsmaven依赖的项目报错Could not resolve dependencies for project问题 maven如果依赖本地项目 1.例如自己本地项目是模块化的 首先将报错的模块执行 clean 和package 2. 然后执行install,将上一步执行成功的jar文件安装到responsitory 3.最后执行 mvn clean install -Dmaven.test.skip=true from:https://blog.csdn.net/zgy_boke/article/details/85243453
View Detailshttp://www.bubuko.com/infodetail-2130667.html 使用maven创建子工程时,子工程需要依赖父工程,创建以后报错Project build error: Non-resolvable parent POM: Failure to find…… 网上的答案绝大多数都是在<parent></parent>中间加一行:<relativePath>../../pom.xml</relativePath>(原因是找不到路径) 但是我加了依旧没有解决,我的解决方法是,在子项目名点击右键--> run as -->maven install。待自动下载完包后就便可以解决错误 from:https://blog.csdn.net/baidu_18607183/article/details/80479925
View Details在本部分中,将修改 HelloWorldController 类,进而使用 Razor 视图文件来顺利封装为客户端生成 HTML 响应的过程。 使用 Razor 创建视图模板文件。 当前,Index 方法返回带有在控制器类中硬编码的消息的字符串。 C#
|
1 2 3 4 |
public IActionResult Index() { return View(); } |
上面的代码调用控制器的 View 方法。 添加视图 Visual Studio Visual Studio Code Visual Studio for Mac 右键单击“视图”文件夹,然后单击“添加”>“新文件夹”,并将文件夹命名为“HelloWorld”。 右键单击“Views/HelloWorld”文件夹,然后单击“添加”>“新项”。 在“添加新项 – MvcMovie”对话框中 在右上角的搜索框中,输入“视图” 选择“Razor 视图” 保持“名称”框的值:Index.cshtml 。 选择“添加” 为 HelloWorldController 添加 Index 视图。 添加一个名为“Views/HelloWorld”的新文件夹。 向 Views/HelloWorld 文件夹添加名为“Index.cshtml”的新文件。 右键单击“视图”文件夹,然后单击“添加”>“新文件夹”,并将文件夹命名为“HelloWorld”。 右键单击“Views/HelloWorld”文件夹,然后单击“添加”>“新文件” 。 在“新建文件”对话框中 : 在左窗格中,选择“Web” 。 在中间窗格中,选择“空 HTML 文件” 。 在“名称”框中键入“Index.cshtml” 。 选择“新建” 。 使用以下内容替换 Razor 视图文件 Views/HelloWorld/Index.cshtml 的内容 : HTML
|
1 |
@{ ViewData["Title"] = "Index"; } <h2>Index</h2> <p>Hello from our View Template!</p> |
导航到 https://localhost:{PORT}/HelloWorld。 更改视图和布局页面 选择菜单链接(“MvcMovie”、“首页”和“隐私”) 。 布局模板使你能够在一个位置指定网站的 HTML 容器布局,然后将它应用到网站中的多个页面。 更改布局文件中的标题、页脚和菜单链接 在标题和页脚元素中,将 MvcMovie 更改为 Movie App。 将定位点元素 <a […]
View Details众所周知,在asp.net core中编写Razor视图的时候,用了一种新的写法--TagHelper 那这个TagHelper是怎么回事呢? 首先来看看TagHelper的项目位置,它是位于Microsoft.AspNetCore.Mvc.TagHelpers。 如果看到project.json,可以发现,它还依赖一个比较重要的东西Microsoft.AspNetCore.Mvc.Razor 为什么这么说呢,其实很简单,看了里面诸多TagHelper,就会发现,里面都是继承了 Microsoft.AspNetCore.Razor.TagHelpers下面的TagHelper这个抽象类。 下面就以我们天天用到的表单--FormTagHelper为例来说一下,他是怎么实现的。 首先要看看TagHelper这个抽象类:
|
1 2 3 4 5 6 7 8 |
public abstract class TagHelper : ITagHelper { protected TagHelper(); public virtual int Order { get; } public virtual void Init(TagHelperContext context); public virtual void Process(TagHelperContext context, TagHelperOutput output); public virtual Task ProcessAsync(TagHelperContext context, TagHelperOutput output); } |
里面包含两比较重要的方法:Process和ProcessAsync 其实看方法名就应该知道一个是同步的方法一个是异步的方法 因为这个是输出html的方法,你说,这能不重要吗?下面来看看FormTagHelper的具体实现吧!
|
1 |
[HtmlTargetElement("form", Attributes = ActionAttributeName)] |
先来看看HtmlTargetElement这个Attribute是用来干嘛的 简单来说,它指定了我们html标签(<form></form>)以及一些相关的元素。 可以看到,诸多Attributes = XXXAttributeName,其中的XXXAttributeName是在类里面定义的变量。
|
1 2 3 4 5 6 7 8 |
private const string ActionAttributeName = "asp-action"; private const string AntiforgeryAttributeName = "asp-antiforgery"; private const string AreaAttributeName = "asp-area"; private const string ControllerAttributeName = "asp-controller"; private const string RouteAttributeName = "asp-route"; private const string RouteValuesDictionaryName = "asp-all-route-data"; private const string RouteValuesPrefix = "asp-route-"; private const string HtmlActionAttributeName = "action"; |
再来看看下面的图,相对比一看,是不是就很清晰了呢? 我们可以看到下面的好几个属性,如Controller,它的上面是有 HtmlAttributeName来标注的 而且这个指向的名字还是ControllerAttributeName(也就是asp-controller)。这个就是用来接收asp-controller的值。
|
1 2 |
[HtmlAttributeName(ControllerAttributeName)] public string Controller { get; set; } |
相对来说,这样做只是起了个别名。
|
1 2 3 4 5 6 7 8 |
[HtmlTargetElement("form", Attributes = ActionAttributeName)] [HtmlTargetElement("form", Attributes = AntiforgeryAttributeName)] [HtmlTargetElement("form", Attributes = AreaAttributeName)] [HtmlTargetElement("form", Attributes = ControllerAttributeName)] [HtmlTargetElement("form", Attributes = RouteAttributeName)] [HtmlTargetElement("form", Attributes = RouteValuesDictionaryName)] [HtmlTargetElement("form", Attributes = RouteValuesPrefix + "*")] public class FormTagHelper : TagHelper |
当然,我们也是可以不指定别名的,也可以不用在HtmlTargetElement指明Attributes 好比如下的代码,就可以直接用Controller
|
1 2 3 4 5 |
[HtmlTargetElement("form")] public class FormTagHelper : TagHelper { public string Controller { get; set; } } |
还有一个RouteValues的属性,它是一个键值对,用来存放参数的,具体可以怎么用呢? 总的来说有两种用法。可以看到它指向asp-all-route-data和asp-route-
|
1 |
[HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] |
用法如下:一种是用asp-all-route-data来接收一个IDictionary类型的变量,一种是通过asp-route-*的方式来接收参数*的值。 这两种写法是等价的。 下面就是FormTagHelper的构造函数和一个Generator属性
|
1 2 3 4 5 |
public FormTagHelper(IHtmlGenerator generator) { Generator = generator; } protected IHtmlGenerator Generator { get; } |
由于在Core中,依赖注入随处可见,看到这个写法马上就是想到了这个 果不其然,发现其对应了一个实现类:DefaultHtmlGenerator。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class DefaultHtmlGenerator : IHtmlGenerator { public DefaultHtmlGenerator(IAntiforgery antiforgery, IOptions<MvcViewOptions> optionsAccessor, IModelMetadataProvider metadataProvider, IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder, ClientValidatorCache clientValidatorCache); public virtual TagBuilder GenerateActionLink(ViewContext viewContext, string linkText, string actionName, string controllerName, string protocol, string hostname, string fragment, object routeValues, object htmlAttributes); public virtual IHtmlContent GenerateAntiforgery(ViewContext viewContext); public virtual TagBuilder GenerateForm(ViewContext viewContext, string actionName, string controllerName, object routeValues, string method, object htmlAttributes); public virtual TagBuilder GenerateLabel(ViewContext viewContext, ModelExplorer modelExplorer, string expression, string labelText, object htmlAttributes); public virtual TagBuilder GenerateTextArea(ViewContext viewContext, ModelExplorer modelExplorer, string expression, int rows, int columns, object htmlAttributes); public virtual TagBuilder GenerateTextBox(ViewContext viewContext, ModelExplorer modelExplorer, string expression, object value, string format, object htmlAttributes); protected virtual TagBuilder GenerateInput(ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, string expression, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format, IDictionary<string, object> htmlAttributes); protected virtual TagBuilder GenerateLink(string linkText, string url, object htmlAttributes); ....省略部分 } |
这个类里面,我们看到了熟悉的TagBuilder,就算不去看它里面的实现都能知道它是用来干嘛的 它就是用来创建我们的Html标签,相信用过MVC的,多多少少都扩展过HtmlHelper,这是类似的。 最后,也是最最重要的重写的Process方法。 可以看到开始就判断了表单<form>中是否包含了action这个属性output.Attributes.ContainsName(HtmlActionAttributeName) 如果包含,就是正常的html标签。换句话说,正常的html写法和我们的TagHelper方法会有冲突,只能用其中一种。 当我们这样写的时候,编译能通过。 但是,运行的时候就会出错。 再下面的处理就是用了TagBuilder去处理了。 收集路由的数据放到一个字典中->区域是否存在->用Generator去创建form表单,返回TagBuilder对象->TagHelperOutput对象把tagbuilder的innerhtml等信息输出。 如下面的写法:
|
1 2 3 |
<form method="post" asp-action="Get" asp-controller="Product" asp-antiforgery="false" asp-route-id="2"> <button type="submit">submit</button> </form> |
生成对应的html如下:
|
1 2 3 |
<form method="post" action="/Product/Get/2"> <button type="submit">submit</button> </form> |
到这里,FormTagHelper的讲解就算是OK,至于其他的,原理都是差不多,就不再累赘了。 来看看,到底有多少种TagHelper(还没有部分没有列出来),以及它们包含的属性。 下面是我们自己写一个TagHelper——CatcherATagHelper,这个TagHelper是干什么的呢?它只是一个精简版的A标签。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; namespace Catcher.EasyDemo.Controllers.TagHelpers { [HtmlTargetElement("catcher-a")] public class CatcherATagHelper:TagHelper { public CatcherATagHelper(IHtmlGenerator generator, IUrlHelperFactory urlHelperFactory) { this.Generator = generator; UrlHelperFactory = urlHelperFactory; } [HtmlAttributeNotBound] public IUrlHelperFactory UrlHelperFactory { get; } protected IHtmlGenerator Generator { get; } public override int Order { get { return -1000; } } public string Action { get; set; } public string Controller { get; set; } public string LinkText { get; set; } [ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { //method 1 if (Action != null || Controller != null) { output.Attributes.Clear(); var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext); output.TagName = "a"; output.Attributes.SetAttribute("href", urlHelper.Action(Action, Controller)); //whether the inner html is null if (output.Content.IsEmptyOrWhiteSpace) { output.PreContent.SetContent(LinkText); } } //method 2 //TagBuilder tagBuilder; //if (Action != null || Controller != null) //{ // tagBuilder = Generator.GenerateActionLink( // ViewContext, // linkText: string.Empty, // actionName: Action, // controllerName: Controller, // protocol: string.Empty, // hostname: string.Empty, // fragment: string.Empty, // routeValues: null, // htmlAttributes: null); // output.TagName = "a"; // //whether the inner html is null // if (output.Content.IsEmptyOrWhiteSpace) // { // output.PreContent.SetContent(LinkText); // } // output.MergeAttributes(tagBuilder); //} } } } |
这里提供了两种写法供大家参考 一种是借助IUrlHelperFactory去生成链接 一种是借助IHtmlGenerator去生成链接 写好了之后要怎么用呢? 不知道大家有没有留意_ViewImports.cshtml这个文件
|
1 2 3 |
@using Catcher.EasyDemo.Website @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @inject Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration TelemetryConfiguration |
这个是默认情况下帮我们添加的TagHelper 我们可以在要用到那个TagHelper的地方添加就好 Index.cshtml addTagHelper的用法如下: @addTagHelper 你的TagHelper , 你的TagHelper所在的命名空间 或者更直接 @addTagHelper * , 你的TagHelper所在的命名空间 可以添加,当然也可以删除,删除是@removeTagHelper 当我们在自己的框架中完全重写了一套自己的TagHelper,那么这个时候,微软自己的TagHelper我们就可以通过下面的方法来移除了。 @removeTagHelper […]
View DetailsOcelot是一个用.NET Core实现并且开源的API网关,它功能强大,包括了:路由、请求聚合、服务发现、认证、鉴权、限流熔断、并内置了负载均衡器与Service Fabric、Butterfly Tracing集成。这些功能只都只需要简单的配置即可完成,下面我们会对这些功能的配置一一进行说明。 介绍 简单的来说Ocelot是一堆的asp.net core middleware组成的一个管道。当它拿到请求之后会用一个request builder来构造一个HttpRequestMessage发到下游的真实服务器,等下游的服务返回response之后再由一个middleware将它返回的HttpResponseMessage映射到HttpResponse上。 API网关—— 它是系统的暴露在外部的一个访问入口。这个有点像代理访问的家伙,就像一个公司的门卫承担着寻址、限制进入、安全检查、位置引导、等等功能。 Ocelot的基本使用 用一台web service来host Ocelot,在这里有一个json配置文件,里面设置了所有对当前这个网关的配置。它会接收所有的客户端请求,并路由到对应的下游服务器进行处理,再将请求结果返回。而这个上下游请求的对应关系也被称之为路由。 集成Identity Server 当我们涉及到认证和鉴权的时候,我们可以跟Identity Server进行结合。当网关需要请求认证信息的时候会与Identity Server服务器进行交互来完成。 网关集群 只有一个网关是很危险的,也就是我们通常所讲的单点,只要它挂了,所有的服务全挂。这显然无法达到高可用,所以我们也可以部署多台网关。当然这个时候在多台网关前,你还需要一台负载均衡器。 Consul 服务发现 在Ocelot已经支持简单的负载功能,也就是当下游服务存在多个结点的时候,Ocelot能够承担起负载均衡的作用。但是它不提供健康检查,服务的注册也只能通过手动在配置文件里面添加完成。这不够灵活并且在一定程度下会有风险。这个时候我们就可以用Consul来做服务发现,它能与Ocelot完美结合。 集成网关 在asp.net core 2.0里通过nuget即可完成集成,或者命令行dotnet add package Ocelot以及通过vs2017 UI添加Ocelot nuget引用都可以。
|
1 |
Install-Package Ocelot |
配置 我们需要添加一个.json的文件用来添加Ocelot的配置,以下是最基本的配置信息。
|
1 2 3 4 5 6 |
{ "ReRoutes": [], "GlobalConfiguration": { "BaseUrl": "https://api.mybusiness.com" } } |
要特别注意一下BaseUrl是我们外部暴露的Url,比如我们的Ocelot运行在http://123.111.1.1的一个地址上,但是前面有一个 nginx绑定了域名http://api.jessetalk.cn,那这里我们的BaseUrl就是 http://api.jessetalk.cn。 将配置文件加入ASP.NET Core Configuration 我们需要通过WebHostBuilder将我们添加的json文件添加进asp.net core的配置
|
1 2 3 4 5 6 7 8 9 |
public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration( (hostingContext,builder) => { builder .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("Ocelot.json"); }) .UseStartup<Startup>() .Build(); |
配置依赖注入与中间件 在startup.cs中我们首先需要引用两个命名空间
|
1 2 |
using Ocelot.DependencyInjection; using Ocelot.Middleware; |
接下来就是添加依赖注入和中间件
|
1 2 3 4 |
public void ConfigureServices(IServiceCollection services) { services.AddOcelot(); } |
|
1 2 3 4 5 6 7 8 9 |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseOcelot().Wait(); } |
Ocelot功能介绍 通过配置文件可以完成对Ocelot的功能配置:路由、服务聚合、服务发现、认证、鉴权、限流、熔断、缓存、Header头传递等。在配置文件中包含两个根节点:ReRoutes和GlobalConfiguration。ReRoutes是一个数组,其中的每一个元素代表了一个路由,我们可以针对每一个路由进行以上功能配置。下面是一个完整的路由配置
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
{ "DownstreamPathTemplate": "/", "UpstreamPathTemplate": "/", "UpstreamHttpMethod": [ "Get" ], "AddHeadersToRequest": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, "AddQueriesToRequest": {}, "RequestIdKey": "", "FileCacheOptions": { "TtlSeconds": 0, "Region": "" }, "ReRouteIsCaseSensitive": false, "ServiceName": "", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 51876, } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "LoadBalancer": "", "RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": false, "Period": "", "PeriodTimespan": 0, "Limit": 0 }, "AuthenticationOptions": { "AuthenticationProviderKey": "", "AllowedScopes": [] }, "HttpHandlerOptions": { "AllowAutoRedirect": true, "UseCookieContainer": true, "UseTracing": true }, "UseServiceDiscovery": false } |
Downstream是下游服务配置 UpStream是上游服务配置 Aggregates 服务聚合配置 ServiceName, LoadBalancer, UseServiceDiscovery 配置服务发现 AuthenticationOptions 配置服务认证 RouteClaimsRequirement 配置Claims鉴权 RateLimitOptions为限流配置 FileCacheOptions 缓存配置 QosOptions 服务质量与熔断 DownstreamHeaderTransform头信息转发 我们接下来将对这些功能一一进行介绍和配置 路由 路由是API网关最基本也是最核心的功能、ReRoutes下就是由多个路由节点组成。
|
1 2 3 4 |
{ "ReRoutes": [ ] } |
而每一个路由由以下几个基本信息组成: 下面这个配置信息就是将用户的请求 /post/1 转发到 localhost/api/post/1
|
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "DownstreamPathTemplate": "/api/post/{postId}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 80, } ], "UpstreamPathTemplate": "/post/{postId}", "UpstreamHttpMethod": [ "Get"] } |
DownstreamPathTemplate:下游戏 DownstreamScheme:下游服务http schema DownstreamHostAndPorts:下游服务的地址,如果使用LoadBalancer的话这里可以填多项 UpstreamPathTemplate: 上游也就是用户输入的请求Url模板 […]
View Details比赛规则
1.统一使用Realse版本的最新 DLL,Realse模式启用程序
2.为了平衡CPU和数据库空闲情况,使用车轮战,每场比赛连续10回合比试
3.多次重启电脑取平均成绩上图
比赛成员
1.SqlSugar 3.1.01
2.Dapper 1.5.0.2 Dapper.Contrib 1.5 官方DLL
View Details用户登录是一个非常常见的应用场景 .net core 2.0 的登录方式发生了点变化,应该是属于是良性的变化,变得更方便,更容易扩展。 配置 打开项目中的Startup.cs文件,找到ConfigureServices方法,我们通常在这个方法里面做依赖注入的相关配置。添加如下代码:
|
1 2 3 4 5 6 7 8 9 |
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => { o.LoginPath = new PathString("/Account/Login"); o.AccessDeniedPath = new PathString("/Error/Forbidden"); }); } |
这段代码的大概意思就是,添加授权支持,并添加使用Cookie的方式,配置登录页面和没有权限时的跳转页面。 再找到Configure方法,添加 app.UseAuthentication(),使用授权:
|
1 2 3 4 |
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseAuthentication(); } |
这样基本的配置就完成了。 登录 添加一个Controller,如AccountController,再添加一个Action,如 Login,所配置的路由,要与上面的配置对应,不然跳转登录时会跳错页面。 用户提交用户名和密码,登录代码大致如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[HttpPost] public async Task <IActionResult> Login(string userName, string password, string ReturnUrl) { var user = _userService.Login(userName, password); if (user != null) { user.AuthenticationType = CookieAuthenticationDefaults.AuthenticationScheme; var identity = new ClaimsIdentity(user); identity.AddClaim(new Claim(ClaimTypes.Name, user.UserID)); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); if (ReturnUrl.IsNullOrEmpty()) { return RedirectToAction("Index", "Dashboard"); } return Redirect(ReturnUrl); } ViewBag.Errormessage = "登录失败,用户名密码不正确"; return View(); } |
这里要注意的是 AuthenticationType 所设置的Scheme一定要与前面的配置一样,这样对应的登录授权才会生效。 使用登录身份 登录的目录,就是希望有些页面或者资源只有登录以后才可访问。使用AuthorizeAttribute来做限制。在需要做限制的Controller上加上[Authorize]特性来做限制。
|
1 2 3 4 |
[Authorize] public class ThemeController { } |
这样这个Controller下的所有的Action都必需要登录后才可访问。如果希望其中某些Action可以不用登录也可访问,可以添加例外:
|
1 2 3 4 5 |
[AllowAnonymous] public ActionResult Index() { return View(); } |
到这里一个最基础的登录就完成了。 在Web项目中,通常会遇到一个问题,后端管理员和前台用户。这两个用户都是可登录的,在 .net core 2.0,这个将很容易实现。 多用户登录 添加一个登录方案(Scheme) CookieAuthenticationDefaults.AuthenticationScheme,这是系统已经定义好的一个默认的登录方案,添加一个新的来实现一个不同的身份登录。代码如下:
|
1 2 3 4 5 6 7 8 |
public class CustomerAuthorizeAttribute : AuthorizeAttribute { public const string CustomerAuthenticationScheme = "CustomerAuthenticationScheme"; public CustomerAuthorizeAttribute() { this.AuthenticationSchemes = CustomerAuthenticationScheme; } } |
添加使用这个新的方案,在Startup.cs文件下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => { o.LoginPath = new PathString("/Account/Login"); o.AccessDeniedPath = new PathString("/Error/Forbidden"); }) .AddCookie(CustomerAuthorizeAttribute.CustomerAuthenticationScheme, option => { option.LoginPath = new PathString("/Account/Signin"); option.AccessDeniedPath = new PathString("/Error/Forbidden"); }); } |
添加新的登录方案,并配置一个新的登录页面,登录的方法和刚才是一样,只是AuthenticationType使用了新的方案。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[HttpPost] public async Task <IActionResult> Login(string userName, string password, string ReturnUrl) { var user = _userService.Login(userName, password); if (user != null) { user.AuthenticationType = <strong>CustomerAuthorizeAttribute.CustomerAuthenticationScheme</strong>; var identity = new ClaimsIdentity(user); identity.AddClaim(new Claim(ClaimTypes.Name, user.UserID)); await HttpContext.SignInAsync(<strong>CustomerAuthorizeAttribute.CustomerAuthenticationScheme</strong>, new ClaimsPrincipal(identity)); if (ReturnUrl.IsNullOrEmpty()) { return RedirectToAction("Index", "Dashboard"); } return Redirect(ReturnUrl); } ViewBag.Errormessage = "登录失败,用户名密码不正确"; return View(); } |
验证登录状态 使用方法和之前的差不多,换成新的CustomerAuthorizeAttribute就行了:
|
1 2 3 4 |
[CustomerAuthorize] public class CustomerController { } |
CustomerAuthorizeAttribute这个类,不是必需的,只是为了方便使用而写,其实完全可以只定义一个新的方案(Scheme)就行了。 谁才是HttpContext.User? 登录了多个用户,那么谁才是HttpContext.User呢?如果你的Controller或者Action上有使用AuthorizeAttribute,那这个Attribute使用的登录方案是哪个,则这个HttpContext.User对应的就是那个方案的登录用户。如果没有使用,则AddAuthentication()方法默认指它的方案(Scheme)所登录的用户,就是这个HttpContext.User了。 如何获取对应方案的登录用户呢?使用HttpContext.AuthenticateAsync
|
1 2 3 4 5 |
var auth = await HttpContext.AuthenticateAsync(CustomerAuthorizeAttribute.CustomerAuthenticationScheme); if (auth.Succeeded) { auth.Principal.Identity... } |
退出登录 这个就简单了,指定方案退出就可以了。
|
1 2 3 4 5 |
public async Task Logout(string returnurl) { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Redirect(returnurl ?? "~/"); } |
from:http://www.zkea.net/codesnippet/detail/asp-net-core-multi-login-user.html
View Details大家在使用ASP.NET的时候一定都用过FormsAuthentication做登录用户的身份认证,FormsAuthentication的核心就是Cookie,ASP.NET会将用户名存储在Cookie中。 现在到了ASP.NET CORE的时代,但是ASP.NET CORE中没有FormsAuthentication这个东西,那么怎么做身份认证呢?答案是ASP.NET CORE已经为我们内置了Cookie身份认证的功能,而且使用起来非常方便,注意本文是基于ASP.NET CORE 2.0版本来阐述Cookie认证方式的。 1.从ASP.NET CORE OWIN框架中启用Cookie身份认证功能 要在ASP.NET CORE中使用Cookie身份认证,第一步就是在项目中的OWIN框架文件Startup.cs中启用Cookie身份认证中间件。 首先我们在Startup中的ConfigureServices方法中使用services.AddAuthentication注册Cookie认证服务,如下代码所示:
|
1 2 3 4 5 6 7 |
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); //注册Cookie认证服务 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); } |
然后在Startup中的Configure方法中使用app.UseAuthentication启用Cookie认证中间件(注意其中app.UseAuthentication和app.UseMvc的调用顺序不能反),如下代码所示:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); //注意app.UseAuthentication方法一定要放在下面的app.UseMvc方法前面,否者后面就算调用HttpContext.SignInAsync进行用户登录后,使用 //HttpContext.User还是会显示用户没有登录,并且HttpContext.User.Claims读取不到登录用户的任何信息。 //这说明Asp.Net OWIN框架中MiddleWare的调用顺序会对系统功能产生很大的影响,各个MiddleWare的调用顺序一定不能反 app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } |
这里顺便说一下app.UseAuthentication是用来干什么的,app.UseAuthentication会启用Authentication中间件,该中间件会根据当前Http请求中的Cookie信息来设置HttpContext.User属性(后面会用到),所以只有在app.UseAuthentication方法之后注册的中间件才能够从HttpContext.User中读取到值,这也是为什么上面强调app.UseAuthentication方法一定要放在下面的app.UseMvc方法前面,因为只有这样ASP.NET Core的MVC中间件中才能读取到HttpContext.User的值。 2.登录用户 在ASP.NET CORE中使用Cookie认证登录用户的方法和传统的FormsAuthentication不太一样,大致步骤如下: 创建Claim类型的数组,将登录用户的所有信息(比如用户名)存储在Claim类型的字符串键值对中 将上面创建的Claim类型的数组传入ClaimsIdentity中,用来构造一个ClaimsIdentity对象 将上面创建的ClaimsIdentity对象传入ClaimsPrincipal中,用来构造一个ClaimsPrincipal对象 调用HttpContext.SignInAsync方法,传入上面创建的ClaimsPrincipal对象,完成用户登录 所以我们可以看到整个ASP.NET CORE的Cookie认证登录流程比以前ASP.NET的FormsAuthentication还是要复杂许多,毕竟以前一个FormsAuthentication.SetAuthCookie方法就搞定了。 在本文的例子中我们在项目中默认的HomeController中创建了一个Acion方法Login,来实现用户登录的代码。当然这里我们实现的是最简的Cookie登录,下面代码中实际上还可以设置Cookie是否持久化、Cookie多久过期、存储登录用户信息的Cookie的名字是什么等,我们就不做过多介绍了,大家可以阅读本文最后推荐的两份官方文档了解更多。 Login方法的代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/// <summary> /// 该Action登录用户Wangdacui到Asp.Net Core /// </summary> public IActionResult Login() { //下面的变量claims是Claim类型的数组,Claim是string类型的键值对,所以claims数组中可以存储任意个和用户有关的信息, //不过要注意这些信息都是加密后存储在客户端浏览器cookie中的,所以最好不要存储太多特别敏感的信息,这里我们只存储了用户名到claims数组, //表示当前登录的用户是谁 var claims = new[] { new Claim("UserName", "Wangdacui") }; var claimsIdentity = new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme); ClaimsPrincipal user = new ClaimsPrincipal(claimsIdentity); Task.Run(async () => { //登录用户,相当于ASP.NET中的FormsAuthentication.SetAuthCookie await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user); //可以使用HttpContext.SignInAsync方法的重载来定义持久化cookie存储用户认证信息,例如下面的代码就定义了用户登录后60分钟内cookie都会保留在客户端计算机硬盘上, //即便用户关闭了浏览器,60分钟内再次访问站点仍然是处于登录状态,除非调用Logout方法注销登录。 //注意其中的AllowRefresh属性,如果AllowRefresh为true,表示如果用户登录后在超过50%的ExpiresUtc时间间隔内又访问了站点,就延长用户的登录时间(其实就是延长cookie在客户端计算机硬盘上的保留时间), //例如本例中我们下面设置了ExpiresUtc属性为60分钟后,那么当用户登录后在大于30分钟且小于60分钟内访问了站点,那么就将用户登录状态再延长到当前时间后的60分钟。但是用户在登录后的30分钟内访问站点是不会延长登录时间的, //因为ASP.NET Core有个硬性要求,是用户在超过50%的ExpiresUtc时间间隔内又访问了站点,才延长用户的登录时间。 //如果AllowRefresh为false,表示用户登录后60分钟内不管有没有访问站点,只要60分钟到了,立马就处于非登录状态(不延长cookie在客户端计算机硬盘上的保留时间,60分钟到了客户端计算机就自动删除cookie) /* await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, user, new AuthenticationProperties() { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60), AllowRefresh = true }); */ }).Wait(); return View(); } |
如果当前Http请求本来登录了用户A,现在调用HttpContext.SignInAsync方法登录用户B,那么相当于注销用户A,登录用户B 3.读取登录用户信息 那么用户登录后怎么将登录用户的信息(比如用户名)读取出来呢?我们在HomeController的Index方法中演示了如何判断当前用户是否已经登录,并且读出登录用户的用户名,Index方法的代码如下所示:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// <summary> /// 该Action判断用户是否已经登录,如果已经登录,那么读取登录用户的用户名 /// </summary> public IActionResult Index() { //如果HttpContext.User.Identity.IsAuthenticated为true, //或者HttpContext.User.Claims.Count()大于0表示用户已经登录 if (HttpContext.User.Identity.IsAuthenticated) { //这里通过 HttpContext.User.Claims 可以将我们在Login这个Action中存储到cookie中的所有 //claims键值对都读出来,比如我们刚才定义的UserName的值Wangdacui就在这里读取出来了 var userName = HttpContext.User.Claims.First().Value; } return View(); } |
注意,最好还是用HttpContext.User.Identity.IsAuthenticated来判断用户是否已经登录 4.注销用户 那么登录用户后怎么注销登录呢?我们在HomeController的Logout方法中演示了如何注销登录的用户,代码如下所示:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/// <summary> /// 该Action从Asp.Net Core中注销登录的用户 /// </summary> public IActionResult Logout() { Task.Run(async () => { //注销登录的用户,相当于ASP.NET中的FormsAuthentication.SignOut await HttpContext.SignOutAsync(); }).Wait(); return View(); } |
如果当前Http请求本来就没有登录用户,那么调用HttpContext.SignOutAsync方法时也不会报错 5.负载均衡 警告: ASP.NET Core使用 ASP.NET Core data protection stack 来实现Cookie身份认证。如果在服务器集群中必须配置 ASP.NET Core Data Protection,有关详细信息,请参阅 Configuring data protection。如果你的ASP.NET Core站点使用了负载均衡部署了多个实例,就要做ASP.NET Core Data Protection的配置,否则ASP.NET CORE跨多个实例进行Cookie身份认证会失败。 还可以参考:Host ASP.NET Core in a web farm 以及 Share authentication cookies among ASP.NET apps 如何管理ASP.NET Core Data Protection的过期key,可以查看:Data Protection – […]
View Details