[转载]ASP.NET MVC - Dr_Hao - 博客园

mikel阅读(1337)

来源: [转载]ASP.NET MVC – Dr_Hao – 博客园

Visual Studio 2010 –MVC2.0 只有aspx视图引擎

Visual Studio 2012 –MVC3.0和MVC4.0 aspx和Razor视图引擎

Visual Studio 2013 –MVC4.0 aspx和Razor视图引擎

Visual Studio 2015 –MVC5.0 只有Razor视图引擎

MVC模式简介

MVC模式两种理解:一种是表现模式,另外一种是架构模式。

它将应用程序分为三个主要组件即:视图(View),控制器(Controller),模型(Model)

M:Model主要是存储或者是处理数据的组件

Model其实是实现业务逻辑对实体类相应数据库操作,如:CRUD。它包括数据、验证规则、数据访问和业务逻辑等应用程序信息。ViewModel:视图模型

V:View是用户接口层组件。主要是将Model中的数据展示给用户,ASPX和ASCX文件被用来处理视图的职责。

C:Controller处理用户交互,从model中获取数据并将数据传给指定的View

[ASP.NET的两种开发方式]

WebForm的开发方式

1.服务器端控件

2.一般处理程序+Html静态页+Ajax

3.一般处理程序+Html模板

ASP.NET MVC的开发方式

2009年第一个开源项目版本发布

更加简洁,更加接近原始的“请求-处理-响应”

更加开发、更多的新的特点、社区活跃

不会取代WebForm

底层跟WebForm都是一样的。管道上不同的处理而已

[控制器的约定大于配置]

*Controller放到controllers文件夹中,并且命名方式以Controller结尾

*每个Controller都对应View中的一个文件夹,文件夹的名称跟Controller名相同

*Controller中的方法名都对应一个View视图(非必须,但是建议这么做),而且View的名字跟Action的名字相同

*控制器必须是非静态类,并且要实现IController接口

*Controller类型可以放到其他项目中

[视图的相关约定]

*所有的视图必须放到Views目录下

*不同控制器的视图用文件夹进行分割,每个控制器都对应一个视图目录

*一般视图名字跟控制器的Action相对应(非必须)

*多个控制器公共的视图放到Shared

[ViewData]传递数据载体

*ViewData是Controller的属性,此属性是继承ControllerBase而来。

*ViewPage下也有一个ViewData的一个属性

*控制器的Action方法执行完成后,返回ViewResult,然后MVC框架会执行ExecuteResult方法时,Controller中的ViewData数据传递给ViewPage类,

其实就是把Controller的ViewData赋值给ViewPage页面的ViewData属性

*ViewBag传递数据(原理参考ViewData)

MVC强类型视图

1:view中把 <%@ Page Language=”C#” Inherits=”System.Web.Mvc.ViewPage<Dynamic>” %>中Dynamic修改为具体Model

2:后台给ViewData.Model赋值

3:前台 就可以使用Model.Id 等等

强类型的HtmlHelper

<%: Html.TextBoxFor(u=>u.UserName) %>

硬编码的HtmlHelper

<%:Html.TextBox(“UserName”) %>

[Html.Encode]

<%:%> 相当于<%= Html.Encode()%>

推荐大家使用<%:%>,方便简洁

输出特殊字符比如<script>

HtmlString和MvcHtmlString可以屏蔽冒号的编码化

Html.Raw():也可以把字符串原封不动的输出到页面上,避免被编码化。

原样输出:

<%:Html.Row(“”)%>

<%: new HtmlString(“”)%>

<%: new MvcHtmlString(“”)%>

[HtmlHelper扩展]

扩展方法的三要素:静态类、静态方法、this关键字

使用方法是: 在视图上。引用静态类所在的命名空间 (或者直接把静态类的命名空间修改为HtmlHelper类的命名空间)

复制代码
 1 namespace System.Web.Mvc
 2 {
 3     public static class MyHtmlExt
 4     {
 5         /// <summary>
 6         /// 原样输出
 7         /// </summary>
 8         /// <param name="htmlHelper"></param>
 9         /// <param name="value"></param>
10         /// <returns></returns>
11         public static string Mylabel(this HtmlHelper htmlHelper, string value)
12         {
13             return string.Format("<h1>{0}</h1>",value);
14         }
15 
16         /// <summary>
17         /// 编码化
18         /// </summary>
19         /// <param name="htmlHelper"></param>
20         /// <param name="value"></param>
21         /// <returns></returns>
22         public static MvcHtmlString MyHtmlLable(this HtmlHelper htmlHelper, string value)
23         {
24             return new MvcHtmlString(string.Format("<h1>{0}</h1>", value));
25         }
26     }
27 }
复制代码

关于控制器Action参数的赋值:

//Action在执行之前,MVC框架会自动的将请求中的数据装配到Action的参数在里面去

参数Id通过MVC路由规则传递参数,参数为非Id则通过QueryString方式进行传递参数

双向数据绑定

1:控制器通过ViewData容器把数据传递到View之后,View在渲染Html标签的时候会自动从ViewData中获取数据进行填充标签

2:表单提交到控制器的时候,执行控制器的Action之前会自动将表单中的内容填充到方法的参数或者参数的属性里面去

[Razor引擎]

在MVC3.0版本的时候(Visual Studio 2012),微软终于引入了第二种模板引擎:Razor。

Razor文件类型:Razor支持两种文件类型,分别是.cshtml 和.vbhtml,其中.cshtml 的服务器代码使用了C#的语法,.vbhtml 的服务器代码使用了vb.net的语法。

Razor在减少代码冗余、增强代码可读性和vs 智能感知方面,都有着突出的优势(C#代码和HTML代码可以进行混编)

[控制器详解]

控制器三个职责:

1:处理跟用户的交互

2:处理业务逻辑的调用

3:指定具体的视图显示数据,并且把数据传递给视图

*控制器的约定:

必须非静态类

必须实现IController接口

必须以Controller为结尾

*在Action中可以访问HttpContext中所有的相关数据:比如:Session、Cookie、也可以设置响应,总之跟WebForm中Page类能做的,在Action中都能做。

[深入讲解Controller]

Controller负责将获取Model数据并将Model传递给View对象,通知View对象显示。

一个Controller可以包含多个Action。每一个Action都是一个方法,返回一个ActionResult实例

一个Controller对应一个XxController.cs控制文件,对应在View中有一个Xx文件夹。一般情况下 一个Action对应一个aspx页面

[MapRoute]

Global.asax.cs文件中定义了路由的识别规则

1 routes.MapRoute(
2     name: "Default",    // 路由名称,作为路由规则的key,一定不能重复
3     url: "{controller}/{action}/{id}", // 带有参数的URL,请求后台URL规则
4     defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }    // 参数默认值
5 );

//路由规则:

1:可以有多条路由规则

2:路由规则是有顺序的

MapRoute()方法

MapRoute( string name, string url);

MapRoute( string name, string url, object defaults);

MapRoute( string name, string url, string[] namespaces);

MapRoute( string name, string url, object defaults, object constraints);

MapRoute( string name, string url, object defaults, string[] namespaces);

MapRoute( string name, string url, object defaults, object constraints, string[] namespaces);

MapRoute参数介绍

【name参数】: 规则名称, 可以随意起名.不可以重名,否则会发生错误:路由集合中已经存在名为“Default”的路由。路由名必须是唯一的。

【url参数】: url获取数据的规则, 这里不是正则表达式,  将要识别的参数括起来即可, 比如: {controller}/{action}

最少只需要传递name和url参数就可以建立一条Routing(路由)规则.比如实例中的规则完全可以改为:

routes.MapRoute( “Default”, “{controller}/{action}”);

【defaults参数】: url参数的默认值.如果一个url只有controller: localhost/home/

而且我们只建立了一条url获取数据规则: {controller}/{action}

那么这时就会为action参数设置defaults参数中规定的默认值. defaults参数是Object类型,所以可以传递一个匿名类型来初始化默认值:

new { controller = “Home”, action = “Index” }

实例中使用的是三个参数的MapRoute方法:

routes.MapRoute(

“Default”, // Route name

“{controller}/{action}/{id}”, // URL with parameters

new { controller = “Home”, action = “Index”, id = “” } // Parameter defaults );

【constraints参数】:

用来限定每个参数的规则或Http请求的类型.constraints属性是一个RouteValueDictionary对象,也就是一个字典表, 但是这个字典表的值可以有两种:

用于定义正则表达式的字符串。正则表达式不区分大小写。

一个用于实现 IRouteConstraint 接口且包含 Match 方法的对象。

通过使用正则表达式可以规定参数格式,比如controller参数只能为4位数字:

new { controller = @”\d{4}”}

[Url路由总结]

实现的功能:

(1)访问 localhost/hotels/list-beijing-100,200-3 会访问酒店频道的列表页,并传入查询参数

(2)访问 localhost/hotels 下面的任何其他页面地址, 都会跳转到酒店首页.

(3)访问 localhost 下面的任何地址, 如果未匹配上面2条, 则跳转到首页.

简单总结:

(1)Routing规则有顺序(按照添加是的顺序), 如果一个url匹配了多个Routing规则, 则按照第一个匹配的Routing规则执行.

(2)由于上面的规则, 要将具体频道的具体页面放在最上方, 将频道首页 和 网站首页 放在最下方.

(3) {*values} 表示后面可以使任意的格式.

【关于测试Routing】

使用RouteDebug辅助类

在Global.asax添加如下代码:

protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
//路由调试,放在所有代码之后
RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
}

【MVC验证】

*.NET框架中的System.ComponentModel.DataAnnotations命名空间包括了众多可为你所用的内置验证特性,

介绍用的最多的其中的四个:[Required],[StringLength],[Range],[RegularExpression]

*定义自己的定制验证特性,然后应用它们。你可以通过继承自System.ComponentModel.DataAnnotations命名空间中的ValidationAttribute基类,定义完全定制的特性。

*[服务器端校验]只需要在Action中校验:ModelState.IsValid属性即可。true就是校验通过,false就是不通过。

*要使用[客户端校验],必须引入Js脚本支持(JQuery的校验)
[
<script src=”~/Scripts/JQuery-1.8.2.js”></script>
<script src=”~/Scripts/jQuery.validate.js”></script>
<script src=”~/Scripts/jQuery.validate.unobtrusive.js”></script>
]

*添加语句 <% Html.EnableClientValidation(); %>(MVC3、4中默认开启)

*WebConfig中可以设置全局客户端校验是否开启或者关闭

【JQuery Ajax】

跟普通的WebForm的开发方式都一致的

Url地址不同点:请求的是Controller下的Action

在使用Json返回JsonResult时注意要将第二个参数设置允许Get提交方式:return Json(“”,JsonRequestBehavior.AllowGet)

在Ajax开发中要注意Ajax方法体内的参数设置正确

提交方式要跟Action打的标签一致

ASP.NET MVC Microsoft Ajax】

*将微软提供的脚本引入到页面中

*注意引用的顺序

*Ajax.BeginForm没有提供闭合的方法,请使用Using配合关闭

*AjaxOptions参数的设置

*SuccessMethod只是提供提醒,并没有数据的传递

[过滤器详解]

项目大一点总会有相关的AOP面向切面的组件,而MVC(特指:ASP.NET MVC,以下皆同)项目中呢Action在执行前或者执行后我们想做一些特殊的操作

(比如身份验证,日志,异常,行为截取等),而不想让MVC开发人员去关心和写这部分重复的代码,那我们可以通过AOP截取实现,

而在MVC项目中我们就可以直接使用它提供的Filter的特性帮我们解决,不用自己实现复杂的AOP了。

[过滤器详解] Ps:新建一个类 MyActionFilterAttribute:ActionFilterAttribute

ActionFilterAttribute 默认实现了IActionFilter和IResultFilter。

而ActionFilterAttribute是一个Abstract的类型,所以不能直接使用,因为它不能实例化,所以我们想使用它必须继承一下它然后才能使用

案例:Action过滤

AttributeUsage特性用于设置标签

[AttributeUsage(AttributeTargets.All,AllowMultiple = True)] 标记在类的级别 //AllowMultiple = true; 允许多个标签同时都起作用

Gloable Filte允许我们设置全局过滤器

异常过滤器:当我们Mvc站点出现了异常的时候会自动执行异常过滤器里面的方法[继承与HandleErrorAttribute]。

Ps:新建一个类:MyExceptionFilterAttribute:HandleErrorAttribute

[区域详解]

ASP.NET MVC提供了区域的功能,可以很方便的为大型的网站划分区域。

可以让我们的项目不至于太复杂而导致管理混乱,有了区域后,每个模块的页面都放入相应的区域内进行管理很方便。

在项目上右击创建新的区域

区域的功能类似一个小的MVC项目,麻雀虽小五脏俱全,有自己的控制器、模型、视图、路由设置

区域的路由设置是最优先的

[模板页]

模板页必须放到共享的文件夹中。

WebForm视图引擎的模板页跟之前的没有什么区别。

Razor引擎引入 渲染区域的概念

所有页面启动的时候ViewStart文件先执行

Demo:Razor引擎模板页 RanderBody和RenderSection

复制代码
 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="utf-8" />
 5     <meta name="viewport" content="width=device-width" />
 6     <title>@ViewBag.Title</title>
 7     @Styles.Render("~/Content/css")
 8     @Scripts.Render("~/bundles/modernizr")
 9 </head>
