[转载]Visual Studio 2010中敏捷开发流程模板的应用

mikel阅读(1175)

[转载]Visual Studio 2010中敏捷开发流程模板的应用 – 周雪峰的博客 – 博客园.

本文将会为您讲述如何使用TFS 2010 MSF Agile 5.0流程模板为迭代项目而提供的工作簿。首先,让我们看一看Visual Studio 2010 IDE中的Team Explorer插件:

Team Explorer

接下来,我们将会进入到SharePoint project portal中,为大家展示Team Explorer是如何模拟它的结构的。我们首先右击名字是Team Project的标题的那个节点,如下图所示:

模拟结构

这会让我们进入到SharePoint project portal中,如下图所示。注意Team Explorer是如何模拟这个结构的,你可以创建新文件夹,上传文档,或复制/粘贴现有的文档或文件夹。

SharePoint project portal

现在,让我们回到Team Explorer,在Team Explorer中,我们将会选择“Product Planning”工作簿(对于这个Team Project来说,它是“Product Backlog”):

回到Team Explorer

在 双击这个“Product Planning”工作簿以后,我们现在可以看到下面这张“Product Backlog”工作表。在这里,你可以为整个项目批量录入工作项。Stack Rank作业,设置Area Paths,和在项目等级上录入Story Points来平衡workload等工作都可以在这个工作簿中完成。

Product Planning

如果你想修改查询,你可以选择“Configure”下拉列表框,然后选择“List”项,如下图所示:

修改查询

选择List以后,你将会看到一个对话框,这个对话框可以让你选择你喜欢的查询。默认的Product Backlog是一个flat query,所以,我建议你改成hierarchical query。

现 在,我们将会选择“Iterations”工作表,如下图所示,在“Iterations”工作表中,我们可以为定义的每个迭代输入开始和结束的 日期。基于在“Product Backlog”工作表中录入的Story Points,你将会在“Velocity”图表中看到这个workload。在你项目开始的时候,你将会看到有什么事情被计划了,在某个迭代中,对于那 些已经完成的用户故事,你可以看到交付这个用户故事所花费的小时数的增加,以及在这个图表中的颜色编码。还有一点需要注意一下,Team Size字段可以粗略地表示在某个迭代中交付的工作量。

选择“Iterations”工作表

接下来,我们一起来看一下“Interruptions”工作表,它可以有效地屏蔽掉那些在项目进行期间无法工作的那些日期。

回到Team Explorer

现 在,让我们回到Team Explorer中,在Team Explorer中,我们将会选择第一个迭代的Iteration Backlog。对于每个迭代来说,我们都会得到一个iteration backlog。TFS(Team Foundation Server)给每个Team Project预置了3个迭代,但是你可以复制和粘贴迭代文件夹,让它指向Team Queries中的合适的查询。你必须要在Team Queries中执行复制/粘贴操作,为每个迭代修改查询。一般来说,在理想情况下,如果在Team Queries中对一个迭代执行了执行拷贝/粘贴/修改操作,然后紧接着就应该在Shared Documents文件夹下对一个迭代执行复制/粘贴/修改操作。

做Capacity计划

双 击以后,我们将会看到“Iteration Backlog”工作簿,如下图所示。在这个工作簿中,你可以管理来自于那些工作表的所有迭代——把任务安排到特定的迭代中,在“燃烧工作表”中,针对燃 烧图设置各个迭代的日期,为团队成员(私人事件或假期)设置中断,为团队成员做Capacity计划。(和2008版本相比,这是一个巨大的改进,仅次于 按等级划分的工作项)

通过Product Backlog,你可以通过选择“Configure”下拉列表修改underlying query。如下图所示:

当你从那个下拉列表中选择“List”的时候,你将会看到一个对话框,这个对话框可以让你选择不同的underlying query,如下图所示:

当我们选择Settings工作表的时候,我们可以为这个迭代输入一些日期,而且,这个工作表还可以计算天数,如下图所示:

接下来,我们可以选择“Interruptions”工作表,在这个工作表中,我们可以为计划的中断输入一些日期,以及这个特定的迭代中的一些假期。在“Capacity”工作表中,可以从各个capacity中减去这些日期。

接 下来。我们看一看比较重要的“Capacity”工作表。仅次于“Burndown”工作表,在任何一个迭代中,这个工作表都是第二有用的工作 表。请注意,我已经安排到“Iteration Backlog”中的任务都反映在这里了,你也可以看到在“Interruptions”工作表中指定的假期也被考虑进来了(也就是说,对于这个迭代来 说,有效的工作时间是15天,而不是19天)。还有,Hours/Day字段被用来表示某个团队成员的“理想”工作时间(根据我的经验,一般是6个小时, 所以,开发者们进行估算的时候一定要注意,真正的估算值是不包括饮水机旁的闲聊时间,上洗手间的时间,或与其他重要的人进行沟通的时间的。)

在“Individual Capacity”图表中,绿色的区域是团队成员可以工作的时间的总数。在这个迭代中,蓝色的部分是实际被分配的工作量。在理想情况下,分配给一个团队成 员的工作应该和他可以完成的工作量相当。如果一个团队成员落后了,会通过Over字段(这个字段是基于每天录入的时间的,我稍后将会讨论这方面的内容)反 映在这个图表中,然后,这个工作表中的工作可以被重新调整。另外,任何时间你都可以回到“Interruptions”工作表,为某个团队成员增加更多的 时间,然后再回到”Capacity“工作表重新进行调整。

至于团队成员任务的时间录入,在客户的重压之下,这是我的主要工作。十分简单,不用每天都录入时间,燃烧图和报告都是没有意义的。在Task工作项中,你将会看到所有重要的字段,如下图所示:

一 般来说,当团队成员们坐下来,把整个团队当成一个整体来进行估算的时候(我推荐这样做),每个团队成员都会做一个初步的估算。当团队成员开始完成 某个特定的任务的时候,每天,他们都在推进这个任务,他们将会输入要完成这个任务,还需要多少个小时,以及在那天完成的小时数。原始的估算值不应该被改 变,因为TFS(Team Foundation Server)会使用它来计算还没有列入计划的工作。如果在某一天“Remaining”字段的值增加了,而不是减少了,那么就像我上面讨论的那样,这会 被反映到“Iteration Backlog”的“Individual Capacity”工作表中。

我们最后要介绍的工作表是“Burndown”,在项目进度方面,它是一个起决定性作用的视图。团队的主管每天都会使用这个视图来追踪某个迭代的项目进度。这里我就不详细讨论了,因为有很多地方可以学到如何更好地理解一个燃烧图。但是,以后我可能会添加一些新想法的。

(本文来源:51cto 作者:周雪峰)

[转载]QQ限制WEB页面注册方法解析及其如何采用多线程技术实现QQ号码快速批量申请

mikel阅读(1109)

[转载]QQ限制WEB页面注册方法解析及其如何采用多线程技术实现QQ号码快速批量申请 – cntlis – 博客园.

腾讯在其QQ免费注册页面http://reg.qq.com/中,为了限制用户注册,设置了多种限制手段,尤其是在其JS页面中设置了多种算法,防止用户批量注册。

本文主要分析QQ是如何在WEB前台实现防止用户批量的注册,并且提供了相应的技术解决方案,程序早都做好了,没有外放,看到博客园上有其他人对外写了这样的文章,但是比较简陋,因此这里将我的设计方案跟各位分析一下

首先看我的最终实现效果图,比较简陋一些,多线程实现的,如果有什么疑问,可以跟我联系,本人联系QQ:8112857.

在开始注册前边的框框里边输入想要一次性批量申请QQ号码的数量,然后点击开始注册,系统自动的生成相应的线程,然后开始进行排队打码,在每个输入 框输入相应的注册码以后,点击回车,系统会自动的进行注册,并跳转到另外一个框框里边,并将正确的QQ号码自动保存到TXT文本里边。系统我在实现的时 候,没有考虑最后的临界区的问题,因此如果在没有输入验证码,并且关闭的时候,系统会假死,当然了,这个问题不影响使用,下面我说明下我的设计方案。

1.分析QQ注册提交

实际上QQ注册页面利用JavaScript操纵和很多COOKIES信息,而且利用COOKIES信息也进行了一系列的操作,而实际上我们完全可以给屏蔽掉,将关于操纵COOKIE的所有信息都给屏蔽掉,因此就是解析来的步骤

第一步,获取验证码,并且显示出来,这里我使用的是我们公司自己的控件,PNHTTP,你们也可以使用相应的组件,譬如说MSXML之类的只要能够实现GET或者POST方式的