10 <body>
11     @RenderSection("Header",false)
12     <h1>这里是Layout模板页</h1>
13     @RenderBody()
14 
15     @Scripts.Render("~/bundles/jquery")
16     @RenderSection("scripts", required: false)
17 </body>
18 </html>
复制代码

Razor 测试视图页面,引用Layout.cshtml 模板页

复制代码
 1 @{
 2     ViewBag.Title = "Index2";
 3 }
 4 
 5 <h2>Index2</h2>
 6 <h1>Razor Index2 子页面</h1>
 7 
 8 @section Header{
 9     <h1>这里是Header部分</h1>
10     }
复制代码

[WebAPI]

淘宝开放平台、腾讯开放平台

WebService和WCF复杂不够灵活

MVC4.0中发布了第一个版本的WebAPI

WebAPI:轻巧、方便就是Http请求

[Web API CRUD] [HTTP请求方法]

CURD 是指 Create 、 Update 、 Read 、 Delete 四个简单的数据库操作, 通常大多数 Web 服务也通过 REST 风格的服务提供这些操作。

HTTP 的四个主要方法 (GET, PUT, POST, DELETE) 按照下列方式映射为 CURD 操作:

GET 用于获取 URI 资源的进行展示, GET 操作不应对服务端有任何影响;

PUT 用于更新 URI 上的一个资源, 如果服务端允许, PUT 也可以用于新建一个资源;

POST 用于新建 资源, 服务端在指定的 URI 上创建一个新的对象, 将新资源的地址作为响应消息的一部分返回;

DELETE 用于删除指定的 URI 资源。

Get:一般用作查询,多次操作得到结果一致

Post:一般用于修改、添加多次重复操作得到结果不一致。

Put:一般用于修改,多次操作得到结果一致。

Delete:一般用于删除数据,多次操作得到结果一致。

code write the life, programe change the world

[转载]使用 JQuery EasyUI - 重庆大漠孤烟 - 博客园

mikel阅读(1519)

来源: [转载]使用 JQuery EasyUI – 重庆大漠孤烟 – 博客园

学习要点:
1.引入必要的文件
2.加载 UI 组件的方式
3.使用 easyload.js 智能加载
4.Parser 解析器

本文重点了解 EasyUI 的两种使用方法,包含不同的加载已经 easyload 智能按需加载。最后了解一下 Parser 解析器的用法。

注意:所有的代码将在文章的最后页统一引入

一. 引入必要的文件

由于刚刚更新了 JQuery EasyUI1.4.4,这个小微版本的更新主要是内部优化和 UI 的一些微调,本身不影响学习。之前我们采用 1.2.4 版本的中文手册学习 1.3.5 都没有什么
障碍,所以,不必担心版本问题。

我们在整个文章中JQuery  EasyUI的目录结构都如下图所示

easyui文件夹是保存jQuery EasyUI的所有要使用的文件

JS文件夹是保存我们自己的JS文件的

index.html是保存我们自己书写的html代码

复制代码
 1 //引入 jQuery 核心库,这里采用的是 2.0
 2 <script type="text/javascript" src="easyui/jquery.min.js"></script>
 3 //引入 jQuery EasyUI 核心库,这里采用的是 1.4.4
 4 <script type="text/javascript" src="easyui/jquery.easyui.min.js"></script>
 5 //引入 EasyUI 中文提示信息
 6 <script type="text/javascript" src="easyui/locale/easyui-lang-zh_CN.js"></script>
 7 //引入自己开发的 JS 文件
 8 <script type="text/javascript" src="js/index.js"></script>
 9 //引入 EasyUI 核心 UI 文件 CSS
10 <link rel="stylesheet" type="text/css" href="easyui/themes/default/easyui.css" />
11 //引入 EasyUI 图标文件
12 <link rel="stylesheet" type="text/css" href="easyui/themes/icon.css" />
复制代码

PS:引入完毕后,我们就可以编写 jQuery EasyUI 代码了。

二、加载 UI  组件的方式

加载 UI 组件有两种方式:1.使用 class 方式加载;2.使用 JS 调用加载。

1 //使用 class 加载,格式为:easyui-组件名
2 <div class="easyui-dialog" id="box" title="标题" style="width:400px;height:200px;">
3 内容部分
4 </div>

PS:使用了规定的格式就可以生成一个 UI 组件,这是由于 jQuery EasyUI 的一个解析器(Parser)的起到了作用。解析之后,从 Firebug 里面可以看到 UI 组件变化后的 HTML。

//使用 JS 调用加载
$('#box').dialog();

PS:一般推荐使用第二种 JS 调用加载,因为一个 UI 组件有很多属性和方法,如果使用 class 的用法将极大的不方便。并且根据 JS 和 HTML 分离的原则,第二种提高了代码的可读性。

三. 使用 easyload.js  智能加载

//删除 jQuery EasyUI 的 JS 核心文件及 CSS,引入 easyloader.js 文件
<script type="text/javascript" src="easyui/jquery.min.js"></script>
<script type="text/javascript" src="easyui/easyloader.js"></script>
//JS 代码
easyloader.load('dialog', function () {
    $('#box').dialog();
});

PS:使用 easyloader 智能加载,是根据你使用的 UI 组件按需加载。我们可以通过Firebug 查看 HTML, 发现加载了非常多的 js 文件, 这些 js 都是 dialog 组件的必须条件。所以,使用 easyloader 加载会减少不必要的内容加载。但问题是,使用智能加载,你编码的难度和成本都提高了,效率降低,并且智能加载的 js 文件数量还是非常多的,并不会提高太大的速度,反而因为 js 文件较多,被搜索引擎要求合并优化。

四、Parser  解析器

Parser 解析器是专门解析渲染各种 UI 组件了,一般来说,我们并不需要使用它即可自动完成 UI 组件的解析工作。当然,有时可能在某些环境下需要手动解析的情况。
手动解析一般是使用 class 的情况下有效,比如设置 class=”easyui-dialog”。

Parser 属性

属性名                 默认值           说明
$.parser.auto      true              定义是否自动解析 EasyUI 组件

//关闭自动解析功能,放在$(function() {})外
$.parser.auto = false;

Parser 方法

属性名                              传参                        说明
$.parser.parse                  空或 JQ 选择器          解析指定的 UI 组件
$.parser.onComplete         回调函数                  解析完毕后执行

//解析所有 UI
$.parser.parse();
//解析指定的 UI
$.parser.parse('#box');

PS:使用指定 UI 解析,必须要设置父类容器才可以解析到。比如:

<div id="box">
<div class="easyui-dialog" title="标题" style="width:400px;height:200px;">
<span>内容部分</span>
</div>
</div>
//UI 组件解析完毕后执行,放在$(function () {})外
$.parser.onComplete = function () {
    alert('UI 组件解析完毕!');
};

 

解决PHP函数imagettftext生成二维码中文乱码问题

mikel阅读(1281)

$str1 = ‘我是’;
imagettftext($target, 20, 0, 150, 70, $bc, $font, $this->to_entities($str1));
imagettftext($target, 20, 0, 210, 70, $cc, $font, $this->to_entities($userinfo[‘nickname’]));

public function to_entities($string){
$len = strlen($string);
$buf = “”;
for($i = 0; $i < $len; $i++){ if (ord($string[$i]) <= 127){ $buf .= $string[$i]; } else if (ord ($string[$i]) <192){ //unexpected 2nd, 3rd or 4th byte $buf .= "&#xfffd"; } else if (ord ($string[$i]) <224){ //first byte of 2-byte seq $buf .= sprintf("&#%d;", ((ord($string[$i + 0]) & 31) << 6) + (ord($string[$i + 1]) & 63) ); $i += 1; } else if (ord ($string[$i]) <240){ //first byte of 3-byte seq $buf .= sprintf("&#%d;", ((ord($string[$i + 0]) & 15) << 12) + ((ord($string[$i + 1]) & 63) << 6) + (ord($string[$i + 2]) & 63) ); $i += 2; } else { //first byte of 4-byte seq $buf .= sprintf("&#%d;", ((ord($string[$i + 0]) & 7) << 18) + ((ord($string[$i + 1]) & 63) << 12) + ((ord($string[$i + 2]) & 63) << 6) + (ord($string[$i + 3]) & 63) ); $i += 3; } } return $buf; } [/php]

[转载]浅谈12306核心模型设计思路和架构设计 - netfocus - 博客园

mikel阅读(820)

来源: [转载]浅谈12306核心模型设计思路和架构设计 – netfocus – 博客园

前言

春节期间,无意中看到一篇文章, 文章中讲到12306的业务复杂度远远比淘宝天猫这种电商网站要复杂。后来自己想想,也确实如此。所以,很想挑战一下12306这个系统的核心领域模型的 设计。一般的电商网站,购买都是基于商品的概念,每个商品有一定量的库存,用户的购买行为是针对商品的。当用户发起购买行为时,系统只需要生成订单并对用 户要购买的商品减库存即可。但是,12306就不是那么简单了,具体复杂在哪里,我下面会进一步分析。

另外一个让我写这篇文章的原因,是我发现也许是否是因为目前12306的核心领域模型设计的不够好,导致用户购票时要处理的业务逻辑异常复杂,维护 数据一致性的难度也几百倍的上升,同时面对高并发的订票也难以支持很高的TPS。我觉得,越是复杂的业务,就越要重视业务分析,重视领域模型的抽象和设 计。如果不假思索,凭以往经验行事,则很可能会被以往的设计经验先入为主,陷入死胡同。我发现技术人员往往更注重技术层面的解决方案,比如一上来就分析如 何集群、如何负载均衡、如何排队、如何分库分表、如何用锁,如何用缓存等技术问题,而忽略了最根本的业务层面的思考,如分析业务、领域建模。我认为越是复 杂的业务系统,则越要设计一个健壮的领域模型。如果一个系统的架构我们设计错了,还有补救的余地,因为架构最终沉淀的只是代码,调整架构即可(一个系统的 架构本身就是不断演进的);而如果领域模型设计错了,那要补救的代价是非常大的,因为领域模型沉淀的是数据结构及其对应的大量数据,对任何一个大型系统, 要改核心领域模型都是成本非常高的。

本文的重点不是在如何解决高并发的问题,而是希望从业务角度去分析,12306的理想模型应该是怎么样的。网上目前谈12306的文章貌似都是千篇一律的只谈技术,不谈业务分析和如何建模的。所以我想写一下自己的设计和大家交流学习。

需求概述

12306这个系统,核心要解决的问题是网上售票。涉及到2个角色使用该系统:用户、铁道部。用户的核心诉求是查询余票、购票;铁道部的核心诉求是 售票。购票和售票其实是一个场景,对用户来说是购票,对铁道部来说是售票。因此,我们要设计一个在线的网站系统,解决用户的查询余票、购票,以及铁道部的 售票这3个核心诉求。看起来,这3个场景都是围绕火车票展开的。

查询余票:用户输入出发地、目的地、出发日三个条件,查询可能存在的车次,用户可以看到每个车次经过的站点名称,以及每种座位的余票数量。

购票:购票分为订票和付款两个阶段,本文重点分析订票的模型设计和实现思路。

其实还有很多其他的需求,比如给不同的车次设定销售座位数配额,以及不同的区段设置不同的限额。但相比前面两个需求来说,我觉得这个需求相对次要一些。

需求分析

确实,12306也是一个电商系统,而且看起来商品就是票了。因为如果把一张票看成是一个商品,那购票就类似于购买商品,然后每张票都有库存,商品 也有库存的概念。但是如果我们仔细想想,会发现12306要复杂很多,因为我们无法预先确定好所有的票,如果非要确定,那只能通过穷举法了。

我们以北京西到深圳北的G71车次高铁为例(这里只考虑南下的方向,不考虑深圳北到北京西的,那是另外一个车次,叫G72),它有17个站(北京西 是01号站,深圳北是17号站),3种座位(商务、一等、二等)。表面看起来,这不就是3个商品吗?G71商务座、G71一等座、G71二等座。大部分轻 易喷12306的技术人员(包括某些中等规模公司的专家、CTO)就是在这里栽第一个跟头的。实际上,G71有136*3=408种商品(408个 SKU),怎么算来的?如下:

如果卖北京西始发的,有16种卖法(因为后面有16个站),北京西到:保定、石家庄、郑州、武汉、长沙、广州、虎门、深圳。。。。都是一个独立的商 品,同理,石家庄上车的,有15种下车的可能,以此类推,单以上下车的站来计算,有136种票:16+15+14….+2+1=136。每种票都有3 种座位,一共是408个商品。

为了方便后面的讨论,我们先明确一下票是什么?

一张票的核心信息包括:出发时间、出发地、目的地、车次、座位号。持有票的人就拥有了一个凭证,该凭证表示持有它的人可以坐某个车次的某个座位号, 从某地到某地。所以,一张票,对用户来说是一个凭证,对铁道部来说是一个承诺;那对系统来说是什么呢?不知道。这就是我们要分析业务,领域建模的原因,我 们再继续思考吧。

明白了票的核心信息后,我们再看看G71这个车次的高铁,可以卖多少张票?

讨论前先说明一下,一辆火车的物理座位数(站票也可以看成是一种座位,因为站票也有数量配额)不等于可用的最大配合。所有的物理座位不可能都通过 12306网站来销售,而是只会销售一部分,比如40%。其余的还是会通过线下的方式销售。不仅如此,可能有些站点上车的人会比较多,有些比较少,所以我 们还会给不同的区间配置不同的限额。比如D31北京南至上海共有765张,北京南有260张,杨柳青有80张,泰安有 76张。如果杨柳青的80张票售完就会显示无票,就算其他站有票也会显示无票的。每个车次肯定会有各种座位的配额和限额的配置的,这种配置我目前无法预 料,但我已经把这些规则都封装近车次聚合根里了,所有的配置策略都是基于座位类型、站点、区间配置的。关于票的配置抽象出来,我觉得主要有3种:1)某个 区段最多允许出多少张;2)某个区段最少允许出多少张;3)某个站点上车的最多多少张;当用户订票时,把用户指定的区段和这3种配置条件进行比较,3个条 件都满足,则可以出票。不满足,则认为无票了。下面举个例子:

ABCDEFG,这是所有站点。座位总配额是100,假设B站点上车,E站下车的人比较少,那我们就可以设定BE这个区段最多只能出10张票。所 以,只要是用户的订票是在这个区段内的,就最多出10张。再比如,一列车次,总共100个座位配额,希望全程票最少满足80张,那我们只要给AG这个区段 设定最少80张。那任何订票请求,如果是子区间的,就不能超过100-80,即20张。这两种条件必须同时满足,才允许出票。

但是,不管如何做配额和限额,我们总是针对某个车次进行配置,这些配置只是车次内部售票时的一些额外的判断条件(业务规则),不影响车次模型的核心 地位和对外暴露的功能。所以,为了本文讨论的清楚起见,我后续的讨论都不涉及配额和限额的问题,而是认为任何区段都可以享受火车最大的物理座位数。

并且,为了讨论问题方便,我们减少一些站点来讨论。假设某个车次有A,B,C,D四个站点。那001这个人购买了A,B这个区间,系统会分配给 001一个座位x;但是因为001坐到B站点后会下车,所以相当于x这个座位又空出来了,也就是说,从B站点开始,系统又可以认为x这个座位是可用的。所 以,我们得出结论:同一个座位,其实可以同时出售AB,BC这两张票。通过这个简单的分析,我们知道,一列火车虽然只有有限的座位数,比如1000个座 位。但可以卖出的票远远不止1000个。还是以A,B,C,D四个站点为例,假如火车总共有1000个座位,那AB可以卖1000张,BC也可以卖 1000张,同样,CD也可以卖1000张。也就是说,理论上最多可以卖出3000张票。但是如果换一种卖法,所有人都是买ABCD的票,也就是说所有的 票都是经过所有站点的,那就是最多只能卖出1000张票了。而实际的场景,一定是介于1000到3000之间。然后实际的G71这个车次,有17个站,那 到底可以卖出多少个票,大家应该可以算了吧。理论上这17个站中的任意两个站点之间所形成的线段,都可以出售为一张票。我数学不好,算不太清楚,麻烦有数 学好的人帮我算算,呵呵。

通过上面的分析,我们知道一张票的本质是某个车次的某一段区间(一条线段),这个区间包含了若干个站点。然后我们还发现,只要区间不重叠,那座位就不会发生竞争,可以被回收利用,也就是说,可以同时预先出售。

另外,经过更深入的分析,我们还发现区间有4种关系:1)不重叠;2)部分重叠;3)完全重叠;4)覆盖;不重叠的情况我们已经讨论过了,而覆盖也 是重叠的一种。所以我们发现如果重叠,比如有两个区间发生重叠,那重叠部分的区间(可能夸一个或多个站点)是在争抢座位的。因为假设一列火车有100个座 位,那每个原子区间(两个相邻站点的连线),最多允许重叠99次。

所以,经过上面的分析,我们知道了一个车次能够出售一张车票的核心业务规则是什么?就是:这张车票所包含的每个原子区间的重叠次数加1都不能超过车次的总座位数,实际上重叠次数+1也可以理解为线段的厚度。

模型设计

上面我分析了一下票的本质是什么。那接下来我们再来看看怎么设计模型,来快速实现购票的需求,重点是怎么设计商品聚合以及减库存的逻辑。

传统电商的思路

如果按照普通电商的思路,把票(站点区间)设计为商品(聚合根),然后为票设计库存数量。我个人觉得是很糟糕的。因为一方面这种聚合根非常多(上面 的G71就有408个);另一方面,即便枚举出来了,一次购票也一定会影响非常多其他聚合根的库存数量(只要被部分或全部重叠的区间都受影响)。这样的一 次订单处理的复杂度是难以评估的。而且这么多聚合根的更新要在一个事务里,这不是为难数据库吗?而且,这种设计必然带来大量的事务的并发冲突,很可能导致 数据库死锁。总之,我认为这种是典型的由于领域模型的设计错误,导致并发冲突高、数据持久化落地困难。或者如果要解决并发问题,只能排队单线程处理,但是 仍然解决不了要在一个事务里修改大量聚合根的尴尬局面。听说12306是采用了Pivotal Gemfire这种高大上的内存数据库,我对这个不太了解。我不可想象要是不使用内存数据库,他们要怎么实现车次内的票之间的数据强一致性(就是保证所有 出售的票都是符合上面讨论的业务规则的)?所以,这种设计,我个人认为是思维定势了,把火车票看成是普通电商的商品来看待。所以,我们有时做设计又要依赖 于经验,又要不能被以往经验所束缚,真的不容易,关键还是要根据具体的业务场景多多深入分析,尽量分析抽象出问题的本质出来,这样才能对症下药。那是否有 其他的设计思路呢?

我的思路

聚合设计

通过上面的分析我们知道,其实任何一次购票都是针对某个车次的,我认为车次是负责处理订票的聚合根。我们看看一 个车次包含了哪些信息?一个车次包括了:1)车次名称,如G71;2)座位数,实际座位数会分类型,比如商务座20个,一等座200个;二等座500个; 我们这里为了简化问题,可以暂时忽略类型,我认为这个类型不影响核心的模型的设计决策。需要格外注意的是:这里的座位数不要理解为真实的物理座位数,很有 可能比真实的座位数要少。因为我们不可能把一个车次的所有座位都在网上通过12306来出售,而是只出售一部分,具体出售多少,要由工作人员人工指定。 3)经过的站点信息(包括站点的ID、站点名称等),注意:车次还会记录这些站点之间的顺序关系;4)出发时间;看过GRASP九大模式中的信息专家模式的同学应该知道,将职责分配给拥有执行该职责所需信息的类。我们这个场景,车次具有一次出票的所有信息,所以我们应该把出票的职责交给车次。另外学过DDD的同学应该知道,聚合设计有一个原则,就是:聚合内强一致性,聚合之间最终一致性。经 过上面的分析,我们知道要产生一张票,其实要影响很多和这个票对应的线段相交的其他票的可用数量。因为所有的站点信息都在车次聚合内部,所以车次聚合内部 自然可以维护所有的原子区间,以及每个原子区间的可用票数(相当于是库存数)。当一个原子区间的可用票数为0的时候,意味着火车针对这个区间的票已经卖完 了。所以,我们完全可以让车次这个聚合根来保证出票时对所有原子区间的可用票数的更新的强一致性。对于车次聚合根来说,这很简单,因为只是几次简单的内存 操作而已,耗时可以忽略。一列火车假如有ABCD四个站点,那原子区间就是3个。对于G71,则是16个。

怎么判断是否能出票

基于上面的聚合设计,出票时扣减库存的逻辑是:

根据订单信息,拿到出发地和目的地,然后获取这段区间里的所有的原子区间。然后尝试将每个原子区间的可用票数减1,如果所有的原子区间都够减,则购 票成功;否则购票失败,提示用户该票已经卖完了。是不是很简单呢?知道了出票的逻辑,那退票的逻辑也就很简单了,就是把这个票的所有原子区间的可用票数加 1就OK了。如果我们从线段的厚度的角度去考虑,那出票时,每个原子区间的厚度就是+1,退票时就是减一。就是相反的操作,但本质是一样的。

所以,通过这样的思路,我们将一次订票的处理控制在了一个聚合根里,用聚合根内的强一致性的特性保证了订票处理的强一致性,同时也保证了性能,免去 了并发冲突的可能性。传统电商那种把票单做类似商品的核心聚合根的设计,我当时第一眼看到就觉得不妥。因为这违背了DDD强调的强一致性应该由聚合根来保 证、聚合根之间的最终一致性通过Saga来保证的原则。

还有一个很重要的概念我想说一下我的看法,就是座位和区间的关系。因为有些朋友和我讲,考虑座位号的问题,虽然都能减1,座位号也必须是同一个。我 觉得座位是全局共享的,和区段无关(也许我的理解完全有误,请大家指正)。座位是一个物理概念,一个用户成功购买了一张票后,座位就会少一个,一张票唯一 对应一个座位,但是一个座位有可能会对应多张票;而区间是一个逻辑上的概念,区间的作用有两个:1)表示票的出发地和目的地;2)记录票的可用数额。如果 区间能连通(即该区间内的每个原子区间的可用数额都大于0),则表示允许拥有一个座位。所以,我觉得座位和票(区间)是两个维度的概念。

如何为票分配座位

我觉得车次聚合根内部应该维护所有该车次已经售出的票,已经出售的票的的本质是区间和座位的对应关系。系统处理订票时,用户提交过来的是一段区间。所以,系统应该做两个事情:

  1. 先根据区间去判断是否有可用的座位;
  2. 如果有可用座位,则再通过算法去选择一个可用的座位;

当得到一个可用座位后,就可以生成一张票了,然后保存这个票到车次聚合根内部即可。下面举个例子:

假设现在的情况是座位有3个,站点有4个
座位:1,2,3
站点:abcd

票的卖法1:
票1:ab,1
票2:bc,2
票3:cd,3
票4:ac,3
票5:bd,1
这种选座位的方式应该比较高效,因为总是优先从座位池里去拿座位,只有在万不得已的时候才会去回收可重复利用的票。
上面的4,5两个票,就是考虑回收利用的结果。

票的卖法2:
票1:ab,1
票2:bc,1
票3:cd,1
票4:ac,2
票5:bd,3
这种选座位的方式应该相对低效,因为总是优先会去扫描是否有可回收的座位,而扫描相对直接从座位池里去拿票总是成本相对要高的。
上面的2,3两个票,就是考虑回收利用的结果。

但是,优先从座位池里拿票的算法有缺陷,就是会出现虽然第一步判断认为有可用的座位,但是这个座位可能不是全程都是同一个座位。举例:
假设现在的情况是座位有3个,站点有4个
座位:1,2,3
站点:abcd

票的卖法3:
票1:ab,1
票2:bc,2
票3:cd,3

现在如果有人要买ad的票,那可用的座位有2,或者3。但是无论是2还是3,都要这个乘客中途换车位。比如卖给他座位2,那他ab是坐的座位2,但是bc的时候要坐座位1的。否则拿票2的那个人上车时,发现座位2已经有人了。而通过优先回收利用的算法,是没这个问题的。

所以,从上面的分析我们也知道选座位的算法该怎么写了,就是采用优先回收利用座位的算法。我认为不管我们这里怎么设计算法,都不影响大局,因为这一切都只发生在车次聚合根内部,这就是预先设计好聚合根,明确出票职责在哪个对象上的好处。

模型分析总结

  1. 我认为票不是核心聚合根,票只是一次出票的结果,一个凭证而已。
  2. 12306真正的核心聚合根应该是车次,车次具有出票的职责,一次出票具体做的事情有:
    • 判断是否可出票;
    • 选择可用的座位;
    • 更新一次出票时所有原子区间的可用票数,用于判断下次是否能出票;
    • 维护所有已售出的票,用于为选择可用座位提供依据;

通过这样的模型设计,我们可以确保一次出票处理只会在一个车次聚合根内进行。这样的好处是:

  1. 不需要依赖数据库事务就能实现数据修改的强一致性,因为所有修改只在一个聚合根内发生;
  2. 在保证数据强一致性的同时还能提供很高的并发处理能力,具体设计见下面的架构设计

架构设计(非本文重点,没兴趣的朋友可以略过)

我觉得12306这样的业务场景,非常适合使用CQRS架构;因为首先它是一个查多写少、但是写的业务逻辑非常复杂的系统。所以,非常适合做架构层面的读写分离,即采用CQRS架构。而且应该使用数据存储也分离的CQRS。这样CQ两端才可以完全不需要顾及对方的问题,各自优化自己的问题即可。我们可以在C端使用DDD领域模型的思路,用良好设计的领域模型实现复杂的业务规则和业务逻辑。而Q端则使用分布式缓存方案,实现可伸缩的查询能力。

订票的实现思路