TRegThread(aThread).Bmp:= Http.HttpBmp(‘http://captcha.qq.com/getimage?aid=1007901&0.9408595752591837’); //远程获取验证码,并保存到TBITMAP里边

第二步,用户输入验证码,及其其他的内容信息以后,还不能直接的提交,腾讯在这里对数据进行了一个加操作,首先像checkconn页面发出一个 GET申请,这个操作主要就是获取一串JSON代码,里边包含了需要提交的各变量的名称,也就是FORM里边的INPUT变量的名称,这个变量的名称腾讯 做的比较变态,还进行了一些算法,经过分析,我给还原过来如下

获取表单变量

1 FormParams:= Http.HttpGet(http://reg.qq.com/cgi-bin/checkconn?seed0.8865932116432269); ///获取checkconn页面内容
2 StrCookie:= Http.CookieMgr.CookieCollection.Cookie[PCCOOKIE,qq.com].Value; ///获取PCCOOKIE这个COOKIE里边保存的COOKIE信息
3 StrCookie:= copy(StrCookie,length(StrCookie)1,2); ///获取COOKIE的倒数两位
4 LBase:= HexToInt(StrCookie); ///将COOKIE倒数两位进行十六进制转换
5
6 ParamArray[0]:= QQ; ///申请类型1
7 ParamArray[1]:= EMAIL; ///申请类型2
8 ParamArray[2]:= zeze; ///QQ昵称
9 ParamArray[3]:= 0; ///QQ性别
10 ParamArray[4]:= 1985; ///出生年
11 ParamArray[5]:= 1; ///出生月
12 ParamArray[6]:= 2; ///出生日
13 ParamArray[7]:= 1; ///忘记了
14 ParamArray[8]:= 2; ///忘记了
15 ParamArray[9]:= abc111111; ///密码
16 ParamArray[10]:= abc111111; ///重复密码
17 ParamArray[11]:= 1; ///国家代码
18 ParamArray[12]:= 11; ///省份代码
19 ParamArray[13]:= 1; ///区域代码
20 ParamArray[14]:= RndStr; ///验证码
21
22 try
23 SListA:= FPNSplit(Copy(FormParams,33,402),,);
24 SListB:= FPNSplit(Copy(FormParams,447,64),,);
25 ///上边的是处理CHECKCONN页面的内容,实际上是JSON格式的,可以直接采用JSON解析,但是我这里嫌麻烦,所以自己用的分割函数直接处理
26 FormParams:= ; ///需要提交的变量名
27 ///下边的是对CHECKCONN返回内容的解密算法
28 for i := 0 to 12 do begin
29 IdxA:= StrToInt(SListB[i]) xor LBase;
30 IdxB:= 12i;
31 IdxA:= IdxA xor 6818;
32 IdxA:= IdxA xor 8315;
33 IdxA:= IdxA xor 5123;
34 IdxA:= IdxA xor 2252;
35 for j := 0 to 5 do
36 IdxA:= IdxA xor 0;
37 IdxA:= IdxA mod 15;
38
39 FormParams:= FormParams+ Copy(SListA[IdxB],2,28)+ =+ ParamArray[IdxA]+ &; ///这里是构造提交数据信息
40 end;
41
42 finally
43 SListA.Free;
44 SListB.Free;
45 end;
46
47
48

上边通过FormParams变量,将所需要提交的信息保存了下来,接下来我们开始像服务器提交

提交注册,并检测结果

1 StrResult:= Http.HttpPost(http://reg.qq.com/cgi-bin/getnum,FormParams,True);
2 Reg:= TPerlRegEx.Create(nil);
3 try
4 Reg.Subject:= StrResult;
5 Reg.RegEx:= 您获得的号码为:\<span id\=\”aq\-uin\” class\=\”number\”>([\s\S]*?)\<;
6 if Reg.MatchAgain then begin
7 StrQQ:= Reg.SubExpressions[1];
8 FPNWriteLnText(注册成功的QQ.txt,StrQQ,False);
9 end else begin
10 FPNWriteLnText(注册失败线程.txt,TRegData(aDataObj).FId,False);
11 end;
12 finally
13 Reg.Free;
14 end;
15

以上的过程就完成了腾讯的注册流程,但是仅仅这样是不够的,因为我们所需要的最终目的是多线程,多线程怎么实现呢?我这里采用DELPHI线程池的方式

代码

1 TCoding= record ///这里是记录打码区的状态
2 Status: integer; //忙碌1,空闲0,等待用户输入数据2,用户已经输入,等待处理3
3 ShowBegin: integer; //开始显示验证码的时间
4 end;
5
6 ///线程池中的线程处理类,可以派生,也可以不用派生
7 TRegThread = class(TPNPoolThread)
8 private
9 MyCodeIdx: integer;
10 bmp: TBitmap;
11 procedure ShowImg1;
12 procedure ShowImg;
13 public
14 destructor Destroy;
15 end;
16
17 TRegData= class(TPNTaskObject)
18 private
19 FId: String; //编号
20 public
21 constructor Create(const AId: string);
22 function Duplicate(DataObj: TPNTaskObject;
23 const Processing: Boolean): Boolean; ///判断两个任务是否重复,此函数必须在派生类写明
24 function Info: string; override; ///输出信息,覆盖
25 end;
26
相关打码区函数

1 var
2 MainForm: TMainForm;
3 Codings: array[1..9] of TCoding;
4 CodingCs: TPNCriticalSection; ///申请打码资源的CS
5
6 RegId: integer;
7
8 StrLog: string; ///日志数据
9 PoolReg: TPNThreadPool; ///线程池
10 csLog: TPNCriticalSection; ///保存日志的临界区
11
12 function CodingApply: integer; //申请打码显示资源,如果申请成功,返回显示的标号,否则返回1
13 function CodingRelease(CodeIdx: Integer): string; //释放显示资源,返回的是打码的信息
14 function CodingWait(CodeIdx: Integer): Boolean; //将状态更改为等待
15 function CodingOK(CodeIdx: Integer): Boolean; //将状态更改为处理完毕
16 function CodingStatus(CodeIdx: Integer): integer; //获取当前状态
17

具体的线程池设置代码,对于申请打码区资源,及其释放打码区资源,都写得有具体的方案

代码

1
2 function CodingApply: integer;
3 var
4 i: integer;
5 begin
6 CodingCs.Enter;
7 Result:= 1;
8 try
9 for i := 1 to 9 do begin
10 if Codings[i].Status=0 then begin
11 Result:= i;
12 Codings[i].Status:= 1;
13 Break;
14 end;
15 end;
16 finally
17 CodingCs.Leave;
18 Sleep(0);
19 end;
20 end;
21
22 function CodingRelease(CodeIdx: Integer): string;
23 begin
24 if (CodeIdx<0) or (CodeIdx>9) then Exit;
25 CodingCs.Enter;
26 try
27 try
28 Result:= TEdit(MainForm.FindComponent(Input+ IntToStr(CodeIdx))).Text;
29 except
30 Result:= ;
31 end;
32 Codings[CodeIdx].Status:= 0;
33 finally
34 CodingCs.Leave;
35 Sleep(0);
36 end;
37 end;
38
39 function CodingWait(CodeIdx: Integer): Boolean;
40 begin
41 if (CodeIdx<0) or (CodeIdx>9) then Exit;
42 Result:= True;
43 CodingCs.Enter;
44 try
45 try
46 Codings[CodeIdx].Status:= 2;
47 except
48 Result:= False;
49 end;
50 finally
51 CodingCs.Leave;
52 Sleep(0);
53 end;
54 end;
55
56 function CodingOK(CodeIdx: Integer): Boolean;
57 begin
58 if (CodeIdx<0) or (CodeIdx>9) then Exit;
59 Result:= True;
60 CodingCs.Enter;
61 try
62 try
63 Codings[CodeIdx].Status:= 3;
64 except
65 Result:= False;
66 end;
67 finally
68 CodingCs.Leave;
69 Sleep(0);
70 end;
71 end;
72 function CodingStatus(CodeIdx: Integer): integer;
73 begin
74 if (CodeIdx<0) or (CodeIdx>9) then Exit;
75 CodingCs.Enter;
76 try
77 Result:= Codings[CodeIdx].Status;
78 finally
79 CodingCs.Leave;
80 Sleep(0);
81 end;
82 end;
83
84 constructor TRegData.Create(const AId: string);
85 begin
86 FId:= AId;
87 end;
88
89 function TRegData.Duplicate(DataObj: TPNTaskObject;
90 const Processing: Boolean): Boolean;
91 begin
92 Result := (not Processing) and
93 (FId = TRegData(DataObj).FId);
94 end;
95
96 function TRegData.Info: string;
97 begin
98 Result:= FId=+ FId+ ;;
99 end;
100
101
102 procedure TRegThread.ShowImg1;
103 begin
104 try
105 TImage(MainForm.FindComponent(Img+ IntToStr(MyCodeIdx))).Picture.Assign(bmp);
106 TEdit(MainForm.FindComponent(Input+ IntToStr(MyCodeIdx))).Text:= ;
107 except
108 end;
109 CodingWait(MyCodeIdx);
110 end;
111 procedure TRegThread.ShowImg;
112 begin
113 Synchronize(ShowImg1);
114 end;
115 destructor TRegThread.Destroy;
116 begin
117 try
118 if bmp<>nil then bmp.Free;
119 except
120 end;
121 inherited Destroy;
122 end;
123
124 function HexToInt(const S: String): DWORD;
125 asm
126 PUSH EBX
127 PUSH ESI
128
129 MOV ESI, EAX //字符串地址
130 MOV EDX, [EAX4] //读取字符串长度
131
132 XOR EAX, EAX //初始化返回值
133 XOR ECX, ECX //临时变量
134
135 TEST ESI, ESI //判断是否为空指针
136 JZ @@2
137 TEST EDX, EDX //判断字符串是否为空
138 JLE @@2
139 MOV BL, $20
140 @@0:
141 MOV CL, [ESI]
142 INC ESI
143
144 OR CL, BL //如果有字母则被转换为小写字母
145 SUB CL, 0
146 JB @@2 // < 0 的字符
147 CMP CL, $09
148 JBE @@1 // 0..9 的字符
149 SUB CL, a010
150 CMP CL, $0A
151 JB @@2 // < a 的字符
152 CMP CL, $0F
153 JA @@2 // > f 的字符
154 @@1: // 0..9, A..F, a..f
155 SHL EAX, 4
156 OR EAX, ECX
157 DEC EDX
158 JNZ @@0
159 JMP @@3
160 @@2:
161 XOR EAX, EAX // 非法16进制字符串
162 @@3:
163 POP ESI
164 POP EBX
165 RET
166 end;
167
168 procedure TMainForm.DownProcessRequest(Sender: TPNThreadPool;
169 aDataObj: TPNTaskObject; aThread: TPNPoolThread);
170 var
171 Http: TPNHttp;
172 i,j,LBase,IdxA,IdxB: integer;
173 RndStr,FormParams,StrResult,StrQQ,StrCookie,StrIP: string;
174 SListA,SListB: TStringList;
175 Reg: TPerlRegEx;
176 ParamArray: array[0..14] of string;
177 begin
178 // FPNWriteLnText(日志.txt,TRegData(aDataObj).FId+开始注册,False);
179 Http:= TPNHttp.Create(nil,True,True);
180 Randomize;
181 StrIP:= 1.193.86.+ IntToStr(Random(255)+ 1);
182 // StrIP:= IntToStr(Random(255)+ 1)+.+ IntToStr(Random(255)+ 1)+.+ IntToStr(Random(255)+ 1)+.+ IntToStr(Random(255)+ 1);
183 Http.Request.CustomHeaders.Add(X-Forwarded-For:+ StrIP);
184 try
185 try
186 Http.HttpGet(http://reg.qq.com/);
187 // FPNWriteLnText(TRegData(aDataObj).FId+HEADER信息.txt,首页:+ Http.HttpHeader,False);
188 TRegThread(aThread).MyCodeIdx:= CodingApply;
189 ///等待获取打码资源
190 while TRegThread(aThread).MyCodeIdx=-1 do begin
191 sleep(500);
192 TRegThread(aThread).MyCodeIdx:= CodingApply;
193 end;
194 try
195 TRegThread(aThread).Bmp:= Http.HttpBmp(http://captcha.qq.com/getimage?aid=1007901&0.9408595752591837);
196 // FPNWriteLnText(TRegData(aDataObj).FId+HEADER信息.txt,验证码:+ Http.HttpHeader,False);
197 TRegThread(aThread).ShowImg;
198 finally
199 if TRegThread(aThread).Bmp<>nil then
200 TRegThread(aThread).Bmp.Free;
201 end;
202 while CodingStatus(TRegThread(aThread).MyCodeIdx)=2 do begin
203 Sleep(200);
204 end;
205 RndStr:= CodingRelease(TRegThread(aThread).MyCodeIdx);
206 // FPNWriteLnText(日志.txt,TRegData(aDataObj).FId+:RndStr=+ RndStr,False);
207 FormParams:= Http.HttpGet(http://reg.qq.com/cgi-bin/checkconn?seed0.8865932116432269);
208 // FPNWriteLnText(TRegData(aDataObj).FId+HEADER信息.txt,CheckConn:+ Http.HttpHeader,False);
209 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,返回的参数集:+ FormParams,False);
210 // FormParams:= Copy(FormParams,33,402);
211 StrCookie:= Http.CookieMgr.CookieCollection.Cookie[PCCOOKIE,qq.com].Value;
212 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,PCCOOKIE值为:+ StrCookie,False);
213 StrCookie:= copy(StrCookie,length(StrCookie)1,2);
214 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,LBASE值为:+ StrCookie,False);
215 LBase:= HexToInt(StrCookie);
216
217 ParamArray[0]:= QQ;
218 ParamArray[1]:= EMAIL;
219 ParamArray[2]:= zeze;
220 ParamArray[3]:= 0;
221 ParamArray[4]:= 1985;
222 ParamArray[5]:= 1;
223 ParamArray[6]:= 2;
224 ParamArray[7]:= 1;
225 ParamArray[8]:= 2;
226 ParamArray[9]:= abc111111;
227 ParamArray[10]:= abc111111;
228 ParamArray[11]:= 1;
229 ParamArray[12]:= 11;
230 ParamArray[13]:= 1;
231 ParamArray[14]:= RndStr;
232 try
233 SListA:= FPNSplit(Copy(FormParams,33,402),,);
234 SListB:= FPNSplit(Copy(FormParams,447,64),,);
235 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,Copy(FormParams,447,64),False);
236 FormParams:= ;
237 for i := 0 to 12 do begin
238 IdxA:= StrToInt(SListB[i]) xor LBase;
239 IdxB:= 12i;
240 IdxA:= IdxA xor 6818;
241 IdxA:= IdxA xor 8315;
242 IdxA:= IdxA xor 5123;
243 IdxA:= IdxA xor 2252;
244 for j := 0 to 5 do
245 IdxA:= IdxA xor 0;
246 IdxA:= IdxA mod 15;
247 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,IdxA:+ IntToStr(IdxA),False);
248
249 FormParams:= FormParams+ Copy(SListA[IdxB],2,28)+ =+ ParamArray[IdxA]+ &
250 end;
251
252 // FormParams:= Copy(SList[0],2,28)+ =1&+ Copy(SList[1],2,28)+ =1&+ Copy(SList[2],2,28)+ =pop67579818&
253 // + Copy(SList[3],2,28)+ =1983&+ Copy(SList[4],2,28)+ =+ RndStr+& + Copy(SList[5],2,28)+ =1&
254 // + Copy(SList[6],2,28)+ =lovezeze&+ Copy(SList[7],2,28)+ =pop67579818&+ Copy(SList[8],2,28)+ =0&
255 // + Copy(SList[9],2,28)+ =1&+ Copy(SList[10],2,28)+ =2&+ Copy(SList[11],2,28)+ =11&
256 // + Copy(SList[12],2,28)+ =1;
257 finally
258 SListA.Free;
259 SListB.Free;
260 end;
261 for i := 0 to Http.CookieMgr.CookieCollection.Count 1 do
262 StrCookie:= StrCookie+ Http.CookieMgr.CookieCollection.Items[i].CookieName+ :
263 + Http.CookieMgr.CookieCollection.Items[i].CookieText;
264 StrResult:= Http.HttpPost(http://reg.qq.com/cgi-bin/getnum,FormParams,True);
265 // FPNWriteLnText(TRegData(aDataObj).FId+HEADER信息.txt,POST时候:+ Http.HttpHeader,False);
266 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,提交COOKIE为:+ StrCookie,False);
267 // FPNWriteLnText(TRegData(aDataObj).FId+数据日志.txt,提交参数为:+ FormParams,False);
268 // FPNWriteLnText(TRegData(aDataObj).FId+返回数据.txt,StrResult,False);
269 Reg:= TPerlRegEx.Create(nil);
270 try
271 Reg.Subject:= StrResult;
272 Reg.RegEx:= 您获得的号码为:\<span id\=\”aq\-uin\” class\=\”number\”>([\s\S]*?)\<;
273 if Reg.MatchAgain then begin
274 StrQQ:= Reg.SubExpressions[1];
275 FPNWriteLnText(注册成功的QQ.txt,StrQQ,False);
276 end else begin
277 FPNWriteLnText(注册失败线程.txt,TRegData(aDataObj).FId,False);
278 end;
279 // FPNWriteLnText(TRegData(aDataObj).FId+匹配结果.txt,StrResult,False);
280 finally
281 Reg.Free;
282 end;
283 except
284
285 end;
286 finally
287 Http.Free;
288 end;
289 end;
290
291 procedure TMainForm.btn1Click(Sender: TObject);
292 var
293 i: integer;
294 begin
295 for i := 1 to StrToInt(RegNum.Text) do begin
296 RegId:= RegId+ 1;
297 PoolReg.AddRequest(TRegData.Create(IntToStr(RegId)));
298 end;
299 end;
300
301 procedure TMainForm.FormCreate(Sender: TObject);
302 begin
303 RegId:= 0;
304 CodingCs:= TPNCriticalSection.Create;
305 PoolReg := TPNThreadPool.Create(nil);
306 with PoolReg do begin
307 OnProcessRequest := DownProcessRequest; ///线程处理函数
308 AdjustInterval := 5 * 1000; ///减少线程间隔时间,5
309 MinAtLeast := False; ///是否设置最小线程数
310 ThreadDeadTimeout := 10 * 1000; ///线程死亡超时时间
311 ThreadsMinCount := 10; ///最小线程数
312 ThreadsMaxCount := 50; ///最大线程数
313 uTerminateWaitTime := 2 * 1000; ///挂起等待时间
314 end;
315
316 end;
317
318 procedure TMainForm.FormDestroy(Sender: TObject);
319 begin
320 PoolReg.Free;
321 CodingCs.Free;
322 end;
323
324 procedure TMainForm.Input1KeyPress(Sender: TObject; var Key: Char);
325 var
326 CodeIdx: integer;
327 ObjName: string;
328 begin
329 if Key= Char(13) then begin
330 ObjName:= (Sender as TRzEdit).Name;
331 CodeIdx:= StrToInt(Copy(ObjName,6,1));
332 CodingOK(CodeIdx);
333 if CodeIdx=9 then
334 CodeIdx:= 1
335 else
336 CodeIdx:= CodeIdx+ 1;
337 TRzEdit(FindComponent(Input+ IntToStr(CodeIdx))).SetFocus;
338 end;
339 end;
340

本文只是对QQ注册页面进行一些分析,供大家参考,其实大家在做WEB设计的时候,如何防止批量注册这块,也可以参考下QQ,比较变态一些,但是感觉腾讯做的还不完善。

[转载]如何使用C#实现QQ号码的申请

mikel阅读(1138)

[转载]如何使用C#实现QQ号码的申请 – wuhuacong(伍华聪)的专栏 – 博客园.

腾讯向大众提供了申请QQ的界面,方便很多潜在用户加入QQ大军中,注册页面是http://reg.qq.com ,为了限制用户的过度使用,设置了验证码、IP限制等手段,一般用户默认一天只能申请几个QQ号码,号码是随机生成的,当然好的号码是不会有的,这些是腾 讯的资源,需要付费才能获取到。

本文主要介绍如何利用C#来实现QQ号码的快速、批量申请操作,以及考虑如何加入宽带拨号的方式实现IP的限制,尽可能的申请到更多的QQ号码,以供他用。

先看看演示程序的界面效果:

=====》

上面是我使用C#实现QQ号码申请的演示程序,是应用的雏形。其中为了方便,把用户需要填写的信息封装起来,用户开通QQ号码后,再自行修改即可,只需要输入验证码即可快速申请到QQ。

1、首先需要获取验证码,然后供用户输入并提交申请,这里我使用了后台线程进行处理,提高用户界面的响应效果。

public partial class Form1 : Form

{
private CookieContainer cookieReg = new CookieContainer();
private BackgroundWorker worker = new BackgroundWorker();
private WebProxy proxy;

public Form1()
{
InitializeComponent();

worker.DoWork +=new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted
+= new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

//proxy = new WebProxy(“120.203.214.184”, 80);
}

private void GetVerifyImage()
{
worker.RunWorkerAsync(
GetImage);
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
string newverifyUrl = http://captcha.qq.com/getimage?aid=1007901& +
new Random().NextDouble() + new Random().Next(10) + new Random().Next(10);

cookieReg = new CookieContainer();
HttpHelper httpHelper
= new HttpHelper();
string regurl = http://reg.qq.com;
httpHelper.GetHtml(regurl, cookieReg, regurl, proxy);

using (Stream s = httpHelper.GetStream(newverifyUrl, cookieReg, regurl, proxy))
{
if (s == null)
{
MessageUtil.ShowWarning(
获取登陆码错误,请检查您的网络!);
return;
}
e.Result
= Image.FromStream(s);
}
}

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Image image
= e.Result as Image;
if (image != null)
{
this.pictureBox1.Image = image;
}
}

2、 处理申请操作结果显示。申请QQ的处理操作相对比较繁琐,我把它封装在一个处理函数中,这样我们在事件处理的时候,先宏观处理结果,然后在进一步细化具体的操作,如下所示:

private void btnApplyNumber_Click(object sender, EventArgs e)
{
if (this.txtVerifyCode.Text.Length < 4)
{
SetTips(
请输入验证码);
this.txtVerifyCode.Focus();
}

string qqnumber = ApplyQQ();
if (!string.IsNullOrEmpty(qqnumber))
{
SetTips(
string.Format(恭喜您,申请到QQ {0}, qqnumber));
LogTextHelper.WriteLine(
string.Format({0}—-{1}, qqnumber, 123abc));
}
else
{
if (getnumHtml.Contains(此IP申请的操作过于频繁))
{
SetTips(
IP操作过于频繁);
}
else
{
SetTips(
申请失败,请重试);
}
}
GetVerifyImage();
}

以 上先对申请的操作进行处理,并记录最后申请结果页面,如果页面有一些特殊的如IP操作频繁的信息,则提示用户IP操作的问题,方便采取如“手工拨号”等方 式进行处理,实现IP的变化,从而可以申请更多的QQ号码。如果一切正常,返回QQ号码,那么记录到相关的媒介并显示即可。

3、 详细QQ申请操作处理逻辑。申请QQ的处理,不是一般的麻烦,为了不给用户识别相应的提交内容,腾讯把申请的资料进行乱码化处理,并把顺序打乱,把相应的 名称处理成0f2b4766321c2d6a4c7a61515324这样不可识别的字符串,然后统一提交,一般人是识别不了这些字符串的,字符串不是加 密串,是随机按照规则组合而成,很变态。这里我提供大致的思路,就是先获取Cookie的相关信息,把里面的变量作为基础,对数据进行处理,然后作为相应 的字段名称,混合提交的内容,一起POST到服务器,如果成功,则返回QQ号码,否则提示相应的错误信息。

private string getnumHtml = “”;//最后申请的网页内容
private string ApplyQQ()
{
string html = “”;// “g_dataArray=new Array(new Array(\”0f2b4766321c2d6a4c7a61515324\”,\”4621587a340437624e71315a5a2b\”,\”23755a6a29192c7b506d73474536\”,\”4f21596e22043464537b774b493a\”,\”2d2d5b6a01163078567072577102\”,\”4121587a340437624e7130525223\”,\”4f2d486122002679117b65555728\”,\”047548612200267910736d5d5f30\”,\”306d4a6024193779584b55656718\”,\”2574486122002679136b75454738\”,\”33344a66250e703f1429370b0970\”,\”252348683412256c49766a585c2d\”,\”161059673e192645547262564206\”),new Array(9796,9806,9807,9803,9797,9795,9793,9798,9802,9801,9800,9799,9792),\”/cgi-bin/getnum\”);”;
string regurl = http://reg.qq.com/;
HttpHelper helper
= new HttpHelper();

int BaseNum = 0;
BaseNum
= GetBaseNumByCookieSkey(cookieReg);

string nick = test;
string pass = 123abc;
string repass= pass;
string year = 2009;
string month = 12;
string day = 13;
string sex = 1;
string verifycode = this.txtVerifyCode.Text;
string province = 11;
string city = 1;
List
<string> ElementsArrName = GetElementArray(nick, pass, repass, year, month, day, sex, verifycode, province, city);
List
<short> DataArrayShort = new List<short>();
List
<string> DataArrayLong = new List<string>();

Thread.Sleep(100);
string regurl2 = http://reg.qq.com/cgi-bin/checkconn?seed + new Random().NextDouble() + new Random().Next(10);

html = helper.GetHtml(regurl2, cookieReg, regurl, proxy);
// html = “g_dataArray=new Array(new Array(\”0f2b4766321c2d6a4c7a61515324\”,\”4621587a340437624e71315a5a2b\”,\”23755a6a29192c7b506d73474536\”,\”4f21596e22043464537b774b493a\”,\”2d2d5b6a01163078567072577102\”,\”4121587a340437624e7130525223\”,\”4f2d486122002679117b65555728\”,\”047548612200267910736d5d5f30\”,\”306d4a6024193779584b55656718\”,\”2574486122002679136b75454738\”,\”33344a66250e703f1429370b0970\”,\”252348683412256c49766a585c2d\”,\”161059673e192645547262564206\”),new Array(9796,9806,9807,9803,9797,9795,9793,9798,9802,9801,9800,9799,9792),\”/cgi-bin/getnum\”);”;
GetDataArray(html, ref DataArrayShort, ref DataArrayLong);

string postData = GetPostData(ElementsArrName, BaseNum, DataArrayShort, DataArrayLong);
//MessageBox.Show(postData);

Thread.Sleep(
100);
string regurl3 = http://reg.qq.com/cgi-bin/getnum;
html
= helper.GetHtml(regurl3, cookieReg, postData, true, regurl, proxy);
getnumHtml
= html;//记录最后分析的内容,以便进一步分析操作

Regex re
= new Regex(var\\s*xyz=\(.*?)\;, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace);
Match mc
= re.Match(html);
if (!mc.Success)
{
return “”;
}
string qqnumber = mc.Groups[1].Value;
return qqnumber;
}

上 面注释的部分,就是获取到的相关信息,乱码一片,不过既然是本地处理,也是有规律可循的,本文只是介绍相关的规则,详细实现不在一一呈现,主要的处理逻辑 就是获取本地Cookie的某项内容,并把内容进行截断,作为一个BaseNum,然后把获取到的数组进行乱码化,作为提交字段的名称,然后一一放置相关 的内容,提交到服务处理。

上面只是一个简单的Demo,一般情况下,申请3~10左右的号码可能就会因为IP的限制,而不能继续,需要暂停几个小时才能继续可以申请一个左右,第二天才可以继续申请更多的号码。

为了更好的申请更多的QQ号码,一般需要加入拨号这种方式来处理IP的限制,如我的QQ搜通天中应用来批量申请QQ号码的界面如下所示:

如果需要了解详细,可以下载该软件来玩玩,熟悉下QQ申请的操作模式。

[转载]如何循序渐进学习Python语言

mikel阅读(910)

[转载]布同:如何循序渐进学习Python语言 – 布同世界 – 博客园.

大家都知道Python语言是一种新兴的编程语言。1989年,Python就由Guido van Rossum发明。Python一直发展态势很好。

原因有几点:1、跨平台性好。Linux、Windows、MacOS、塞班……。2、快速易学。3、模块齐全。

Python本身是一种面向对象的脚本语言。功能涉及到应用程序开发、网络编程、网站设计、图形界面编程等等,基本囊括众多应用。手机应用开发,电脑PC 程序开发,连Google也开始使用python作为其开发语言。很多编程比赛也开始接受python语言。

既然这么有用,那怎么学习呢?废话少说,马上解答。

《征服Python——语言基础与典型应用》

这本书中的ppt部分简单介绍了Python的相关知识。其源代码部分涉及到常见的数据结构和算法、语法的应用实例,完全可以用来进行模仿学习。这本书的源代码可以帮助你了解部分Python语言的应用和掌握几个简单的应用。

Python视频讲座-台湾辅仁大学教学视频

这个教学视频可以帮助你从一定程度上了解Python的一些属性,但是不能够帮助你入门。如果你简单看过Python的入门级别的语法教程,再 看看这个是有好处的,毕竟这个视频不长,几个小时就看完了。当然,里面的老师的普通话带一点台湾腔,程序说成是程式,字符串说成是字串或者序列 sequence……还有很多与大陆称呼的不同之处,不过这个不影响你的理解。

[Python教程].Game.Development.Tutorial

这个教程共19集,每集约10分钟,一共大概190分钟,三个小时。其中是一个老外Bucky Roberts的关于利用pygame模块进行小游戏编写的讲解,讲的非常细致,即使没有Python基础也可以看,你可以从中了解到Python编写 10-30行代码时的一些情形。他并不致力于讲解Python语法,而是讲解如何使用模块中的方法来快速编写小游戏。Bucky的英文不知道为什么,听起 来特别的顺,不像那些高深的系统专家的视频讲解那样枯燥。

[Python教程].Python.Programming.Tutorial

与之前那个视频比起来,这个视频就是Bucky Roberts讲解Python基本语法的视频。讲解非常细致。我相信大学英语四级水平完全可以看懂(PS:我知道四级什么水平,自信一点,相信我)。其 中完全是用示例来进行解释。很好懂。每集10分钟,一共43集,看完我估计你会跃跃欲试想要用Python编写自己的程序了。这个真的很不错的。用来在学 习python基础的时候间插着学习很好。

c经典100例Python语言实现

这本书是帮助那些有c或者c++数据结构基础的人,如何平滑过渡到Python语言上来。其中讲解了很多如何用Python解决实际的小型数学 问题。很不错的。看完你就知道Python用来替代c或者c++还是挺可能的。本书适合那种学完Python基础之后不知道如何下手写程序的人。

Python参考手册2004年3月22日版

共97页。这个版本是我看见的最新的版本了。如果你有更新的版本,请留言告诉我,谢谢了。其中用中英文对照的方式讲解了很多Python的特性 和语法知识。这本书不长,但是并不是一个全面的讲解Python知识的书籍。其中很多地道的实例可以教会你很多东西,这就是它的一个很好的价值所在。示例 好是很难得的。

Python Tutorial Python 入门指南Release 2.5b2

共137页。这本书是上面那本书的“升级版”,相似,而且更全面。很不错的示例,强力推荐。

《Python核心编程》第二版

好吧。我承认,这本书才是介绍语法知识最全面,涵盖面最广的书籍。用来为你构建一个完整的Python知识构架实在是太合适了。但是我推荐不要 一开始就看着本书。先了解Python的一些大概之后再来从这本书中吸取全面的营养会更加合适。如何了解大概呢?下面会给你介绍的,先别急。

Python技术参考大全

这本书,书如其名。非常厚实,非常全面。从语法知识到应用编程都包含了。完全是同上面这本书一个样子的。可以把这两本书结合起来看看。非常好。

Python学习笔记——皮大庆

这本书对于Python相当于《易学c++》对于C++。浅显而易懂,完全适合没有太多语言基础,甚至刚刚开始学习编程的人。这本书就是你学习Python的对好的第一本书。其中谈到了很多作者的个人理解,挺地道的,对你学习Python有很大帮助。

Python与Tkinter编程

这本书适合那种,学好了Python语法知识,需要进行应用程序设计的人。或者有MFC基础,了解了部分Python语法,急于用于编程的 人。(笔者是第二种)这本书讲到了如何利用Tkinter模块进行界面开发和应用开发。因为Python2.5已经包含了Tkinter模块,所以你不需 要安装和额外的导入就可以用Tkinter来编程了。你安装好Python就默认安装了Tkinter模块。

Python语言入门Learning Language

其实这本书用来学习Python语法和基础知识也是不错的,还可以简单了解一点应用编程的知识。但是由于国内好像还没有电子版的,只有影印版的,不利于你拷贝示例程序进行练习……如果你愿意自己一个个字母的敲打示例程序的话还是很不错的。

Python源码剖析

个人感觉这个书从整体和一些关键点去研究Python,比较高度。如果是有Python基础来看这本书比较好。其中有很多示例程序值得学习。也是一本好书,如果学好基础了,一定要过来看看这本书。

Tkinter编程代码示例

如果你有一些Python语法基础了,想要马上学习应用编程,又觉得学习Tkinter入手比较好,那么这本书很不错。其中的很多示例浅显易 懂,帮助你快速的搞出个widget在你的程序里易如反掌。要是学习Tkinter的widget,先读读这个书是很有好处的。帮你入门吧。由于这本书的 很多代码直接可以拷贝使用,所以这本书的作者提到还是尽量自己敲打示例程序比较好。我选择性的接受这个意见。

派森标准库Python standard library

如果你学完语法知识,感觉很好。但是开始变成后却发现Python的模块实在是太诡异了,到底有什么模块,都是干什么用的,如果你提出这个问题,这本书很好的解决你的疑问。其中讲解了大量的模块,你肯定很受用的。

深入Python (Dive Into Python)

这本书也是学习Python的最好的第一本书之一。浅显易懂,有选择性的为你展示Python的特点,这本书很好,而且厚度还可以接受。太厚的 书笔者不推荐用来作为学习的第一本。你可以试试看。这个东西还分为多个版本,不一定新版本就更加全面而易懂。笔者手中就有2001年版和2004年版。自 己看看就知道差别了。

Python document

这个是Python官方介绍Python的一个英文文档,系统的涉及到诸多Python的东西。但不像MSDN,它并不是一个全面而细致的文档 库,毕竟只有几兆大小。他还是会推荐你要看进一步详细的东西应该去看看“Python作者的书”。这句话不知道在其中提到多少次。这个document不 能当MSDN一样用,虽然都是英文的。还是可以查到部分信息。

Python bible

笔者感觉这个书就是把Python document整理之后形成的一本书。你想想圣经,代表神的旨意,这个书估计代表Python的作者的意图。挺强大的书,如果你有什么不懂的东西,也许你可以从中搜索到相关的解释和示例程序。所以一并推荐。

介绍了这么多书,我到底适合先读那本呢??下面介绍一下:

深入Python。阅读这个东西很轻松愉快。读到正则表达式的时候比较纠结。呵呵,你不一定要从一本书中学会Python基础。

Python核心编程。再学习这本书,你就轻松加愉快了。这本书的Tkinter部分之前你可以插读一下其他Tkinter入门更好的书。

下面介绍如何Tkinter入门:

Tkinter编程代码示例。这个读起来轻松愉快。我可不想把你搞的特别郁闷。这个作为开始点再合适不过了。其中作者还有很多东西没有在书中解决,呵呵,你多学两天也许就能够搞定这些书中遗留下来的问题,你会很有成就感的。

Python与Tkinter编程。这本书就稍微系统和周全的为你讲解Tkinter的知识了。

Python核心编程。又回到这本书了。

当然,你可以参考上面的简介自行选择一套学习路线的方案。请多参考上面的简介部分。

如何开始编写自己的第一个Python程序?

这个问题很重要。

1、安装IDLE。这个东西的名字叫做pywin32-214.win32-py3.0.exe。

字面解释。其中py:Python;

win32:Windows下的安装程序。

3.0:版本号。笔者使用的是2.5.4其实入门都可以用。

下载连接:http://www.python.org/download/。打开这个页面之后选择一款Windows下的安装程序。即可。

2、安装完成之后进入:开始/程序/Python*.*/DIE(Python GUI )。打开界面,输入print ‘Hello World’,回车,查看是否能够正常工作即可。

如何显示Hello World,那么就是正确可用的。这个是命令行方法,还有源代码编辑方法。要是需要进一步信息请发送邮件到wuqiangxx@126.com,我一定给你详细的解释,如何开始进行编程。

笔者的一点建议:

1、一棵树上吊死我不推荐你,你应该多试几棵。

2、每本书都有自己讲解的好的地方和不好的地方,你应该花几分钟体验一下,然后从最好入门的地方入手。

3、学习东西的过程是有一个加速度的。刚开始的时候是线形的速度,之后便是一个快速的上升期,然后又回到线形的速度。如果选择好书,一开始也可以处于有加速度的状态,那就是越学越开心,越来越感兴趣,好书常常能够让你做到这样子。

4、尽信书不如无书。有的示例程序看上去显而易见,但是你不拿过去运行一下,自己调试一下,你始终掌握不到其精髓和灵活运用的方法。自己动手改改,你会心悦诚服很多。

5、要善于总结。如果你光学不练,这是不好的,如果你不善于总结,这也是不好的。语言都是用不上的时候开始学习。都是用的上的时候开始复习。要 是用得上的时候开始学习,除非你抗压能力一流,不然我想你心情烦躁,效果会很不好的。学习的时候多总结一下,复习的时候可以翻出来看看,这样就不至于完全 荒废了,并且恢复相当快速。

6、学习编程不要太排斥英文。如果让你直接从英文开始学习,我想这个很难,但是如果用英文版本开始复习,这个就很好了。

笔者个人经验,不知道在你身上适不适用,如果你有收获,我就开心了。

[转载]使用MapReduce进行排序

mikel阅读(1135)

[转载]使用MapReduce进行排序 – Just For Fun – 博客园.

之前在工作中使用到过MapReduce的排序,当时对于这个平台的理解还比较浅显,选择的是一个最为简单的方式,就是只用一个Recude来做。因为 Map之后到Reduce阶段,为了Merge的方便,MapReduce的实现会自己依据key值进行排序,这样得出的结果就是一个整体排序的结果。而 如果使用超过一个Reduce任务的话,所得的结果是每个part内部有序,但是整体是需要进行merge才可以得到最终的全体有序的。今天读了 《Hadoop权威指南》中的第8章,对使用Hadoop这一MapReduce的Java实现进行排序有所了解,在此进行简单的总结。

首先我们来看一下Hadoop中内部Map和Reduce两个阶段所做的排序,可以使用下图来说明。

对MapReduce或者Hadoop有所了解的人可能都知道,所谓对于key值的排序,其实是在Map阶段进行的,而Rduce阶段所做的工作是对各个 Map任务的结果进行Merge工作,这样就能保证整体是有序的。如果想在使用多个Reduce任务的情况下保证结果有序,我们可以做的是在上图中的 partition阶段进行控制,使分配到每个reduce task的数据块为数值区域独立的,即比如整体数据在0~50之间,划分为5个Reduce任务的话,可以0~10区间的数据到第一个Reduce Task,10~20之间的到第二个,以此类推。但是这样就存在一个问题,划分出的各个任务中的数据可能并不是均等的,这样某些Reduce Task处理了很多数据,而其他的处理了很少的数据。Hadoop提供了RandomSampler类(位于InputSampler类中)来进行随机取 样,然后按照取样结果对值域进行划分。一个示例代码如下:

public class SortByTemperatureUsingTotalOrderPartitioner extends Configured implements Tool { @Override public int run(String[] args) throws Exception { JobConf conf = JobBuilder.parseInputAndOutput(this, getConf(), args); if (conf == null) { return -1; } conf.setInputFormat(SequenceFileInputFormat.class); conf.setOutputKeyClass(IntWritable.class); conf.setOutputFormat(SequenceFileOutputFormat.class); SequenceFileOutputFormat.setCompressOutput(conf, true); SequenceFileOutputFormat .setOutputCompressorClass(conf, GzipCodec.class); SequenceFileOutputFormat.setOutputCompressionType(conf, CompressionType.BLOCK); conf.setPartitionerClass(TotalOrderPartitioner.class); InputSampler.Sampler<IntWritable, Text> sampler = new InputSampler.RandomSampler<IntWritable, Text>( 0.1, 10000, 10); Path input = FileInputFormat.getInputPaths(conf)[0]; input = input.makeQualified(input.getFileSystem(conf)); Path partitionFile = new Path(input, "_partitions"); TotalOrderPartitioner.setPartitionFile(conf, partitionFile); InputSampler.writePartitionFile(conf, sampler); // Add to DistributedCache URI partitionUri = new URI(partitionFile.toString() + "#_partitions"); DistributedCache.addCacheFile(partitionUri, conf); DistributedCache.createSymlink(conf); JobClient.runJob(conf); return 0; } public static void main(String[] args) throws Exception { int exitCode = ToolRunner.run( new SortByTemperatureUsingTotalOrderPartitioner(), args); System.exit(exitCode); } }

使用上述程序执行所得的结果会是多个划分,每个划分内部是有序的,而且第i个划分的key值会比i+1个划分的key值都要小。这样,就可以不需要进行再一步的merge,就可以得到整体的上有序结果。

关 于排序,一个更加有意思的应用是所谓的Secondary Sort,亦即在保证第一个key值有序的情况下,对第二个key值也要保证有序(可以是升序或者降序)。此处的一个实现方法是将这两个需要排序的部分都 作为key值,使用IntPair进行存储,然后自己实现一个继承自WritableComparator的名为KeyComparator的用于比较的 类,其代码如下:

public static class KeyComparator extends WritableComparator { protected KeyComparator() { super(IntPair.class, true); } @Override public int compare(WritableComparable w1, WritableComparable w2) { IntPair ip1 = (IntPair) w1; IntPair ip2 = (IntPair) w2; int cmp = IntPair.compare(ip1.getFirst(), ip2.getFirst()); if (cmp != 0) { return cmp; } return -IntPair.compare(ip1.getSecond(), ip2.getSecond()); // reverse } }

这里对于第二列是得到降序的结果,在conf设置的时候,可以使用 conf.setOutputKeyComparatorClass(KeyComparator.class);语句进行设置。这样执行计算程序的话, 会存在一个问题,因为将两个int型的值共同作为key值来处理,在Map阶段结束后进行Partition的划分的时候,就会同样依照这个总key值进 行划分,我们想要两个值,比如(1900,20)和(1900,23)被放到同一个Reduce任务中进行处理就无法实现,于是我们需要实现自己的 Partitioner接口,代码如下:

public static class FirstPartitioner implements Partitioner<IntPair, NullWritable> { @Override public void configure(JobConf job) { } @Override public int getPartition(IntPair key, NullWritable value, int numPartitions) { return Math.abs(key.getFirst() * 127) % numPartitions; } }

同样在配置过程中使用conf.setPartitionerClass(FirstPartitioner.class);语句进行设置。除此之外,需 要进行控制的还有一个Reduce中的Group by操作,方法是实现一个GroupComparator类,其中的比较只使用第一个键值即可,代码如下:

public static class GroupComparator extends WritableComparator { protected GroupComparator() { super(IntPair.class, true); } @Override public int compare(WritableComparable w1, WritableComparable w2) { IntPair ip1 = (IntPair) w1; IntPair ip2 = (IntPair) w2; return IntPair.compare(ip1.getFirst(), ip2.getFirst()); } }

需要设置的是conf.setOutputValueGroupingComparator(GroupComparator.class);。这样就可以实现Secondary Sort过程了。

转载Asp.net防止盗链

mikel阅读(1365)

转载Asp.net防止盗链 – 钱李峰的博客 – 博客园.

今天在书上偶然看到了如何实现防盗链的问题,到网上查看了一下盗链主要是其他的一些网站引用本站图片或者下载链接。如果被引用的站比较小这样外站盗链带来 的“无作用”流量就给站长带来了压力了(流量也是要钱滴)。对于防盗链可以从两个方面来防,一个是服务器,一个是程序里面判断。各个服务器的判断不一 样,iis需要安装特定的防盗链软件。我们今天主要讨论的是程序里实现防盗链。

原理解释

网上现在比较流行的是使用handler来实现防盗链。具体的意思如下:专门新建一个针对某种文件请求的处理类(继承于IHttpHandler)并在 web.config里面配置好所有的该文件请求都指向该类。然后在类里面判断该请求的前一次请求是不是存在并且指向我们站的域名,如果存在则认为不是盗 链,返回真实的文件。否则返回error图片。

让我们设想一下如果现在另外一个网站引用了我们站的图片,并应用到了一篇文章当中。现在有个人请求那个网站的文章,用户的request是向他们的服务器 发出的,他们站的服务器返回html让浏览器解析。浏览器解析到我们的图片地址时他会向我们站发起这个图片的请求。因为我们程序的设置这个请求会被转发到 我们特定的类做处理,程序判断这个request前面的请求是不是为空(显然它只请求了我们的图片其他没有请求,所以当然没有前面的请求),为空则返回 error图片。理解了上面的过程就容易知道为什么请求我们站的那个网页时里面的图片则正确显示了,用户显示request了那个页面,所以里面当然有前 面访问的记录。当浏览器解析我们站的图片时候就正确返回了。不知道大家懂了没呢?

代码实现

首先创建一个类继承于IHttpHandler,我这里就叫做ForbiddenInvaliteDownload类了:

代码
        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            if (null != context.Request.UrlReferrer)
            {
                context.Response.Expires = 0;
                context.Response.Clear();
                context.Response.ContentType = "image/jpg";
                context.Response.WriteFile(context.Request.PhysicalPath);  
                context.Response.End();
            }
            else
            {
                context.Response.Expires = 0;
                context.Response.Clear();
                context.Response.ContentType = "text/html";
                context.Response.Write("盗链");
                context.Response.End();
            }
        }

代码很少,大家一看就明白了。就是用context.Request.UrlReferrer这个判断一下前面一个请求是否存在,存在则认为是合法的,否则不合法。

仅仅是这个类还不能将所有的jpg请求转发过来,我们需要配置一下webconfig,在System.Web下配置:

代码
    <httpHandlers>
      <add verb="*" path="*.jpg" type="Namespace.ForbiddenInvaliteDownload,Namespace"/>
    </httpHandlers>

这里还要提到的是IIS默认是不会为JPG文件发送请求的,而是直接获取。所以我们还要在iis里面配置一下让所有的jpg请求都转发到我们的那个处理程序,而不是iis默认的直接获得。配置如图:

盗链

至此,你的网站已经可以防止jpg文件被盗了。当然,其他的zip等等文件同样也可以实现,您可以处理一个通用类,根据请求的后缀判断是什么类型从而进行操作。下面是效果图:

1

2

结束了吗

上面的方式并不能防止迅雷等下载软件的下载,在迅雷里面输入这些地址照样可以下载。如果别的站引用了你的某个.zip的文件链接,这样还是可以直接下载到的。那该如何解决呢?我目前想到的就是加入session验证。

如果用户访问了你的下载页面则在load里面设置session[“visited”]=”true”,然后在下载里面增加一个session验证,代码如下(注意,需要继承

System.Web.SessionState.IRequiresSessionState才能使用session):
代码
            if (null != context.Request.UrlReferrer && context.Session["visited"] == "true")
            {
                context.Response.Expires = 0;
                context.Response.Clear();
                context.Response.ContentType = "image/jpg";
                context.Response.WriteFile(context.Request.PhysicalPath);
                context.Response.End();
            }
            else
            {
                context.Response.Expires = 0;
                context.Response.Clear();
                context.Response.ContentType = "image/jpg";
                context.Response.WriteFile(context.Request.PhysicalApplicationPath + "Images/2.jpg");
                context.Response.End();
            }

这样我们调试的时候调用迅雷下载这个图片就可以发现session里面是没有值的,当然就下载error图片了。

[转载]中国标准书号校验码的计算方式(附C#代码)

mikel阅读(1131)

[转载]中国标准书号校验码的计算方式(附C#代码) – 枢木 – 博客园.

近段时间做了一个ERP项目,涉及到图书销售,于是熬了几个晚上研究了一下相关事情……抽空将一些内容梳理了一下。

首先了解一些必要的术语及定义:

—————————————
EAN•UCC前缀  EAN•UCC prefix
国际物品编码协会分配的产品标识编码。
—————————————
校验码  check digit
中国标准书号的最后一位,由校验码前面的12位数字通过特定的数学算法计算得出,用以检查中国标准书号编号的正确性。
—————————————
ISBN
国际标准书号英文International Standard Book Number的缩写,国际上通用的出版物标识编码的标识符。
—————————————
出版者  publisher
向中国ISBN 管理机构申请并获得出版者号的出版机构或组织。
—————————————
组区  registration group
由国际ISBN管理机构指定的,以国家、地理区域、语言及其他社会集团划分的工作区域。

=======================================

中国标准书号的结构:

—————————————
中国标准书号的构成
中国标准书号由标识符“ISBN”和13位数字组成。其中13位数字分为以下五部分:
1)EAN•UCC前缀
2)组区号
3)出版者号
4)出版序号
5)校验码
书写或印刷中国标准书号时,标识符“ISBN”使用大写英文字母,其后留半个汉字空,数字的各部分应以半字线隔开。如下所示:
ISBN EAN•UCC前缀-组区号-出版者号-出版序号-校验码
示例:ISBN 978-7-5064-2595-7
—————————————
EAN•UCC前缀
中国标准书号数字的第一部分。由国际物品编码(EAN•UCC)系统专门提供给国际ISBN管理系统的产品标识编码。
—————————————
组区号
中国标准书号数字的第二部分。它由国际ISBN管理机构分配。中国的组区号为“7”。
—————————————
出版者号
中国标准书号数字的第三部分。标识具体的出版者。其长度为2至7位,由中国ISBN管理机构设置和分配。
—————————————
出版序号
中国标准书号数字的第四部分。由出版者按出版物的出版次序管理和编制。
—————————————
校验码
中国标准书号数字的第五部分,也是其最后一位。采用模数10加权算法计算得出。
—————————————
出版者号的取值范围和出版量

出版者号设置范围
00 09
100 499
5000 7999
80000 89999
900000 989999
9900000 9999999

每一出版者号含有的出版量(与上面一一对应,如:00 09对应1000000)
1000000
100000
10000
1000
100
10

—————————————

10位数字中国标准书号校验码的计算:

代码

/// <summary> /// 10位数字中国标准书号校验码的计算。 /// <remarks> /// 10位数字中国标准书号校验码采用模数11的加权算法计算得出。 /// /// 数学公式为: /// 校验码 = mod 11 {11-[mod 11 (加权乘积之和)]} /// = mod 11 {11-[mod 11 (248)]} /// = 5 /// /// 以ISBN 7-5064-2595-5为例。 /// </remarks> /// </summary> /// <param name="barCode"></param> /// <returns></returns> public static string GetF10ISBN(string sCode) { string coreCode = sCode.Replace("-", ""); coreCode = coreCode.Substring(0, 9); int sum = 0; for (int i = 10; i > 1; i--) { // 从高位至低位,分别乘以(10-i) sum += i * Convert.ToInt32(coreCode.Substring((10 - i), 1)); } string checkCode = ""; if (sum % 11 == 0) { checkCode = "0"; } else if (sum % 11 == 1) { checkCode = "X"; } else { checkCode = (11 - (sum % 11)).ToString(); } return string.Concat(coreCode, checkCode); }

13位数字中国标准书号校验码的计算:

代码

/// <summary> /// 13位数字中国标准书号的校验码的计算。 /// <remarks> /// 13位数字中国标准书号的校验码采用模数10的加权算法计算得出。 /// /// 数学算式为: /// 校验码 = mod 10 {10 – [mod 10 (中国标准书号前12位数字的加权乘积之和)]} /// = mod 10 {10 – [mod 10 (123)]} /// = 7 /// /// 以ISBN 978-7-5064-2595-7为例。 /// </remarks> /// </summary> /// <param name="sCode"></param> /// <returns></returns> public static string GetF13ISBN(string sCode) { string coreCode = sCode.Replace("-", ""); coreCode = coreCode.Substring(0, 12); int oddSum = 0; //奇数和 int evenSum = 0;//偶数和 for (int i = 0; i < 12; i++) { if (i % 2 == 0) { evenSum += Convert.ToInt32(coreCode.Substring(i, 1)); } else { oddSum += Convert.ToInt32(coreCode.Substring(i, 1)); } } int sum = oddSum + evenSum * 3; string checkCode = null; if (sum % 10 == 0) { checkCode = "0"; } else { checkCode = (10 - (sum % 10)).ToString(); } return string.Concat(coreCode, checkCode); }

10位数字的中国标准书号转换为13位数字的中国标准书号:

代码

[转载]在Eclipse中的Android项目里实现代码复用

mikel阅读(913)

[转载]在Eclipse中的Android项目里实现代码复用 – SkyD – 斯克迪亚(徐明璐)个人博客 – 博客园.

引言

开发时通常都会有多个项目间代码复用的需求,这时通常的做法是设置项目依赖,让一个项目引用另一个项目,比如在Visual Studio中这样就很容易实现。

而在Eclipse中的Android项目里,如果要引用另一个项目,理论上应该也使用类似的办法:

SNAGHTML10cdf2c

这里是引用一个名为Core的项目。

这样做下来,在编写代码时不会有问题,但是在模拟器中运行时就会出现NoClassDefFoundError异常,原因是找不到引用项目中的类。

而通过此窗口中Libraries选项卡中的各种功能添加引用,通常得到的结果要么是编译不通过,要么也是和上面一样。

我在网上查了半天,看到很多人也都遇到此问题无解~

创建jar文件

看来Eclipse无法自动实现项目的整合,那么只能考虑先将公共项目输出为jar,再由需要的项目进行导入了。

由 于我这里的公共项目并不是一个Android常规项目,我只希望它充当一个公共的类库,提供一些通用功能,因此我将此项目创建为一个普通的Java项目, 然后再Libraries中去掉了Java-SE的引用,加入了Android.jar(这个请根据需要,从SDK中导入特定版本的 Android.jar文件),然后执行导出:

image

选择JAR文件类型:

image

下一步,这里设置保存路径:

image

下一步,再下一步,这里可以采用默认设置生成manifest文件,也可以自行设置指定的文件:

image

完成之后就导出完毕了。

导入jar文件

现在需要在要用到的项目中导入先前导出的jar文件,只需在Build Path中通过Add External JARs…功能选取先前导出的jar文件即可:

SNAGHTML11c454e

参考资料

http://blog.csdn.net/guanmjie/archive/2009/08/12/4437779.aspx

http://hi.baidu.com/gaogaf/blog/item/1757d309274b098cd1581bd7.html

[转载]用dynamic增强C#泛型表达力

mikel阅读(979)

[转载]用dynamic增强C#泛型表达力 – { Code Talk } – 博客园.

C++的泛型是基于模板(template)技术的。模板本身并不作为独立的编译单元,而是在编译时绑定实际参数类型进行模板实例化,类似与C语言 的宏展开,在运行时并不存在独立的模板类型。模板对泛型参数的约束是基于操作的语法特征的,属于一种静态的duck typing机制,十分灵活。

下面的代码定义了一个泛型Add函数,它对泛型参数T的要求只是支持+运算,并不要求T是某个类的子类,或是实现了某个接口。int, double, std::string等支持+运算符的类型都可以成功匹配T。

//C++

template<typename T>
T Add(const T& t1, const T& t2) {
return t1 + t2;
}

int main() {
int i = Add(1, 2);
double d = Add(1.1, 2.2);
std::string s = Add(std::string(“abc”), std::string(“def”));

std::cout << i << ” ” << d << ” ” << s << std::endl;

return 0;
}

输出:

>>3 3.3 abcdef

而类似的代码在C#中却无法编译通过:

csadd

这 是由于C#采用基于reification的泛型机制,泛型类会单独编译,并且在运行时存在;因此,C#对于泛型参数的要求更加严格,只能通过where 关键字表达基于继承关系的约束,无法通过duck typing的方式表达类型约束。与模板相比,这种机制的好处在于可以更好的支持反射和元编程,但其缺点是泛型的表达能力不如模板。幸好C#4.0中引入 了动态类型机制,我们可以通过动态类型来实现基于duck typing的泛型参数约束。

//C#

static class Calculator {
public static T Add<T>(T t1, T t2) {
dynamic d1 = t1;
dynamic d2 = t2;

return (T)(d1 + d2);
}
}

public static void Main(string[] args){
int i = Calculator.Add(1, 2);
double d = Calculator.Add(1.1, 2.2);
string s = Calculator.Add(“abc”, “def”);

Console.WriteLine(i + ” ” + d + ” ” + s);

}

输出:

>>3 3.3 abcdef

除了运算符重载,对于普通的方法调用也是适用的。这种方法是一种动态duck typing的泛型参数约束机制,依赖于运行时的方法查找,与模板编译时的检查不同,它需要使用者保证传入的对象符合相应要求。

参考:

wikipedia: Generic Programming

Dynamic Objects in C# 4.0

where (generic type constraint) (C# Reference)

[转载]通过源代码研究ASP.NET MVC中的Controller和View(五)

mikel阅读(1027)

[转载]通过源代码研究ASP.NET MVC中的Controller和View(五) – Ivony… – 博客园.

通过源代码研究ASP.NET MVC中的Controller和View(一)

通过源代码研究ASP.NET MVC中的Controller和View(二)

通过源代码研究ASP.NET MVC中的Controller和View(三)

通过源代码研究ASP.NET MVC中的Controller和View(四)

第五篇,从这一篇开始,将研究ASP.NET的Controller,IController接口是这个样子的:

  public interface IController
  {
    void Execute( RequestContext requestContext );
  }

IController是控制器的抽象,由资料可 知,当ASP.NET MVC捕获到HTTP请求时,便会通过一系列的机制确定处理当前请求的Controller,创建IController的实例来处理这个请求 (RequestContext)。在IController之前的东西,其实是个Routing,或者说请求分发。具体的分发机制与ASP.NET Routing相关,不在我这一次的研究范畴。我们现在假设已经通过分发处理来到了IController,来看看IController的实例是如何处 理请求的。

首先通过Reflector看这个接口的实现情况:

image

很干净的继承链,没有什么旁系和分支,IAsyncController和AsyncController这两个类型从名称来看已经知道大体上应该 是用异步处理实现的Controller或IController(就像是IHttpAsyncHandler),不妨看看 IAsyncController接口长啥样:

  public interface IAsyncController : IController
  {
    IAsyncResult BeginExecute( RequestContext requestContext, AsyncCallback callback, object state );
    void EndExecute( IAsyncResult asyncResult );
  }

显然事实就是这样,那么我们只需要关心同步处理的实现(Controller)便可以了,异步处理的逻辑不可能有很大的偏差。

按照一贯的传统,IController接口应该会被抽象基类ControllerBase实现,来看看:


    #region IController Members
    void IController.Execute( RequestContext requestContext )
    {
      Execute( requestContext );
    }
    #endregion

    protected virtual void Execute( RequestContext requestContext )
    {
      if ( requestContext == null )
      {
        throw new ArgumentNullException( "requestContext" );
      }
 
      VerifyExecuteCalledOnce();
      Initialize( requestContext );
      ExecuteCore();
    }

要说明一下这里兜了一个圈子,IController.Execute是一个显示接口实现,当我们将实例当作IController来调用时,会调用到这个方法,但旋即这个方法就调用了ControllerBase.Execute。那么来看Execute方法的实现。

VerifyExecuteCalledOnce,大意是验证Execute是否只被调用一次,一会儿来研究这个方法的实现。然后是初始化(Initialize),最后调用派生类的ExecuteCore方法(因为ExecuteCore是抽象方法)。

初始化的工作非常简单:

    protected virtual void Initialize( RequestContext requestContext )
    {
      ControllerContext = new ControllerContext( requestContext, this );
    }

从这里也能看出,ControllerContext = RequestContext + ControllerBase

同时我发现Initialize方法是个虚的,看看派生类是否有篡改,果然:

    protected override void Initialize( RequestContext requestContext )
    {
      base.Initialize( requestContext );
      Url = new UrlHelper( requestContext );
    }

不过逻辑也非常简单,也只是创建了一个UrlHelper的实例。Execute方法虽然也是虚的,但是Controller并没有篡改,而是老老 实实的实现了ExecuteCore。这个一会儿再看,先来研究一下这个VerifyExecuteCalledOnce的实现。话说研究源代码的好处就 在于你可以收获许多研究结论之外的东西:

    internal void VerifyExecuteCalledOnce()
    {
      if ( !_executeWasCalledGate.TryEnter() )
      {
        string message = String.Format( CultureInfo.CurrentUICulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType() );
        throw new InvalidOperationException( message );
      }
    }

调用了一个TryEnter方法,从方法名来看,似乎是进入一个什么状态?临界区?暂时不清楚这个方法和只调用一次的逻辑有什么关系,继续查看源代码:

    private readonly SingleEntryGate _executeWasCalledGate = new SingleEntryGate();
  // used to synchronize access to a single-use consumable resource
  internal sealed class SingleEntryGate
  {
 
    private const int NOT_ENTERED = 0;
    private const int ENTERED = 1;
 
    private int _status;
 
    // returns true if this is the first call to TryEnter(), false otherwise
    public bool TryEnter()
    {
      int oldStatus = Interlocked.Exchange( ref _status, ENTERED );
      return (oldStatus == NOT_ENTERED);
    }
 
  }

_executeCalledGate是一个SingleEntryGate的实例,SingleEntryGate的代码也一并列出了。从名称和代码基本上已经可以搞清楚是怎么一回事儿了。

这里的Interlocked.Exchange方法其实就是赋值,只不过是一个原子操作(就是说这个操作只有完成和未完成两种状态,不存在进行中状态),你可以简单的理解为这样的伪代码:

      lock ( _status )
      { 
        oldStatus = _status;
        _status = ENTERED;
      }

当然这个代码是不正确的,因为值类型是不能被lock的,明白大体上是这个意思就行。

其实TryEnter方法上的注释已经写的非常明白了,意思是:如果TryEnter是第一次被调用,那么返回true,否则返回false。

当TryEnter方法第一次被调用时,oldStatus是_status没有被修改之前的默认值也就是0,而_status则会被修改为1(ENTERED),然后比较oldStatus和0(NOT_ENTERED)得到一个true的结果,从而实现这个功能。

那么ControllerBase的Execute逻辑已经清楚了,主要就干了两件事儿,确保Execute方法只被调用一次和准备ControllerContext,然后就把工作交给派生类的ExecuteCore:

    protected override void ExecuteCore()
    {
      // If code in this method needs to be updated, please also check the BeginExecuteCore() and
      // EndExecuteCore() methods of AsyncController to see if that code also must be updated.
 
      PossiblyLoadTempData();
      try
      {
        string actionName = RouteData.GetRequiredString( "action" );
        if ( !ActionInvoker.InvokeAction( ControllerContext, actionName ) )
        {
          HandleUnknownAction( actionName );
        }
      }
      finally
      {
        PossiblySaveTempData();
      }
    }

方法一开头的注释大体上是告诉开发人员不要忘了还有BeginExecuteCore和EndExecuteCore这回事儿(如果这个方法的代码 需要更新,也请检查AsyncController的Begin和EndExecuteCore方法,看看代码是否也必须更新)。

猜测一下,由于IAsyncController的入口不再是Execute,这样ExecuteCore也就不会被调用到,写在 ExecuteCore里面的逻辑就应当被写到Begin和EndExecute中去。同样的,ControllerBase的Execute也不会被执 行,这部分逻辑恐怕也要写在Begin和EndExecute里面,看了一下源代码,果然不出所料。因为源代码太长,也与今天的研究没啥关系。就不贴了。

看完了注释,接下来是尽可能的(?)加载TempData,最后又有一个尽可能的(?)保存TempData。暂时不明白这个Possibly是咩 意思,但加载和保存临时数据还是能明白的,应该就是像ViewState一样的东西,这个与主线逻辑无关,暂时不去探究其实现。

然后是从路由数据中找出actionName,接着InvokeAction,如果返回false(我猜是找不到Action),则处理未知Action。

逻辑非常简单,可以看出来这里又把工作外包给了ActionInvoker去干,总结一下ExecuteCore的逻辑就是:

  • 加载临时数据
  • 调用action
  • 保存临时数据

由于我要追溯的是主线逻辑,所以继续来看ActionInvoker.InvokeAction。ActionInvoker是一个属性:

    public IActionInvoker ActionInvoker
    {
      get
      {
        if ( _actionInvoker == null )
        {
          _actionInvoker = CreateActionInvoker();
        }
        return _actionInvoker;
      }
      set
      {
        _actionInvoker = value;
      }
    }
    protected virtual IActionInvoker CreateActionInvoker()
    {
      return new ControllerActionInvoker();
    }

兜了一个圈子,我发现ActionInvoker属性的类型是IActionInvoker,而默认实例是一个ControllerActionInvoker类型的。

IActionInvoker只有一个方法:

  public interface IActionInvoker
  {
    bool InvokeAction( ControllerContext controllerContext, string actionName );
  }

那么职责显然是通过actionName调用Action,IActionInvoker的实现类型情况如下:

image

AsyncControllerActionInvoker和IAsyncActionInvoker应该是异步版本,那么看看ControllerActionInvoker的实现:

    public virtual bool InvokeAction( ControllerContext controllerContext, string actionName )
    {
      if ( controllerContext == null )
      {
        throw new ArgumentNullException( "controllerContext" );
      }
      if ( String.IsNullOrEmpty( actionName ) )
      {
        throw new ArgumentException( MvcResources.Common_NullOrEmpty, "actionName" );
      }
 
      ControllerDescriptor controllerDescriptor = GetControllerDescriptor( controllerContext );
      ActionDescriptor actionDescriptor = FindAction( controllerContext, controllerDescriptor, actionName );
      if ( actionDescriptor != null )
      {
        FilterInfo filterInfo = GetFilters( controllerContext, actionDescriptor );
 
        try
        {
          AuthorizationContext authContext = InvokeAuthorizationFilters( controllerContext, filterInfo.AuthorizationFilters, actionDescriptor );
          if ( authContext.Result != null )
          {
            // the auth filter signaled that we should let it short-circuit the request
            InvokeActionResult( controllerContext, authContext.Result );
          }
          else
          {
            if ( controllerContext.Controller.ValidateRequest )
            {
              ValidateRequest( controllerContext );
            }
 
            IDictionary<string, object> parameters = GetParameterValues( controllerContext, actionDescriptor );
            ActionExecutedContext postActionContext = InvokeActionMethodWithFilters( controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters );
            InvokeActionResultWithFilters( controllerContext, filterInfo.ResultFilters, postActionContext.Result );
          }
        }
        catch ( ThreadAbortException )
        {
          // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
          // the filters don't see this as an error.
          throw;
        }
        catch ( Exception ex )
        {
          // something blew up, so execute the exception filters
          ExceptionContext exceptionContext = InvokeExceptionFilters( controllerContext, filterInfo.ExceptionFilters, ex );
          if ( !exceptionContext.ExceptionHandled )
          {
            throw;
          }
          InvokeActionResult( controllerContext, exceptionContext.Result );
        }
 
        return true;
      }
 
      // notify controller that no method matched
      return false;
    }

好家伙,大量的代码都在这里了,我们慢慢来分析。

跳过一开始的入口检查,首先是获取两个Descriptor,ControllerDescriptor和ActionDescriptor,如果 ActionDescriptor是null,那么返回false,由于ActionDescriptor是由FindAction方法返回,结合调用方 的行为,有理由相信这里的逻辑是找不到Action的话就返回false,return false上方的注释也佐证了这一点。

然后从ActionDescriptor获取FilterInfo,从方法名GetFilters来看,FilterInfo应该是一个筛选器的集合。

紧接着进入一个try块,下面的catch逻辑首先是忽略ThreadAbortException(这个对于HTTP处理程序要说是必须的,因为 Response.End或Redirect就会产生这个异常),接着其他任何异常都会被捕获,然后InvokeExceptionFilters,这里 应该是异常筛选器(关于所有的Filter的内容,主线逻辑完成后我会来做一个总结)。如果异常没有被异常筛选器处理 (ExceptionHandled),那么继续抛出,否则InvokeActionResult(猜测这个方法就是调用 ActionResult.ExecuteResult)。

核实InvokeActionResult这个猜测很简单,看看源代码:

    protected virtual void InvokeActionResult( ControllerContext controllerContext, ActionResult actionResult )
    {
      actionResult.ExecuteResult( controllerContext );
    }

OK,枝节不继续深入,看try里面的情况,首先是调用授权筛选器(InvokeAuthorizationFilters),如果筛选器有结果(推测多半是授权失败之类),那么执行这个结果(InvokeActionResult)。

如果授权部分没有任何结果,那么看看Controller.ValidateRequest是不是true,决定是否进行ValidateRequest,这个ValidateRequest应该是检查XSS威胁之类的,实现如下:

    internal static void ValidateRequest( ControllerContext controllerContext )
    {
      if ( controllerContext.IsChildAction )
      {
        return;
      }
 
      // DevDiv 214040: Enable Request Validation by default for all controller requests
      // 
      // Note that we grab the Request's RawUrl to force it to be validated. Calling ValidateInput()
      // doesn't actually validate anything. It just sets flags indicating that on the next usage of
      // certain inputs that they should be validated. We special case RawUrl because the URL has already
      // been consumed by routing and thus might contain dangerous data. By forcing the RawUrl to be
      // re-read we're making sure that it gets validated by ASP.NET.
 
      controllerContext.HttpContext.Request.ValidateInput();
      string rawUrl = controllerContext.HttpContext.Request.RawUrl;
    }

果然,HttpContext.Request.ValidateInput()。最后的那个rawUrl赋值并不是闲着蛋疼的,上面的注释说了这个原因,大意是:如果不获取RawUrl的值,那么请求验证其实不会真正的被执行,可以认为这是ASP.NET的一个Bug。

继续研究,ValidateRequest之后,调用GetParameterValues方法来获取一个IDictionary<string, object>,这个从名称上来看是获取参数。

然后InvokeActionMethodWithFilters,接着InvokeActionResultWithFilters

InvokeActionResultWithFilters看起来就是InvokeActionResult的WithFilters版本:

    protected virtual ResultExecutedContext InvokeActionResultWithFilters( ControllerContext controllerContext, IList<IResultFilter> filters, ActionResult actionResult )
    {
      ResultExecutingContext preContext = new ResultExecutingContext( controllerContext, actionResult );
      Func<ResultExecutedContext> continuation = delegate
      {
        InvokeActionResult( controllerContext, actionResult );
        return new ResultExecutedContext( controllerContext, actionResult, false /* canceled */, null /* exception */);
      };
 
      // need to reverse the filter list because the continuations are built up backward
      Func<ResultExecutedContext> thunk = filters.Reverse().Aggregate( continuation,
          ( next, filter ) => () => InvokeActionResultFilter( filter, preContext, next ) );
      return thunk();
    }

相当的复杂,但我们看到的确是调用了InvokeActionResult,其他的代码大体上是筛选期的逻辑,这些在以后再铺展来谈。我们还是看看 InvokeActionMethodWithFilters是不是也调用了InvokeActionMethod然后应用筛选器的逻辑:

    protected virtual ActionExecutedContext InvokeActionMethodWithFilters( ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters )
    {
      ActionExecutingContext preContext = new ActionExecutingContext( controllerContext, actionDescriptor, parameters );
      Func<ActionExecutedContext> continuation = () =>
          new ActionExecutedContext( controllerContext, actionDescriptor, false /* canceled */, null /* exception */)
          {
            Result = InvokeActionMethod( controllerContext, actionDescriptor, parameters )
          };
 
      // need to reverse the filter list because the continuations are built up backward
      Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate( continuation,
          ( next, filter ) => () => InvokeActionMethodFilter( filter, preContext, next ) );
      return thunk();
    }

这两个方法的代码几乎是如出一辙(似乎问到了DRY的味道)。好,暂且不管复杂的Filter逻辑,我们赶紧来总结一下ActionInvoker.InvokeAction的流程:

  • 获取Controller的描述(Descriptor)
  • 查找Action(FindAction)
    • 如果找不到Action,那么返回false
  • 获取所有的筛选器
  • 进入try
    • 调用授权筛选
      • 如果授权筛选有结果,那么调用授权结果(猜测是授权失败之类)。
    • 获取参数
    • 调用ActionMethod(InvokeActionMethodWithFilters)
      • InvokeActionMethod
    • 调用ActionResult(InvokeActionResultWithFilters)
      • InvokeActionResult
        • ActionResult.ExecuteResult()
  • 如果try块内有任何不是ThreadAbortedException的异常
    • 调用异常筛选

如果我们把这些筛选的逻辑都去掉,则看起来像是这样:

  • 查找Action(FindAction)
  • 获取参数
  • InvokeActionMethod
  • InvokeActionResult

这里面 InvokeActionResult我们已经知道是干什么的了,而InvokeActionMethod从名称上来看应该是调用我们写在 Controller里面的被称之为Action的方法(例如HomeController.Index等),结合起来上面的 GetParameterValues方法就应该是获取这个方法的参数。