同时借助像ENode这样的框架,我们可以实现in-memory + Event Sourcing的架构。Event Sourcing技术,可以让领域模型的所有状态修改的持久化统一起来,本来要用ORM的方式保存聚合根最新状态的,现在只需要简单的通用的方式保存一个 事件即可(一次订票只涉及一个车次聚合根的修改,修改只产生一个事件,只需要持久化一个事件(一个JSON串)即可,保证了高性能,无须依赖事务,而且通 过ENode可以解决并发问题)。我们只要保存了聚合根每次变化的事件(事件的结构怎么设计,本文不做多的介绍了,大家可以思考下),就相当于保存了聚合 根的最新状态。而正是由于Event Sourcing技术的引入,让我们的模型可以一直存活在内存中,即可以使用in-memory技术。不要小看in-memory技术,in- memory技术在某些方面对提高命令的处理性能非常有帮助。比如就以我们车次聚合根处理出票的逻辑,假设某个车次有大量的命令发送到分布式消息队列,然 后有一台机器订阅了这个队列的消息,然后这台机器处理这个车次的订票命令时,由于这个车次聚合根一直在内存,所以就省去了每次要去数据库取出聚合根的步 骤,相当于少了一次数据库IO。这样的好处是,因为一个车次能够真正出售的票是有限的,因为座位就那么几个,比如就1000个座位,估计一般正常情况也就 出个2000个左右的票吧(具体能出多少张票要取决于区间的相交程度,上面分析过)。也就是说,这个聚合根只会产生2000个事件,也就是说只会有 2000个订票命令的处理是会产生事件,并持久化事件;而其余的大量命令,因为车次在内存计算后发现没有余票了,就不会做任何修改,也不会产生领域事件, 这样就可以直接处理下一个订票命令了。这样就可以大大提高处理订票命令的性能。

另外一个问题我觉得还需要提一下,因为用户订票成功后,还需要付款。但用户有可能不去付款或者没有在规定的时间内完成付款。那这种情况下,系统会自 动释放该用户之前订购的票。所以基于这样的需求,我们在业务上需要支持业务级别的2pc。即先预扣库存,也就是先占住这张票一定时间(比如15分钟),然 后付款成功后再真实给你这张票,系统做真正的库存修改。通过这样的预扣处理,可以保证不会出现超卖的情况。这个思路其实和传统电商比如淘宝这样的系统类 似,我就不多展开了,我之前写的Conference案例也是这样的思路,大家有兴趣的可以去看一下我之前录制的视频。

查询余票的实现思路

我觉得余票的查询的实现相对简单。虽然对于12306来说,查询的请求占了80%,提交订单的请求只占20%。但查询由于对数据没有修改,所以我们 完全可以使用分布式缓存来实现。我们只需要精心设计好缓存的key即可;缓存key的多少要看成本,如果所有可能的查询都设计对应的key,那时间复杂度 为1,查询性能自然高;但代价也大,因为key多了。如果想key少一点,那查询的复杂度自然要上去一点。所以缓存设计无非就是空间换时间的思路。然后, 缓存的更新无非就是:自动失效、定时更新、主动通知3种。通过CQRS架构,由于CQ两端是事件驱动的,当C端有任何状态变化,都会产生对应的事件去通知 Q端,所以我们几乎可以做到Q端的准实时更新。

同时由于CQ两端的完全解耦,Q端我们可以设计多种存储,如数据库和缓存(Redis等);数据库用于线下维护关系型数据,缓存用户实时查询。数据 库和缓存的更新速度相互不受影响,因为是并行的。对同一个事件,可以10台机器负责更新缓存,100台机器负责更新数据库。即便数据库的更新很慢,也不会 影响缓存的更新进度。这就是CQRS架构的好处,CQ的架构完全不同,且我们随时可以重建一种新的Q端存储。不知道大家体会到了没有?

关于缓存key的设计,我觉得主要从查询余票时传递的信息来考虑。12306的关键查询是:出发地、目的地、出发日期三个信息。我觉得有两种key 的设计思路:1)直接设计了该查询条件的key,然后快速拿到车次信息,直接返回;这种方式就是要求我们系统已经枚举了所有车次的所有可能出现的票(区 间)的缓存key,相信你一定知道这样的key是非常多的。2)不是枚举所有区间,而是把每个车次的每个原子区间(相邻的两个站点所连成的直线)的可用票 数作为key。这样,key就非常少了,因为车次假如有10000个,然后每个车次平均15个区间,那也就15W个key而已。当我们要查询时,只需要把 用户输入的出发地和目的地之间的所有原子区间的可用票数都查出来,然后比较出最小可用票数的那个原子区间。则这个原子区间的可用票数就是用户输入的区间的 可用票数了。当然,到这里我提到考虑出发日期。我认为出发日期是用来决定具体是哪个车次聚合根的。同一个车次,不同的日期,对应的聚合根实例是不同的,即 便是同一天,也可能有多个车次聚合根,因为有些车次一天有几班的,比如上午9点发车的一班,下午3点发车的一般。所以,我们也只要把日期也作为缓存key 的一部分即可。

总结

本文完全是凭自己对12306这个网站的核心业务的简单思考而得到的一些设计结果。如果真正的DDD领域建模,更多的是要和业务一线的工作人员、领 域专家进行深入沟通,才能更深入的了解该领域内的业务知识,从而才能设计出更靠谱的领域模型和架构设计。我本人非常惭愧因为没有上12306买过火车票, 家离的比较近,就算要买也是家人给我买:)所以,本文所分享的内容难免是纸上谈兵。但我觉得12306这个系统的业务确实比传统的电商系统要复杂,且并发 又这么高。所以,我觉得这个系统真的很值得大家重视模型的设计,而不只是只关注技术层面的实现。2016年,我有计划打算基于ENode实现一套 12306的核心功能,比如余票查询、订票的功能。有兴趣的朋友可以加入ENode QQ群(185916873)报名哦。

[转载]玩转Asp.net MVC 的八个扩展点 - richiezhang - 博客园

mikel阅读(980)

来源: [转载]玩转Asp.net MVC 的八个扩展点 – richiezhang – 博客园

MVC模型以低耦合、可重用、可维护性高等众多优点已逐渐代替了WebForm模型。能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面ASP.NET MVC优秀的设计和高质量的代码也值得我们去阅读和学习。

本文将介绍ASP.NET MVC中常用的八个扩展点并举例说明。

一、ActionResult

ActionResult代表了每个Action的返回结果。ASP.NET mvc提供了众多内置的ActionResult类型,如:ContentResult,ViewResult,JsonResult等,每一种类型都代 表了一种服务端的Response类型。我们什么时候需要使用这个扩展点呢?

假如客户端需要得到XML格式的数据列表:

        public void GetUser()
        {
            var user = new UserViewModel()
            {
                Name = "richie",
                Age = 20,
                Email = "abc@126.com",
                Phone = "139********",
                Address = "my address"
            };
            XmlSerializer serializer = new XmlSerializer(typeof(UserViewModel));
            Response.ContentType = "text/xml";
            serializer.Serialize(Response.Output, user);
        }

我们可以在Controller中定义一个这样的方法,但是这个方法定义在Controller中有一点别扭,在MVC中每个Action通常都需 要返回ActionResult类型,其次XML序列化这段代码完全可以重用。经过分析我们可以自定义一个XmlResult类型:

    public class XmlResult : ActionResult
    {
        private object _data;

        public XmlResult(object data)
        {
            _data = data;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            var serializer = new XmlSerializer(_data.GetType());
            var response = context.HttpContext.Response;
            response.ContentType = "text/xml";
            serializer.Serialize(response.Output, _data);
        }
    }

这时候Action就可以返回这种类型了:

        public XmlResult GetUser()
        {
            var user = new UserViewModel()
            {
                Name = "richie",
                Age = 20,
                Email = "abc@126.com",
                Phone = "139********",
                Address = "my address"
            };

            return new XmlResult(user);
        }

同样的道理,你可以定义出其他的ActionResult类型,例如:CsvResult等。

二、Filter

MVC中有四种类型的Filter:IAuthorizationFilter,IActionFilter,IResultFilter,IExceptionFilter

这四个接口有点拦截器的意思,例如:当有异常出现时会被IExceptionFilter类型的Filter拦截,当Action在执行前和执行结束会被IActionFilter类型的Filter拦截。

通过实现IExceptionFilter我们可以自定义一个用来记录日志的Log4NetExceptionFilter:

     public class Log4NetExceptionFilter : IExceptionFilter
    {
        private readonly ILog _logger;

        public Log4NetExceptionFilter()
        {
            _logger = LogManager.GetLogger(GetType());
        }
        public void OnException(ExceptionContext context)
        {
            _logger.Error("Unhandled exception", context.Exception);
        }
    }

最后需要将自定义的Filter加入MVC的Filter列表中:

    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new Log4NetExceptionFilter());
        }
    }

为了记录Action的执行时间,我们可以在Action执行前计时,Action执行结束后记录log:

    public class StopwatchAttribute : ActionFilterAttribute
    {
        private const string StopwatchKey = "StopwatchFilter.Value";
        private readonly ILog _logger= LogManager.GetLogger(typeof(StopwatchAttribute));

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            filterContext.HttpContext.Application[StopwatchKey] = Stopwatch.StartNew();
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var stopwatch = (Stopwatch)filterContext.HttpContext.Application[StopwatchKey];
            stopwatch.Stop();

            var log=string.Format("controller:{0},action:{1},execution time:{2}ms",filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,filterContext.ActionDescriptor.ActionName,stopwatch.ElapsedMilliseconds)
            _logger.Info(log);
        }
    }

ActionFilterAttribute是一个抽象类,它不但继承了IActionFilter, IResultFilter等Filter,还继承了FilterAttribute类型,这意味着我们可以将这个自定义的类型当作Attribute来 标记到某个Action或者Controller上,同时它还是一个Filter,仍然可以加在MVC的Filter中起到全局拦截的作用。

三、HtmlHelper

在Razor页面中,如果需要写一段公用的用来展示html元素的逻辑,你可以选择使用@helper标记,例如:


@helper ShowProduct(List<ProductListViewModel.Product> products, string style)
{
    <ul class="list-group">
        @foreach (var product in products)
        {
            <li class="list-group-item @style"><a href="@product.Href" target="_blank">@product.Name</a></li>
        }
    </ul>
}

这一段代码有点像一个方法定义,只需要传入一个list类型和字符串就会按照定义的逻辑输出html:

<h2>Product list using helper</h2>
<div class="row">
    <div class="col-md-6">@ShowProduct(Model.SportProducts, "list-group-item-info")</div>
    <div class="col-md-6">@ShowProduct(Model.BookProducts, "list-group-item-warning")</div>
</div>
<div class="row">
    <div class="col-md-6">@ShowProduct(Model.FoodProducts, "list-group-item-danger")</div>
</div>

这样抽取的逻辑只对当前页面有效,如果我们想在不同的页面公用这一逻辑如何做呢?

在Razor中输入@Html即可得到HtmlHelper实例,例如我们可以这样用:@Html.TextBox(“name”)。由此可见我们可以将公用的逻辑扩展在HtmlHelper上:

    public static class HtmlHelperExtensions
    {
        public static ListGroup ListGroup(this HtmlHelper htmlHelper)
        {
            return new ListGroup();
        }
    }

    public class ListGroup
    {
        public MvcHtmlString Info<T>(List<T> data, Func<T, string> getName)
        {
            return Show(data,getName, "list-group-item-info");
        }

        public MvcHtmlString Warning<T>(List<T> data, Func<T, string> getName)
        {
            return Show(data,getName, "list-group-item-info");
        }

        public MvcHtmlString Danger<T>(List<T> data, Func<T, string> getName)
        {
            return Show(data,getName, "list-group-item-info");
        }

        public MvcHtmlString Show<T>(List<T> data, Func<T, string> getName, string style)
        {
            var ulBuilder = new TagBuilder("ul");
            ulBuilder.AddCssClass("list-group");
            foreach (T item in data)
            {
                var liBuilder = new TagBuilder("li");
                liBuilder.AddCssClass("list-group-item");
                liBuilder.AddCssClass(style);
                liBuilder.SetInnerText(getName(item));
                ulBuilder.InnerHtml += liBuilder.ToString();
            }
            return new MvcHtmlString(ulBuilder.ToString());
        }
    }

有了上面的扩展,就可以这样使用了:

<h2>Product list using htmlHelper</h2>
<div class="row">
    <div class="col-md-6">@Html.ListGroup().Info(Model.SportProducts,x=>x.Name)</div>
    <div class="col-md-6">@Html.ListGroup().Warning(Model.BookProducts,x => x.Name)</div>
</div>
<div class="row">
    <div class="col-md-6">@Html.ListGroup().Danger(Model.FoodProducts,x => x.Name)</div>
</div>

效果:

四、RazorViewEngine

通过自定义RazorViewEngine可以实现同一份后台代码对应不同风格的View。利用这一扩展能够实现不同的Theme风格切换。再比如站点可能需要在不同的语言环境下切换到不同的风格,也可以通过自定义RazorViewEngine来实现。

下面就让我们来实现一个Theme切换的功能,首先自定义一个ViewEngine:

    public class ThemeViewEngine: RazorViewEngine
    {
        public ThemeViewEngine(string theme)
        {

            ViewLocationFormats = new[]
            {
                "~/Views/Themes/" + theme + "/{1}/{0}.cshtml",
                "~/Views/Themes/" + theme + "/Shared/{0}.cshtml"
            };

            PartialViewLocationFormats = new[]
            {
                "~/Views/Themes/" + theme + "/{1}/{0}.cshtml",
                "~/Views/Themes/" + theme + "/Shared/{0}.cshtml"
            };

            AreaViewLocationFormats = new[]
            {
                "~Areas/{2}/Views/Themes/" + theme + "/{1}/{0}.cshtml",
                "~Areas/{2}/Views/Themes/" + theme + "/Shared/{0}.cshtml"
            };

            AreaPartialViewLocationFormats = new[]
            {
                "~Areas/{2}/Views/Themes/" + theme + "/{1}/{0}.cshtml",
                "~Areas/{2}/Views/Themes/" + theme + "/Shared/{0}.cshtml"
            };
        }
    }

当我们启用这一ViewEngine时,Razor就会在/Views/Themes/文件夹下去找View文件。为了启用自定义的ViewEngine,需要将ThemeViewEngine加入到ViewEngines

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
           
            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["Theme"]))
            {
                var activeTheme = ConfigurationManager.AppSettings["Theme"];
                ViewEngines.Engines.Insert(0, new ThemeViewEngine(activeTheme));
            };
      
           //...
        }
    }

接下来就开始编写不同风格的View了,重点在于编写的View文件夹组织方式要跟ThemeViewEngine中定义的路径要一致,以ServiceController为例,我们编写ocean和sky两种风格的View:

最后在web.config制定一种Theme:<add key=”Theme” value=”ocean”/>,ocean文件夹下的View将会被优先采用:

五、Validator

通过在Model属性上加Attribute的验证方式是MVC提倡的数据验证方式,一方面这种方式使用起来比较简单和通用,另一方面这种统一的方 式也使得代码很整洁。使用ValidationAttribute需要引入System.ComponentModel.DataAnnotations 命名空间。

但是有时候现有的ValidationAttribute可能会不能满足我们的业务需求,这就需要我们自定义自己的Attribute,例如我们自定义一个AgeValidator:

    public class AgeValidator: ValidationAttribute
    {
        public AgeValidator()
        {
            ErrorMessage = "Please enter the age>18";
        }

        public override bool IsValid(object value)
        {
            if (value == null)
                return false;

            int age;
            if (int.TryParse(value.ToString(), out age))
            {
                if (age > 18)
                    return true;

                return false;
            }

            return false;
        }
    }

自定义的AgeValidator使用起来跟MVC内置的ValiatorAttribute没什么区别:

        [Required]
        [AgeValidator]
        public int? Age { get; set; }

不过我们有时候可能有这种需求:某个验证规则要针对Model中多个属性联合起来判断,所以上面的方案无法满足需求。这时候只需Model实现IValidatableObject接口即可:

     public class UserViewModel:IValidatableObject
    {
        public string Name { get; set; }

        [Required]
        [AgeValidator]
        public int? Age { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if(string.IsNullOrEmpty(Name))
                yield return new ValidationResult("the name can not be empty");

            if (Name.Equals("lucy"))
            {
                if(Age.Value<25)
                    yield return new ValidationResult("lucy's age must greater than 25");
            }
        }
    }

六、ModelBinder

Model的绑定体现在从当前请求提取相应的数据绑定到目标Action方法的参数中。

        public ActionResult InputAge(UserViewModel user)
        {
            //...
            return View();
        }

对于这样的一个Action,如果是Post请求,MVC会尝试将Form中的值赋值到user参数中,如果是get请求,MVC会尝试将QueryString的值赋值到user参数中。

假如我们跟客户的有一个约定,客户端会POST一个XML格式的数据到服务端,MVC并不能准确认识到这种数据请求,也就不能将客户端的请求数据绑定到Action方法的参数中。所以我们可以实现一个XmlModelBinder:

    public class XmlModelBinder:IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            try
            {
                var modelType = bindingContext.ModelType;
                var serializer = new XmlSerializer(modelType);
                var inputStream = controllerContext.HttpContext.Request.InputStream;
                return serializer.Deserialize(inputStream);
            }
            catch
            {
                bindingContext.ModelState.AddModelError("", "The item could not be serialized");
                return null;
            }

        }

    }

有了这样的自定义ModelBinder,还需要通过在参数上加Attribute的方式启用这一ModelBinder:

        public ActionResult PostXmlContent([ModelBinder(typeof(XmlModelBinder))]UserViewModel user)
        {
            return new XmlResult(user);
        }

我们使用PostMan发送个请求试试:

刚才我们显示告诉MVC某个Action的参数需要使用XmlModelBinder。我们还可以自定义一个XmlModelBinderProvider,明确告诉MVC什么类型的请求应该使用XmlModelBinder:

    public class XmlModelBinderProvider: IModelBinderProvider
    {
        public IModelBinder GetBinder(Type modelType)
        {
            var contentType = HttpContext.Current.Request.ContentType.ToLower();
            if (contentType != "text/xml")
            {
                return null;
            }

            return new XmlModelBinder();
        }
    }

这一Provider明确告知MVC当客户的请求格式为text/xml时,应该使用XmlModelBinder。

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
           
            ModelBinderProviders.BinderProviders.Insert(0, new XmlModelBinderProvider());
          //...
        }
    }

有了XmlModelBinderProvier,我们不再显示标记某个Action中的参数应该使用何种ModelBinder:

        public ActionResult PostXmlContent(UserViewModel user)
        {
            return new XmlResult(user);
        }

七、自定义ControllerFactory实现依赖注入

MVC默认的DefaultControllerFactory通过反射的方式创建Controller实例,从而调用Action方法。为了实现依赖注入,我们需要自定义ControllerFactory从而通过IOC容器来创建Controller实例。

以Castle为例,需要定义WindsorControllerFactory,另外还要创建ContainerInstaller文件,将组建 注册在容器中,最后通过ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));将MVC的ControllerFactory指定为我们自定义的 WindsorControllerFactory。

为了简单起见,这一Nuget包可以帮助我们完成这一系列任务:

Install-Package Castle.Windsor.Web.Mvc

上面提到的步骤都会自动完成,新注册一个组件试试:

public class ProvidersInstaller:IWindsorInstaller
    {
        public void Install(IWindsorContainer container, IConfigurationStore store)
        {
            container.Register(Component.For<IUserProvider>().ImplementedBy<UserProvider>().LifestylePerWebRequest());
        }
    }

Controller就可以进行构造器注入了:

        private readonly IUserProvider _userProvider;

        public ServiceController(IUserProvider userProvider)
        {
            _userProvider = userProvider;
        }

        public ActionResult GetUserByIoc()
        {
            var user = _userProvider.GetUser();
            return new XmlResult(user);
        }

八、使用Lambda Expression Tree扩展MVC方法

准确来说这并不是MVC提供的扩展点,是我们利用Lambda Expression Tree写出强类型可重构的代码。以ActionLink一个重载为例:

public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, object htmlAttributes);

在Razor页面,通过@Html.ActionLink(“Line item 1”, “OrderLineItem”, “Service”, new { id = 1 })可以生成a标签。这一代码的缺点在于Controller和Action都以字符串的方式给出,这样的代码在大型的软件项目中不利于重构,即便 Controller和Action字符串编写错误,编译器也能成功编译。

我们可以利用Lambda Expression Tree解析出Controller和Action的名称。理论上所有需要填写Controller和Action字符串的方法都可以通过这一方法来实现。具体实现步骤参考Expression Tree 扩展MVC中的 HtmlHelper 和 UrlHelper。下面给出两种方法的使用对比:

<div class="row">
    <h2>Mvc way</h2>
    <ul>
        <li>@Html.ActionLink("Line item 1", "OrderLineItem", "Service", new { id = 1 }) </li>
        <li>@Html.ActionLink("Line item 2", "OrderLineItem", "Service", new { id = 2 })</li>
        <li>@Url.Action("OrderLineItem","Service",new {id=1})</li>
        <li>@Url.Action("OrderLineItem","Service",new {id=2})</li>
    </ul>
</div>

<div class="row">
    <h2>Lambda Expression tree</h2>
    <ul>
        <li>@Html.ActionLink("Line item 1", (ServiceController c) => c.OrderLineItem(1))</li>
        <li>@Html.ActionLink("Line item 2", (ServiceController c) => c.OrderLineItem(2))</li>
        <li>@Url.Action((ServiceController c)=>c.OrderLineItem(1))</li>
        <li>@Url.Action((ServiceController c)=>c.OrderLineItem(2))</li>
    </ul>
</div>

本文Demo下载:https://git.oschina.net/richieyangs/MVCExtension.Points

EasyAR SDK在unity中的简单配置及构建一个简单场景。

mikel阅读(1027)

首先打开EasyAR的官方网站http://www.easyar.cn/index.html,注册登陆之后,打开首页的开发页面。

下载sdk和Unity Samples。

创建一个unity3d工程NewAR,导入sdk解压目录中的EasyAR.unitypackage。

接下来回到EasyAR的官方网站,进入开发页面,点击创建应用。输入应用程序名称及包名。

 

就如图所示生成了应用的key。

 

回到unity,创建一个新的场景start并保存,删除场景中的 MainCamera。打开window->lighting,将Environment Lighting的Skybox设为none。打开file->Build Settings,添加当前场景,并进入Android发布的Player Settings。将Other Settings->Identification->Bundle Identifier一栏的包名改为我们在网站上注册时填写的包名。同时将Other Settings->Rendering的Auto Graphics API的勾取消,删除OpenGLES3.0.

 

之后就可以开始正式编辑了。首先向场景中拖入EasyAR->Prefab 目录下的EasyAR.prefab与ImageTarget.prefab。之后开始我们的脚本编辑工作。创建 MyEasyARBehaviour.cs脚本,这个脚本的主要功能为初始化key并对ImageTarget的载入做出相应的响应。

复制代码
 1 using UnityEngine;
 2 
 3 namespace EasyAR
 4 {
 5 
 6     public class MyEasyARBehaviour : MonoBehaviour, ITargetEventHandler
 7     {
 8         [TextArea(1, 10)]//为key在unity编辑面板的最小行数及最大行数设定
 9         public string Key;//输入的key
10 
11         private void Awake()
12         {
13           
14             ARBuilder.Instance.InitializeEasyAR(Key);//用key去初始化EasyAR
15             ARBuilder.Instance.EasyBuild();
16 
17             /*遍历trackerBehaviour脚本组件,这个trackerBehaviour组件继承自TrackerBaseBehaviour。
18             ImageTargetBaseBehaviour必须先被加载到TrackerBaseBehaviour中它内部的target才能被跟踪。所以在做多目标识别时,每一个
19             ar物体都要加载一个trackerBehaviour组件,因此在这里进行遍历。
20             */
21             foreach (var trackerBehaviour in ARBuilder.Instance.TrackerBehaviours)
22                 trackerBehaviour.RegisterTargetEventHandler(this);
23         }
24 
25         
26        // 以下的代码是对ITargetEventHandler接口的重写
27 
28         void ITargetEventHandler.OnTargetFound(Target trackable)//target找到事件。
29         {
30             Debug.Log("<Global Handler> Found: " + trackable.Id);
31         }
32 
33         void ITargetEventHandler.OnTargetLost(Target trackable)//target丢失事件。
34         {
35             Debug.Log("<Global Handler> Lost: " + trackable.Id);
36         }
37 
38         void ITargetEventHandler.OnTargetLoad(Target trackable, bool status)//target加载结束事件。如果加载成功status为true否则为false。
39         {
40             Debug.Log("<Global Handler> Load target (" + status + "): " + trackable.Id + " -> " + trackable.Name);
41         }
42 
43         void ITargetEventHandler.OnTargetUnload(Target trackable, bool status)//target卸载结束事件。如果卸载成功status为true否则为false。
44         {
45             Debug.Log("<Global Handler> Unload target (" + status + "): " + trackable.Id + " -> " + trackable.Name);
46         }
47     }
48 }
复制代码

接下来让我们打开EasyAR->Scripts目录,我们所要编写的脚本主要都是基于sdk目录里的这几个脚本。这里我们编写一个MyImageTargetBehaviour.cs脚本继承于该目录里的ImageTargetBehaviour脚本。代码如下:

复制代码
 1 using UnityEngine;
 2 
 3 namespace EasyAR
 4 {
 5     public class MyImageTargetBehaviour : ImageTargetBehaviour, ITargetEventHandler
 6     {
 7         protected override void Start()
 8         {
 9             base.Start();
10             HideObjects(transform);
11         }
12         /// <summary>
13         /// 依次隐藏ImageTarget的子物体
14         /// </summary>
15         /// <param name="trans"></param>
16         void HideObjects(Transform trans)
17         {
18             for (int i = 0; i < trans.childCount; ++i)
19                 HideObjects(trans.GetChild(i));
20             if (transform != trans)
21                 gameObject.SetActive(false);
22         }
23         /// <summary>
24         /// 依次显示ImageTarget的子物体
25         /// </summary>
26         /// <param name="trans"></param>
27         void ShowObjects(Transform trans)
28         {
29             for (int i = 0; i < trans.childCount; ++i)
30                 ShowObjects(trans.GetChild(i));
31             if (transform != trans)
32                 gameObject.SetActive(true);
33         }
34 
35         // 以下的代码是对ITargetEventHandler接口的重写
36         void ITargetEventHandler.OnTargetFound(Target target)
37         {
38             ShowObjects(transform);
39             Debug.Log("Found: " + target.Id);
40         }
41 
42         void ITargetEventHandler.OnTargetLost(Target target)
43         {
44             HideObjects(transform);
45             Debug.Log("Lost: " + target.Id);
46         }
47 
48         void ITargetEventHandler.OnTargetLoad(Target target, bool status)
49         {
50             Debug.Log("Load target (" + status + "): " + target.Id + " -> " + target.Name);
51         }
52 
53         void ITargetEventHandler.OnTargetUnload(Target target, bool status)
54         {
55             Debug.Log("Unload target (" + status + "): " + target.Id + " -> " + target.Name);
56         }
57     }
58 }
复制代码

为场景中的EasyAR添加MyEasyARBehaviour脚本,同时移除ImageTarget自带的ImageTargetBehaviour脚本组件,用我们写的MyImageTargetBehaviour组件代替。

我们将网站上获取的key填入MyEasyARBehaviour脚本组件相应的key一栏中即可。

*随后我们准备一个识别图和对应的模型。

在project窗口新建Texture,StreamingAssets文件 夹,分别将识别图lingzhou.jpg导入。新建一个Material名为Mat,将其shader改为Legacy Shaders/Diffuse,用Texture文件夹的lingzhou.jpg作为贴图,再将其赋予场景中的Image Target实例。这是为了在场景中对识别图可视化而进行的操作。而StreamingAssets里的lingzhou.jpg则用作实时比对,需要在 Image Target实例的MyImageTargetBehaviour脚本组件处填写相应路径以及识别图的尺寸,如图所示:

将准备好的模型拖入场景,调整好位置,大小,角度之后将其作为ImageTarget的子物体即可。最终场景如图所示:

打包成apk在手机上运行的效果如图,一个萌妹子跃然纸上。

继续node爬虫 — 百行代码自制自动AC机器人日解千题攻占HDOJ

mikel阅读(885)

前言

不说话,先猛戳 Ranklist 看我排名。

这是用 node 自动刷题大概半天的 “战绩”,本文就来为大家简单讲解下如何用 node 做一个 “自动AC机”。

过程

先来扯扯 oj(online judge)。计算机学院的同学应该对 ACM 都不会陌生,ACM 竞赛是拼算法以及数据结构的比赛,而 oj 正是练习 ACM 的 “场地”。国内比较有名的 oj 有 poj、zoj 以及 hdoj 等等,这里我选了 hdoj (完全是因为本地上 hdoj 网速快)。

在 oj 做题非常简单。以 hdoj 为例,先注册个账号(http://bestcoder.hdu.edu.cn/register.php),然后随便打开一道题(http://acm.hdu.edu.cn/showproblem.php?pid=1000),点击最下面的 Submit 按钮(http://acm.hdu.edu.cn/submit.php?pid=1000),选择提交语言(Language),将答案复制进去,最后再点击 Submit 按钮提交,之后就可以去查看是否 AC(Accepted) 了(http://acm.hdu.edu.cn/status.php)。

用 node 来模拟用户的这个过程,其实就是一个 模拟登录+模拟提交 的过程,根据经验,模拟提交这个 post 过程肯定会带有 cookie。提交的 code 哪里来呢?直接爬取搜索引擎就好了。

整个思路非常清晰:

  1. 模拟登录(post)
  2. 从搜索引擎爬取 code(get)
  3. 模拟提交(post)

模拟登录

首先来看模拟登录,根据经验,这大概是一个 post 过程,会将用户名以及密码以 post 的方式传给服务器。打开 chrome,F12,抓下这个包,有必要时可以将 Preserve log 这个选项勾上。

请求头居然还带有 Cookie,经测试,key 为 PHPSESSID 的这个 Cookie 是请求所必须的,这个 Cookie 哪来的呢?其实你只要一打开 http://acm.hdu.edu.cn/ 域名下的任意地址,服务端便会把这个 Cookie “种” 在浏览器中。一般你登录总得先打开登录页面吧?打开后自然就有这个 Cookie 了,而登录请求便会携带这个 Cookie。一旦请求成功,服务器便会和客户端建立一个 session,服务端表示这个 cookie 我认识了,每次带着这个 cookie 请求的我都可以通过了。一旦用户退出,那么该 session 中止,服务端把该 cookie 从认识名单中删除,即使再次带着该 cookie 提交,服务端也会表示 “不认识你了”。

所以模拟登录可以分为两个过程,首先请求 http://acm.hdu.edu.cn/ 域名下的任意一个地址,并且将返回头中 key 为 PHPSESSID 的 Cookie 取出来保存(key=value 形式),然后携带 Cookie 进行 post 请求进行登录。

// 模拟登录
function login() {
  superagent
    // get 请求任意 acm.hdu.edu.cn 域名下的一个 url
    // 获取 key 为 PHPSESSID 这个 Cookie
    .get('http://acm.hdu.edu.cn/status.php')
    .end(function(err, sres) {
      // 提取 Cookie
      var str = sres.header['set-cookie'][0];
      // 过滤 path
      var pos = str.indexOf(';');

      // 全局变量存储 Cookie,登录 以及 post 代码时候用
      globalCookie = str.substr(0, pos);

      // 模拟登录
      superagent
        // 登录 url
        .post('http://acm.hdu.edu.cn/userloginex.php?action=login')
        // post 用户名 & 密码
        .send({"username": "hanzichi"})
        .send({"userpass": "hanzichi"})
        // 这个请求头是必须的
        .set("Content-Type", "application/x-www-form-urlencoded")
        // 请求携带 Cookie
        .set("Cookie", globalCookie)
        .end(function(err, sres) {
          // 登录完成后,启动程序
          start();
        });
    });
}

模拟 HTTP 请求的时候,有些请求头是必须的,有些则是可以忽略。比如模拟登录 post 时,Content-Type 这个请求头是必须携带的,找了我好久,如果程序一直启动不了,可以试试把所有请求头都带上,逐个进行排查。

搜索引擎爬取 Code

这一部分我做的比较粗糙,这也是我的爬虫 AC 正确率比较低下的原因。

我选择了百度来爬取答案。以 hdu1004 这道题为例,如果要搜索该题的 AC 代码,我们一般会在百度搜索框中输入 hdu1004,而结果展现的页面 url 为 https://www.baidu.com/s?ie=UTF-8&wd=hdu1004。这个 url 还是非常有规律的,https://www.baidu.com/s?ie=UTF-8&wd= 加上 keyword。

百度的一个页面会展现 10 个搜索结果,代码里我选择了 ACMer 在 csdn 里的题解,因为 csdn 里的代码块真是太好找了,不信请看。

csdn 把代码完全放在了一个 class 为 cpp 的 dom 元素中,简直是太友好了有没有!相比之下,博客园等其他地方还要字符串过滤,为了简单省事,就直接选取了 csdn 的题解代码。

一开始我以为,一个搜索结果页有十条结果,每条结果很显然都有一个详情页的 url,判断一下 url 中有没有 csdn 的字样,如果有,则进入详情页去抓 code。但是百度居然给这个 url 加密了!

我注意到每个搜索结果还带有一个小字样的 url,没有加密,见下图。

于是我决定分析这个 url,如果带有 csdn 字样,则跳转到该搜索结果的详情页进行代码抓取。事实上,带有 csdn 的也不一定能抓到 code( csdn 的其他二级域名,比如下载频道 http://download.csdn.net/),所以在 getCode() 函数中写了个 try{}..catch(){} 以免代码出错。

// 模拟百度搜索题解
function bdSearch(problemId) {
  var searchUrl = 'https://www.baidu.com/s?ie=UTF-8&wd=hdu' + problemId;
  // 模拟百度搜索
  superagent
    .get(searchUrl)
    // 必带的请求头
    .set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36")
    .end(function(err, sres) {
      var $ = cheerio.load(sres.text);
      var lis = $('.t a');
      for (var i = 0; i < 10; i++) {
        var node = lis.eq(i);

        // 获取那个小的 url 地址
        var text = node.parent().next().next().children("a").text();

        // 如果 url 不带有 csdn 字样,则返回
        if (text.toLowerCase().indexOf("csdn") === -1)
          continue;

        // 题解详情页 url
        var solutionUrl = node.attr('href');
        getCode(solutionUrl, problemId);
      }
    });
}

bdSearch() 函数传入一个参数,为 hdoj 题目编号。然后去爬取百度获取题解详情页的 url,经过测试 爬取百度必须带有 UA!其他的就非常简单了,代码里的注释很清楚。

// 从 csdn 题解详情页获取代码
function getCode(solutionUrl, problemId) {

  superagent.get(solutionUrl, function(err, sres) {
    // 为防止该 solutionUrl 可能不是题解详情页
    // 没有 class 为 cpp 的 dom 元素
    try {
      var $ = cheerio.load(sres.text);

      var code = $('.cpp').eq(0).text();

      if (!code)
        return;
      
      post(code, problemId);
    } catch(e) {

    }
    
  });
}

getCode() 函数根据题解详情页获取代码。前面说了,csdn 的代码块非常直接,都在一个类名为 cpp 的 dom 元素中。

模拟提交

最后一步来看模拟提交。我们可以抓一下这个 post 包看看长啥样。

很显然,Cookie 是必须的,我们在第一步模拟登录的时候已经得到这个 Cookie 了。因为这是一个 form 表单的提交,所以 Content-Type 这个请求 key 也需要携带。其他的话,就在请求数据中了,problemid 很显然是题号,code 很显然就是上面求得的代码。

// 模拟代码提交
function post(code, problemId) {
  superagent
    .post('http://acm.hdu.edu.cn/submit.php?action=submit')
    .set('Content-Type', 'application/x-www-form-urlencoded')
    .set("Cookie", globalCookie)
    .send({"problemid": problemId})
    .send({"usercode": code})
    .end(function (err, sres) {
    });
}

完整代码

完整代码可以参考 Github

其中 singleSubmit.js 为单一题目提交,实例代码为 hdu1004 的提交,而 allSubmit.js 为所有代码的提交,代码中我设置了一个 10s 的延迟,即每 10s 去百度搜索一次题解,因为要爬取 baidu、csdn 以及 hdoj 三个网站,任意一个网站 ip 被封都会停止整个灌水机的运作,所以压力还是很大的,设置个 10s 的延迟后应该木有什么问题了。

学习 node 主要就是因为对爬虫有兴趣,也陆陆续续完成了几次简单的爬取,可以移步我的博客中的 Node.js 系列。这之前我把代码都随手扔在了 Github 中,居然有人 star 和 fork,让我受宠若惊,决定给我的爬虫项目单独建个新的目录,记录学习 node 的过程,项目地址 https://github.com/hanzichi/funny-node。我会把我的 node 爬虫代码都同步在这里,同时会记录每次爬虫的实现过程,保存为每个小目录的 README.md 文件。

后续优化

仔细看,其实我的爬虫非常 “智弱”,正确率十分低下,甚至不能 AC hdu1001!我认为可以从以下几个方面进行后续改进:

  • 爬取 csdn 题解详情页时进行 title 过滤。比如爬取 hdu5300 的题解 https://www.baidu.com/s?ie=UTF-8&wd=hdu5300,搜索结果中有 HDU4389,程序显然没有预料到这一点,而会将之代码提交,显然会 WA 掉。而如果在详情页中进行 title 过滤的话,能有效避免这一点,因为 ACMer 写题解时,title 一般都会带 hdu5300 或者 hdoj5300 字样。
  • 爬取具体网站。爬取百度显然不是明智之举,我的实际 AC 正确率在 50% 左右,我尼玛,难道题解上的代码一半都是错误的吗?可能某些提交选错了语言(post 时有个 language 参数,默认为 0 为 G++提交,程序都是以 G++ 进行提交),其实我们并不能判断百度搜索得到的题解代码是否真的正确。如何提高正确率?我们可以定向爬取一些题解网站,比如 http://accepted.com.cn/ 或者 http://www.acmerblog.com/,甚至可以爬取 http://acm.hust.edu.cn/vjudge/problem/status.action 中 AC 的代码!
  • 实时获取提交结果。我的代码写的比较粗糙,爬取百度搜索第一页的 csdn 题解代码,如果有 10 个就提交 10 个,如果没有那就不提交。一个更好的策略是实时获取提交结果,比如先提交第一个,获取返回结果,如果 WA 了则继续提交,如果 AC 了那就 break 掉。获取提交结果的话,暂时没有找到这个返回接口,可以从 http://acm.hdu.edu.cn/status.php 中进行判断,也可以抓取 user 详情页 http://acm.hdu.edu.cn/userstatus.php?user=hanzichi

PS:我在 hdoj 的账号用户名和密码均为 hanzichi,有兴趣的可以用我的账号继续刷题。

其他

其实 hdoj 排行榜第一页有不少都是机器人刷的。

比如 NKspider 这位仁兄,他是用 C++ 写的,具体可以参考 http://blog.csdn.net/nk_test/article/details/49497017

再比如 beautifulzzzz 这位哥们,是用 C# 刷的,具体可以参考 http://www.cnblogs.com/zjutlitao/p/4337775.html

同样用 C# 刷的还有 CSUSTrobot,过程可以参考 http://blog.csdn.net/qwb492859377/article/details/47448599

思路都是类似的,它们的代码都不短,node 才百来行就能搞定,实在是太强大了!

[转载]构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(1)-前言与目录(持续更新中...) - ymnets - 博客园

mikel阅读(729)

来源: [转载]构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(1)-前言与目录(持续更新中…) – ymnets – 博客园

开篇:系统已经由MVC4+EF5+UNITY2.X+Quartz 2.0+easyui 1.3.4无缝接入 MVC5+EF6+Unity4.x+Quartz 2.3 +easyui 1.4.3.

从50节起为MVC5+EF6+Unity4.x+Quartz 2.3 +easyui 1.4.3.的特性文章 所以你们也要更新你们的环境

功能不变属于无缝接入,最大改变只在UI

VS2013+SQL2012

演示地址暂时关闭

第2讲源码下载  访问密码 ae54

最新代码生成器+17讲源码下载   访问密码 15d3

SwfUpload在MVC4下多文件上传 密码:  访问密码 0fc2

也可以有偿获取一份最新源码联系QQ:729994997 价格 666RMB

 升级后界面效果如下:

任务调度系统界面 http://www.cnblogs.com/ymnets/p/5065154.html

系统权限全套完整图 http://www.cnblogs.com/ymnets/p/5065201.html

系统配置部分图 http://www.cnblogs.com/ymnets/p/5065232.html

———————————————————————————————————————————————–

曾几何时我想写一个系列的文章,但是由于工作很忙,一直没有时间更新博客,但你一定会喜欢这个系列的

前言

声明:本系统的读者可能需要一些职场的项目经验

您可以在阅读本系列文章之前必须对以下技术有个初步的了解。

1. ASP.NET MVC 基础

为什么选择MVC,假如您是个有梦想的人,那么你也应该喜欢极致的东西,而不顾一切付出
表现层的性能可以优化到极致
强迫学习前端语言html以及css、JavaScript
关注点分离
原生态url routing,便于url rewrite利于SEO优化
Control与View完全分离,利于换肤且没有性能损失
2. EF ORM框架的原理

EF5.0相比他的性能提升了,事务很重要,你可以关注一下EF6因为他开源了
3. 依赖注入(Ioc控制反转)原理

更好的关注点分离,更快的代码风格,更快捷的单元测试
4. PowerDesign数据库建模工具

更好的理解模型
5. 简单的js,JQuery,css+html基础

极致的界面

这个系列我们能大约能得到以下内容,最后能得到一份优质的代码。

1. easyui构建系统前端框架

2. EF DataBase Frist构建数据库

3. unity2.x注入,AOP面向方面编程技巧

4. 一个样例程序,他实现了利用easyui datagrid的列表显示,用了JQuery ajax的增删改。

5. 数据验证注解

6. 权限管理系统,他将精确到每个按钮,我们将在过滤器判断权限,你的代码非常简洁,只需要一个数据注解就完成了验证

7. T4模版或代码生成器,有了T4模版,妈妈再也不用当心我写代码了要熬夜了

8. MVC4 用微软自带的报表制作报表在razor视图展示和导出

9. 扩展….

目录:(尽管很小心,但是文章难免有很些错别字和思维错误)

第二节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(2)-easyui构建前端页面框架

第三节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(3)-漂亮系统登陆界面

第四节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(4)-构建项目解决方案 创建EF Model Frist模式

第五节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(5)-EF增删改查by糟糕的代码

第六节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(6)-Unity 2.x依赖注入by运行时注入[附源码]

第七节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(7)-MVC与EasyUI DataGrid

第八节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(8)-MVC与EasyUI DataGrid 分页

第九节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(9)-MVC与EasyUI结合增删改查

第十节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(10)-系统菜单栏[附源码]

第十一节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(11)-系统日志和异常的处理①

第十二节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(12)-系统日志和异常的处理②

第十三节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(13)-系统日志和异常的处理③

第十四节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(14)-EasyUI缺陷修复与扩展[附源码]

第十五节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(15)-权限管理系统准备

第十六节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(16)-权限管理系统-漂亮的验证码

第十七节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(17)-LinQ动态排序

第十八节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(18)-权限管理系统-表数据

第十九节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(19)-权限管理系统-用户登录

第二十节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(20)-权限管理系统-根据权限获取菜单

第二十一节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(21)-权限管理系统-跑通整个系统

第二十二节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(22)-权限管理系统-模块导航制作

第二十三节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(23)-权限管理系统-角色组模块

第二十四节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(24)-权限管理系统-将权限授权给角色

第二十五节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(25)-权限管理系统-系统管理员

第二十六节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(26)-权限管理系统-分配角色给用户

第二十七节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(27)-权限管理系统-分配用户给角色

第二十八节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(28)-系统小结

第二十九节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(29)-T4模版

第三十节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(30)-本地化(多语言)

第三十一节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(31)-MVC使用RDL报表

第三十二节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(32)-swfupload多文件上传[附例子]

第三十三节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(33)-数据验证共享

第三十四节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析

第三十五节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(35)-文章发布系统②-构建项目

第三十六节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(36)-文章发布系统③-kindeditor使用

第三十七节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(37)-文章发布系统④-百万级数据和千万级数据简单测试

第三十八节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(38)-Easyui-accordion+tree漂亮的菜单导航

第三十九节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(39)-在线人数统计探讨

第四十节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(40)-精准在线人数统计实现-【过滤器+Cache】

第四十一节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(41)-组织架构

第四十二节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(42)-工作流设计-数据库表建立

第四十三节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(43)-工作流设计-字段分类设计

第四十四节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(44)-工作流设计-设计表单

第四十五节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(45)-工作流设计-设计步骤

第四十六节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(46)-工作流设计-设计分支

第四十七节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(47)-工作流设计-补充

第四十八节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(48)-工作流设计-起草新申请

第四十九节:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(49)-工作流设计-我的申请

—————————————————-重大版本转变(但是功能不变,无缝接 入)———————————————————–

第五十节:构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(50)-Easyui 扁平化皮肤

第五十一节:构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(51)-系统升级

第五十二节:构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(52)-美化EasyUI皮肤和图标

第五十三节: 构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(53)-工作流设计-我的批阅

第五十四节: 构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(54)-工作流设计-所有流程监控

 

 

附录:项目最后理想完整图(黄色表示已经发布的功能)

作者:YmNets
出处:http://ymnets.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]ASP.NET 系列:RBAC权限设计 - #王刚 - 博客园

mikel阅读(1025)

来源: [转载]ASP.NET 系列:RBAC权限设计 – #王刚 – 博客园

权限系统的组成通常包括RBAC模型、权限验证、权限管理以及界面访问控制。现有的一些权限系统分析通常存在以下问题:

(1)没有权限的设计思路

认为所有系统都可以使用一套基于Table设计的权限系统。事实上设计权限系统的重点是判断角色的稳定性和找出最小授权需求。角色的稳定性决定了系统是通过角色判断权限还是需要引入RBAC方式,最小授权需求防止我们过度设计导致超出授权需求的权限粒度。

(2)没有独立的RBAC模型的概念

直接使用实体类表示RBAC模型,导致本身本应该只有几行代码且可以在项目级别复用的RBAC模型不仅不能复用,还要在每个项目无论是否需要都要有User、Role、Permission等实体类,更有甚者把实体类对应的数据表的结构和关联当作权限系统的核心。

(3)权限的抽象错误

我们通常既不实现操作系统也不实现数据库,虽然操作系统的权限和数据库的权限可以借鉴,但一般的业务系统上来就弄出一堆增删该查、访问和执行这样的 权限,真是跑偏的太远了。首先业务层次的操作至少要从业务的含义出发,叫浏览、编辑、审核等这些客户容易理解或就是客户使用的词汇更有意义,更重要的是我 们是从角色中按照最小授权需求抽象出来的权限,怎么什么都没做就有了一堆权限呢。

(4)将界面控制和权限耦合到一起

开始的时候我们只有实体类Entities、应用服务Service以及对一些采用接口隔离原则定义的接口Interfaces,通常这个时候我们 在Service的一个或多个方法会对应1个权限,这个时候根本界面还没有,就算有界面,也是界面对权限的单向依赖,对于一个系统,可能不止有1个以上类 型的客户端,每个客户端的界面访问控制对权限的依赖都应该存储到客户端,况且不同的客户端对这些数据各奔没有办法复用。

下面我们使用尽可能少的代码来构建一个可复用的既不依赖数据访问层也不依赖界面的RBAC模型,在此基础上对角色的稳定性和权限的抽象做一个总结。

1.创建RBAC模型

使用POCO创建基于RBAC0级别的可复用的User、Role和Permissin模型。

复制代码
using System.Collections.Generic;

namespace RBACExample.RBAC
{
    public class RBACUser
    {
        public string UserName { get; set; }

        public ICollection<RBACRole> Roles { get; set; } = new List<RBACRole>();
    }

    public class RBACRole
    {
        public string RoleName { get; set; }

        public ICollection<RBACPermission> Permissions { get; set; } = new List<RBACPermission>();
    }

    public class RBACPermission
    {
        public string PermissionName { get; set; }
    }
}
复制代码

2.创建安全上下文

创建安全上下文RBACContext用于设置和获取RBACUser对象。RBACContext使用线程级别的静态变量保存RBACUser对象,不负责实体类到RBAC对象的转换,保证复用性。

复制代码
using System;

namespace RBACExample.RBAC
{
    public static class RBACContext
    {
        [ThreadStatic]
        private static RBACUser _User;

        private static Func<string, RBACUser> _SetRBACUser;

        public static void SetRBACUser(Func<string, RBACUser> setRBACUser)
        {
            _SetRBACUser = setRBACUser;
        }

        public static RBACUser GetRBACUser(string username)
        {
            return _User == null ? (_User = _SetRBACUser(username)) : _User;
        }

        public static void Clear()
        {
            _SetRBACUser = null;
        }
    }
}
复制代码

3.自定义RoleProvider

自定义DelegeteRoleProvider,将权限相关的GetRolesForUser和IsUserInRole的具体实现委托给静态代理,保证复用性。

复制代码
using System;
using System.Web.Security;

namespace RBACExample.RBAC
{
    public class DelegeteRoleProvider : RoleProvider
    {
        private static Func<string, string[]> _GetRolesForUser;

        private static Func<string, string, bool> _IsUserInRole;

        public static void SetGetRolesForUser(Func<string, string[]> getRolesForUser)
        {
            _GetRolesForUser = getRolesForUser;
        }

        public static void SetIsUserInRole(Func<string, string, bool> isUserInRole)
        {
            _IsUserInRole = isUserInRole;
        }

        public override string[] GetRolesForUser(string username)
        {
            return _GetRolesForUser(username);
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            return _IsUserInRole(username, roleName);
        }

        #region NotImplemented

        #endregion NotImplemented
    }
}
复制代码

在Web.config中配置DelegeteRoleProvider

复制代码
<system.web>
    <compilation debug="true" targetFramework="4.5.2"/>
    <httpRuntime targetFramework="4.5.2"/>
      <authentication mode="Forms">
      <forms loginUrl="~/Home/Login" cookieless="UseCookies" slidingExpiration="true" />
    </authentication>
    <roleManager defaultProvider="DelegeteRoleProvider" enabled="true">
      <providers>
        <clear />
        <add name="DelegeteRoleProvider" type="RBACExample.RBAC.DelegeteRoleProvider" />
      </providers>
    </roleManager>
  </system.web>
复制代码

4.配置RBACContext和DelegeteRoleProvider

在Application_Start中配置RBACContext和DelegeteRoleProvider依赖的代理。为了便于演示我们直接创建RBACUser对象,在后文中我们再针对不同系统演示实体类到RBAC模型的映射。

复制代码
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            RBACContext.SetRBACUser(u =>
            {
                return new RBACUser
                {
                    UserName = u,
                    Roles = new List<RBACRole> {
                        new RBACRole
                        {
                            RoleName="admin",
                            Permissions = new List<RBACPermission> {
                                new RBACPermission {
                                     PermissionName="admin"
                                }
                            }
                        }
                    }
                };
            });
            DelegeteRoleProvider.SetGetRolesForUser(userName => RBACContext.GetRBACUser(userName).Roles.SelectMany(o => o.Permissions).Select(p => p.PermissionName).ToArray());
            DelegeteRoleProvider.SetIsUserInRole((userName, roleName) => RBACContext.GetRBACUser(userName).Roles.SelectMany(o => o.Permissions).Any(p => p.PermissionName == roleName));
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
复制代码

5.在ASP.NET MVC中通过.NET API使用

User.IsInRole和AuthorizeAttribute此时都可以使用,我们已经完成了一个RBAC权限中间层,即隔离了不同系统的具体实现,也不用使用新的API调用。如果是服务层,使用Thread.CurrentPrincipal.IsInRole和PrincipalPermissionAttribute。

复制代码
namespace RBACExample.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Login(string returnUrl)
        {
            FormsAuthentication.SetAuthCookie("admin", false);
            return Redirect(returnUrl);
        }

        public ActionResult Logoff()
        {
            FormsAuthentication.SignOut();
            return Redirect("/");
        }

        public ActionResult Index()
        {
            return Content("home");
        }

        [Authorize]
        public ActionResult Account()
        {
            return Content(string.Format("user is IsAuthenticated:{0}", User.Identity.IsAuthenticated));
        }

        [Authorize(Roles = "admin")]
        public ActionResult Admin()
        {
            return Content(string.Format("user is in role admin:{0}", User.IsInRole("admin")));
        }
    }
}
复制代码

6.扩展AuthorizeAttribute,统一配置授权

AuthorizeAttribute的使用将授权分散在多个Controller中,我们可以扩展AuthorizeAttribute,自定义一个MvcAuthorizeAttribute, 以静态字典保存配置,这样就可以通过代码、配置文件或数据库等方式读取配置再存放到字典中,实现动态配置。此时可以从Controller中移除 AuthorizeAttribute。如前文所述,客户端的访问控制与权限的匹配应该存储到客户端为最佳,即使存放到数据库也不要关联权限相关的表。

复制代码
namespace RBACExample.RBAC
{
    public class MvcAuthorizeAttribute : AuthorizeAttribute
    {
        private static Dictionary<string, string> _ActionRoleMapping = new Dictionary<string, string>();

        public static void AddConfig(string controllerAction, params string[] roles)
        {
            var rolesString = string.Empty;
            roles.ToList().ForEach(r => rolesString += "," + r);
            rolesString = rolesString.TrimStart(',');
            _ActionRoleMapping.Add(controllerAction, rolesString);
        }

        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            var key = string.Format("{0}{1}", filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName);
            if (_ActionRoleMapping.ContainsKey(key))
            {
                this.Roles = _ActionRoleMapping[key];
                base.OnAuthorization(filterContext);
            }
        }
    }
}
复制代码

通过GlobalFilterCollection配置将MvcAuthorizeAttribute配置为全局Filter。

复制代码
         public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            MvcAuthorizeAttribute.AddConfig("AccountIndex");
            MvcAuthorizeAttribute.AddConfig("AdminIndex", Permission.AdminPermission);
            filters.Add(new MvcAuthorizeAttribute());
        }
复制代码

7.按需设计实体类

当RBAC模型不直接依赖实体类时,实体类可以按需设计,不再需要为 了迁就RBAC的关联引入过多的实体,可以真正做到具体问题具体分析,不需要什么系统都上Role、Permission等实体类,对于角色稳定的系统, 既减少了系统的复杂度,也减少了大量后台的功能实现,也简化了后台的操作,不用什么系统都上一套用户头疼培训人员也头疼的权限中心。

(1)使用属性判断权限的系统

有些系统,比如个人博客,只有一个管理员角色admin,admin角色是稳定的权限不变的,所以既不需要考虑使用多个角色也不需要再进行权限抽 象,因此使用User.IsAdmin属性代替Role和Permission就可以,没必要再使用Role和Permission实体类,增大代码量。 后台进行权限管理只需要实现属性的编辑。

复制代码
              RBACContext.SetRBACUser(u =>
            {
                var user = new UserEntity { UserName = "admin", IsAdmin = true };
                var rbacUser = new RBACUser { UserName = user.UserName };
                if (user.IsAdmin)
                {
                    rbacUser.Roles.Add(new RBACRole
                    {
                        RoleName = "admin",
                        Permissions = new List<RBACPermission> {new RBACPermission {
                                     PermissionName="admin"
                                } }
                    });
                }
                return rbacUser;
            });
复制代码

(2)使用角色判断权限的系统

有些系统,比如B2C的商城,虽然有多个角色,但角色都是稳定的权限不变的,使用User和Role就可以, 没有必要为了应用RBAC而引入Permission类,强行引入虽然实现了Role和Permission的分配回收功能,但实际上不会使用,只会使用 User的Role授权功能。权限的抽象要做到满足授权需求即可,在角色就能满足授权需求的情况下,角色和权限的概念是一体的。后台实现权限管理只需要实 现对用户角色的管理。

(3)需要对角色进行动态授权的系统

有些系统,比如ERP,有多个不稳定的角色,每个角色通常对应多项权限,由于组织机构和人员职责的变化,必须对角色的权限进行动态分配,需要使用User、Role和Permission的组合。 User由于权限范围的不同,通常具有一个或多个权限,不同的User具有的角色通常不再是平行关系而是层级关系,如果不从Role中抽象 Permission,需要定义大量的Role对应不同权限的组合,遇到这种情况时,分离权限,对角色进行权限管理就成了必然。后台实现权限管理即需要实 现对用户角色的管理也需要实现对角色权限的管理。

复制代码
              RBACContext.SetRBACUser(u =>
            {
                var user = ObjectFactory.GetInstance<IUserService>().GetUserByName(u);
                return new RBACUser
                {
                    UserName = user.UserName,
                    Roles = user.Roles.Select(r => new RBACRole
                    {
                        RoleName = r.RoleName,
                        Permissions = r.Permissions.Select(p => new RBACPermission
                        {
                            PermissionName = p.Name
                        }).ToList()
                    }).ToList()
                };
            });
复制代码

8.总结

使用RBAC模型和.NET的权限验证API解决了权限系统的复用问题,从角色的稳定性出发防止实体类规模膨胀,通过最小授权需求的抽象可以防止权限的滥用。

参考:

(1)https://en.wikipedia.org/wiki/Role-based_access_control

(2)http://csrc.nist.gov/groups/SNS/rbac/faq.html

(3)http://www.codeproject.com/Articles/875547/Custom-Roles-Based-Access-Control-RBAC-in-ASP-NET

(4)http://www.ibm.com/developerworks/cn/java/j-lo-rbacwebsecurity/

Demo下载

随笔里的文章都是干货,都是解决实际问题的,这些问题我在网上找不到更好的解决方法,才会有你看到的这一篇篇随笔,因此如果这篇博客内容对您稍有帮助或略有借鉴,请您推荐它帮助更多的人。如果你有提供实际可行的更好方案,请推荐给我。

[转载]autorun.inf(自动运行文件)_蓝天_新浪博客

mikel阅读(1047)

autorun.inf(自动运行文件)_蓝天_新浪博客,蓝天,

来源: [转载]autorun.inf(自动运行文件)_蓝天_新浪博客

autorun.inf文件是从Windows95开始的,最初用在其安装盘里,实现自动安装,以后的各版本都保留了该文件并且部分内容也可用于其他存储设备。
其结构有三个部分:[AutoRun] [AutoRun.Alpha] [DeviceInstall]
[AutoRun]适用于Windows95以上系统与32位以上CD-ROM,必选。
[AutoRun.alpha]适用于基于RISC的计算机光驱,适用系统为Windows NT 4.0,可选。
[DeviceInstall]适用于Windows XP以上系统,可选。
[AutoRun]部分的命令及其详解
1、DefaultIcon
含义:指定应用程序的默认图标。
格式:
DefalutIcon=图标路径名[,序号]
参数:
图标文件名:应用程序的默认图标路径名,格式可以为.ico、.bmp、.exe、.dll。当文件格式为.exe和.dll时,有时需要使用序号来指定图标。
序号:当文件格式为.exe和.dll时,文件可能包括多余一个图标,此时需要使用序号来指定图标,需要注意的是,序号是从0开始的。
备注:
应用程序的默认图标将在windows explorer核心的驱动显示窗口中替代设备的默认图标来显示。
图标路径名的默认目录是设备根目录。
2、Icon
含义:指定设备显示图标。
格式:
Icon=图标路径名[,序号]
参数:
图标文件名:应用程序的默认图标路径名,格式可以为.ico、.bmp、.exe、.dll。当文件格式为.exe和.dll时,有时需要使用序号来指定图标。
序号:当文件格式为.exe和.dll时,文件可能包括多余一个图标,此时需要使用序号来指定图标,需要注意的是,序号是从0开始的。
备注:
设备显示图标将在windows explorer核心的驱动显示窗口中替代设备的默认图标来显示。
图标路径名的默认目录是设备根目录。
当存在应用程序默认图标(DefaultIcon)时,本命令无效。
3、Label
含义:指定设备描述
格式:
Label=描述
参数:
描述:任意文字,可以包括空格。
备注:
设备描述将在windows explorer核心的驱动显示窗口中替代设备的默认描述卷标来显示。
在非windows explorer核心的驱动显示窗口中(例如右击设备选择属性)显示的仍然是设备的卷标。
4、Open
含义:指定设备启用时运行之命令行。
格式:
Open=命令行
(命令行:程序路径名 [参数])
参数:
命令行:自动运行的命令行,必须是.exe、.com、.bat文件,其他格式文件可以使用start.exe打开或使用ShellExecute命令。
备注:
命令行的起始目录是设备根目录和系统的$Path环境变量。
5、ShellExecute
含义:
指定设备启用时执行文件。(操作系统支持未知)
格式:
ShellExecute=执行文件路径名 [参数]
参数:
执行文件路径名:设备启用时执行文件路径名。可以是任意格式文件。系统会调用设置的程序执行此文件。
参数:参数,根据执行文件作调整
备注:
命令行的起始目录是设备根目录和系统的$Path环境变量。
6、Shell关键字Command
含义:
定义设备右键菜单执行命令行。
格式:
Shell关键字Command=命令行
(命令行:程序路径名 [参数])
参数:
命令行:自动运行的命令行,必须是.exe、.com、.bat文件,其他格式文件可以使用start.exe打开。
备注:
命令行的起始目录是设备根目录和系统的$Path环境变量。
7、Shell关键字
含义:定义设备右键菜单文本。
格式:
Shell关键字=文本
参数:
关键字:用以标记菜单,可以使用任何字符表示,包括空格。
文本:在右键菜单中显示的文本。可以使用任何字符,不能存在空格。
备注:
在同一Autorun.inf文件中,不同右键菜单关键字不同,相同右键菜单关键字相同。
右键菜单文本中可以使用&设定加速键,&&输出一个&。
Shell关键字Command命令Shell关键字两者缺一不可,顺序无所谓。
当不存在Open、ShellExecute与Shell命令时,设备启用时运行第一个设备右键菜单指定命令。
8、Shell
含义:定义设备启用时运行之设备右键命令。
格式:
Shell=关键字
参数:
关键字:标记过的菜单关键字
备注:
Shell指定的关键字可以在AutoRun.inf文件的任意部分。
OpenShellExecuteShell命令后定义的优先级高。
[编辑本段]常见U盘病毒autorun.inf里面的内容及结构:
[AutoRun]
//表示AutoRun部分开始

Icon=X:\“图标”.ico
//给X盘一个图标

Open=X:\“程序”.exe或者“命令行”
//双击X盘执行的程序或命令

shell\“关键字”=“鼠标右键菜单中加入显示的内容”
//右键菜单新增选项

shell\“关键字”\command=“要执行的文件或命令行”
//选中右键菜单新增选项执行的程序或者命令
[3][4]
[编辑本段][AutoRun.alpha]部分的命令简介
[AutoRun.alpha]部分的命令与[AutoRun]部分的命令相同,只不过在基于RISC的计算机光驱中,[AutoRun.alpha]优先级高于[AutoRun]
[DeviceInstall]部分命令及其详解
DriverPath
含义:定义搜索驱动程序目录。
格式:
DriverPath=驱动程序路径
参数:
驱动程序路径:驱动程序所在路径,包括其子路径。
备注:
Windows XP以上支持。
仅CD-ROM支持
当系统监测到一个新的设备时,会提示用户寻找设备的驱动程序。当用户点选此CD-ROM时,当[DeviceInstall]部分存在时,系统会按照 DriverPath所标记的路径出寻找驱动程序。未标记的路径系统将忽略查找。当[DeviceInstall]部分不存在时,系统将进行完全查找。
如果不希望系统在此CD-ROM中搜索驱动程序,只加一行[DeviceInstall]不加DriverPath命令即可。
系统识别该文件过程如下:
系统在插入U盘的时候会根据这个AUTORUN.INF文件在注册表[HKEY_CURRENT_USER\Software\Microsoft \Windows\CurrentVersion\Explorer\MountPoints2]下建立一个u盘的关联项,使双击打开指定的程序(如病毒 程序)。
Windows 2000/XP下如何删除autorun.inf文件夹在命令提示符中,输入rd (文件夹路径)即可删除文件夹
如文件夹内有内容可把rd替换为deltree来完成删除。
========================================================================
清除autorun病毒的批处理文件代码
u盘插上
首先新建个文本文档,在里面添加以下内容:
@echo on
taskkill /im explorer.exe /f
rem 结束病毒进程(以u.vbe病毒的进程w.exe为例)
taskkill /im w.exe
start reg add HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\EXplorer\Advanced /v ShowSuperHidden /t REG_DWORD /d 1 /f
start reg import kill.reg
del c:\autorun.* /f /q /as
del %SYSTEMROOT%\system32\autorun.* /f /q /as
del d:\autorun.* /f /q /as
del e:\autorun.* /f /q /as
del f:\autorun.* /f /q /as
del g:\autorun.* /f /q /as
del h:\autorun.* /f /q /as
del i:\autorun.* /f /q /as
del j:\autorun.* /f /q /as
del k:\autorun.* /f /q /as
del l:\autorun.* /f /q /as
start explorer.exe
=====到这里为止(这行不用复制)==========================
其次打开我的电脑,在菜单栏里选择“工具-文件夹选项-查看”,将“隐藏已知文件类型的扩展名”前面的勾去掉-确定-退出窗口。
再次将刚才新建的那个文件文档的文件名,由“新建文本文档.txt”改为“u.vbe病毒消除.bat”。
最后直接双击它就能清除这个病毒了!
【另外】对于杀毒软件产生的此类文件夹(如超级巡警),可用DOS命令快速干净的删除,方法如下
假设autorun.inf文件夹是在D盘,操作如下: 打开“开始”,选择“运行”,输入“CMD”,打开命令行窗口,在命令行窗口中输入一下命令:
第一步:输入D: 然后回车
第二步:输入rmdir /s autorun.inf 然后回车
第三步:当出现提示时,按“Y”,并回车
其他盘照此方法执行即可!! !
此文引自:http://hi.baidu.com/liliang101/blog/item/a5f354b154157f5e0923025a.html