摘要: Azure Services Platform入门专题受到了越来越多人的关注,并被网络上许多网站进行转载。作者感到非常荣幸。目前这套入门教程已基本成型,我将它做成索引便于大家查阅。对 IT168 等网站无视国家法律未经作者许可擅自篡改版权进行转载的龌龊侵权行为表示最深的鄙视。
阅读全文
posted @ 2009-04-17 16:59 流牛木马 阅读(5474) | 评论(12) |
编辑
在实际建立工作流模型时,往往会有很多相似步骤。相似步骤间的逻辑,往往又会包括大量的if..else..判断。
例如,我们先看一个简单的报销流程。它很可能会是这样的:

看似很简单的四个步骤。但客户会告诉我们: 部门经理应该分为两个步骤,因为有些部门设立了“副经理”职位,有些部门甚至“经理”职位空缺,只设“副经理”。换言之,“经理”与“副经理”至少有一个。
于是我们将流程图改为以下情况:

我们发现,增加了一个步骤后,需要增加四条“流程路径”。例如,按照途中的说明,当流程以及流转到了“部门副经理”时,取得下一个步骤需要做一次“if…else…”。如果当前部门有“部门经理”,则下一步流转到“部门经理”步骤;如果当前部门没有“部门经理”,则直接流转到“会计”步骤。
这似乎能够满足客户的要求。但客户的组织机构往往非常复杂,他很可能在某一个时刻又告诉我们:我们共有两级部门,一共四个经理职位,均需要按照上述方式进行流转。
这是非常清晰明了的一个需求。于是我们又继续修改流程模型为:
此时我们就陷入了困境。这新增的两个步骤,带来了更多的逻辑关系,且每个逻辑关系都更为复杂。
例如,假如当前流程以及流转到了“二级部门副经理”处,工作流平台取得下一步骤需要做的判断至少有:
- 如果该部门有“二级部门经理”,则流转到“二级部门经理”;
- 如果该部门没有“二级部门经理”,但有“一级部门副经理”,则流转到“一级部门副经理”;
- 如果该部门没有“二级部门经理”,也没有“一级部门副经理”,但有“一级部门经理”,则流转到“一级部门经理”
- 如果该部门没有“二级部门经理”,也没有“一级部门副经理”,也没有“一级部门经理”,则流转到“会计”步骤
可以看到,仅仅“二级部门副经理”这一个步骤的“选择下一步骤”逻辑,就包含了4个“if…else…”判断,这是非常丑陋的。开发组往往兵强马壮,此种情况也可以通过大量的人工来保证。不过,可以预计的是,客户的多种流程(如报销、列账、冲销、预付等),都有类似的部门经理逻辑。大量的重复工作,无法保证不出错。
更为恐怖的是,假如客户有一天,变成了三个层级的部门组织架构了呢? 从“三级部门副经理”开始,一直往上,到“会计”步骤,那是不是仍然采用这样简单粗暴的方式? 再按照这种方式来做的话,也许我们就直接崩溃了。
怎么办呢?
其实,在GoF的23个设计模式中,就已经提到了一个经典的解决方案:职责链模式。
职责链(Chain of Responsibility)模式
责任链模式是一种对象的行为模式【GOF95】。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。 |
职责链模式的UML图:
这个UML图非常有意思。我们可以注意到,Handler角色自己聚合成自己。这有点类似于《数据结构》里描述的“链式结构”。
抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法,以设定和返回对下家的引用。这个角色通常由一个抽象类或接口实现。
具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
我们将这个设计模式运用到刚才所提到的实际环境中。
这时,我们的“抽象处理者”(Handler)是一个能够返回下一步步骤名称的处理类。对于上面提到的“二级部门”的情况,我们将会有五个“具体处理者”(ConcreteHandler):二级部门副经理、二级部门经理、一级部门副经理、一级部门经理、会计。 他们属于两个类别:“经理类”与“会计类”。
“会计类”的处理无逻辑判断。即,当步骤选择器流入“会计类”时,直接返回“会计类”的步骤名。
“经理类”就复杂一些,需要判断当前部门各个层级经理的有无、各个层级经理的是否已经审批。这两种判断可以抽象为两个数组参数的比较。我们设定两个数组参数byte[] candidates与byte[] approvers。第一个数组表示,候选步骤的有与无;第二个数字表示,在数据库记录中,所有步骤的当前审批状态.
例如,假如一个部门有“二级部门副经理、二级部门经理、一级部门经理”,没有“一级部门副经理”,即所有职位从小到大排列,第三大的职位空缺,其余职位齐全——则byte[] candidates的值可以设为{1,1,0,1}。在流程刚刚开始时,所有步骤均未审批,byte[] approvers值为{0,0,0,0};假如当前流程已经流转到“二级部门副经理”步骤,则byte[] approvers可以的值为{1,0,0,0}。
业务规则有了,参数有了,具体该如何处理呢?其实就是比较两个数组中相同index对应值的相等与否。
例如,假设当前 candidates为{1,1,0,1},approvers为{1,0,0,0}。从数组索引0开始,依次往后,我们可以知道“需要”“二级部门副经理”审批,当前“二级部门副经理”已经审批;“需要”“二级部门经理”审批,当前“二级部门经理”还未审批。很明显,流程下一步应该流转到“二级部门经理”步骤。
以上的自然语言描述翻译为算法为:按照索引从小到大的顺序,依次比较candidates数组与approvers数组的元素值是否相等;如果相等,跳到下一个索引处继续比较;如果不等,则返回当前索引所对照的步骤名称。
按照我们设计好的“职责链模式”实现这个整体算法。即:每个“经理”处理者类,只判断属于自己索引的两数组元素,如果相等,则“处理”(返回步骤名);如果不等,则移交下家进行后续判断。
首先构造处理者基类:

处理者基类
//处理者基类
abstract class Handler
{
protected int dutyId;//处理者对应的数据键值
protected string stepName;//处理者对应步骤的名称
protected Handler successor;//后继处理者
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
//处理任务的抽象方法,必须在每个子类进行Override
abstract public string HandleRequest(byte[] candidates, byte[] approvers);
}
构造两个类型的处理者子类:经理子类,会计子类。

具体处理者
/// <summary>
/// 会计处理者
/// </summary>
class Accounting : Handler
{
//会计处理者需要赋予“流程步骤名”
public Accounting(string _stepName)
{
stepName = _stepName;
}
//当职责链到达会计步骤时,直接进入会计步骤
override public string HandleRequest(byte[] candidates, byte[] approvers)
{
return stepName;
}
}
/// <summary>
/// 经理处理者
/// </summary>
class Manager : Handler
{
//经理处理者需要赋予“流程步骤名”与对应的“数据键值”
public Manager(int _dutyId, string _stepName)
{
dutyId = _dutyId;
stepName = _stepName;
}
override public string HandleRequest(byte[] candidates, byte[] approvers)
{
//如果两个值不相等,说明这个步骤还未处理过。立即返回该步骤的名称。
if (candidates[dutyId] != approvers[dutyId])
return stepName;
else
if (successor != null)
return successor.HandleRequest(candidates, approvers);
else
throw new ArgumentNullException("没有指定successor!");
}
}
客户端:

客户端(示例)
//客户端
public class StepSelector
{
//假设初始情况是:有二级部门副经理、有二级部门经理、
//无一级部门副经理、有一级部门经理
byte[] candidates;
//初始时,所有人均为审批
byte[] approvers;
//传入工作流平台的任务Id,初始化当前状态
StepSelector(string taskId)
{
//◇到业务平台中取得所有的候选步骤
candidates = GetCandidates(taskId);
//例如,初始情况可能是这样的:有二级部门副经理、有二级部门经理、
//无一级部门副经理、有一级部门经理。
//则设置如下:
//candidates = new byte[] { 1, 1, 0, 1 };
//◇到流程平台中取得当前已经完成的步骤
approvers = GetApprovers(taskId);
//初始时,所有人均为审批
//approvers = new byte[] { 0, 0, 0, 0 };
}
//客户端主要方法: 传入整体数据、当前状态,取得下一步骤的名称
public string GetNextStep(byte[] candidates, byte[] approvers)
{
Manager LevelTwoViceManager = new Manager(0, "二级部门副经理审批");
Manager LevelTwoManager = new Manager(1, "二级部门经理审批");
Manager LevelOneViceManager = new Manager(2, "二级部门经理审批");
Manager LevelOneManager = new Manager(3, "二级部门经理审批");
Accounting accounting = new Accounting("公司会计审核");
LevelTwoViceManager.SetSuccessor(LevelTwoManager);
LevelTwoManager.SetSuccessor(LevelOneViceManager);
LevelOneViceManager.SetSuccessor(LevelOneManager);
LevelOneManager.SetSuccessor(accounting);
//从最低值为“二级部门副经理”开始处理请求
return LevelTwoViceManager.HandleRequest(candidates, approvers);
}
}
请注意“客户端”的代码,它完完全全地示例了职责链模式的调用方法。总体分为三步:1. 实例化处理者。2. 设置后继关系 3.从最前端的处理者开始处理。
值得一提的是,这样的结构是符合“开放封闭原则”的(对修改封闭,对扩展开放)。也就是说,无论是需要两个部门经理来审批,还是四个部门经理来审批,甚至扩展到六个部门经理审批,都是不需要修改已有的业务逻辑代码,只需要在客户端中增加更多的处理者类实例、增加每个类的后继处理者、设置参数,就可以了。

posted @ 2010-04-14 20:45 流牛木马 阅读(1075) | 评论(4) |
编辑
两年前,微软亚洲研究院出版了《编程之美》一书,其中的第一章第一节“让CPU占用率曲线听你指挥”一文,可谓是在全国范围内掀起了一股玩转CPU占用率曲线热。我本人甚至在坐公交的时候都在思考如何折腾CPU占用率曲线。
书中给出了这样一张让人耳目一新的图,吸引了万千IT少年:
也许您会觉得这还不够好玩。我也是。对了,今天流牛木马打算与各位看官讨论的,是以下这幅图的效果——双核的CPU,左手画圆,右手画方!
该书出版的时候,我正在微软亚洲研究院创新工程组实习,该书的好几位作者都是我的好朋友,如李东、陈远等 ——于是我可以负责任地告诉大家,该书的所有实习生作者 ,当时他们使用的都是Intel P4 3.4Ghz单核CPU ! o(∩_∩)o
今天突然想到了这里,就打算拿我的Thinkpad T60来试试。CPU是Genuine Intel(R) CPU T2400 @1.83Ghz , 双核。
首先我将书中提供的C++代码翻译成了C# .翻译过程没任何技术难度,仅供各位看官了解个大概:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
const double SPLIT = 0.01;
const int COUNT = 200;
const double PI = 3.14159265;
const int INTERVAL = 100;
double[] busySpan = new double[COUNT]; //array of busy times
double[] idleSpan = new double[COUNT]; //array of idle times
int half = INTERVAL / 2;
double radian = 0.0;
for (int i = 0; i < COUNT; i++)
{
busySpan[i] = (double)(half + (Math.Sin(PI * radian) * half));
idleSpan[i] = INTERVAL - busySpan[i];
radian += SPLIT;
}
double startTime = 0;
int j = 0;
while (true)
{
j = j % COUNT;
startTime = Environment.TickCount;
while ((Environment.TickCount - startTime) <= busySpan[j]) ;
System.Threading.Thread.Sleep((int)idleSpan[j]);
j++;
}
}
}
}
将这段代码直接F5, 运行结果如下:
果然,与大家意料的一样,两个CPU中都会显示出非常扭曲、不规则的正弦曲线。
按照书中的说法,C++程序可以使用WinAPI里的SetThreadAffinityMast()函数,将程序固定到某个特定的CPU上执行。
查了下MSDN,当然,在.NET里,也有Manage的相似代码。见http://msdn.microsoft.com/zh-tw/library/system.diagnostics.process.processoraffinity(VS.80).aspx
按照MSDN,在代码中加入了简单的两句:
System.Diagnostics.Process p = System.Diagnostics.Process.GetCurrentProcess();
p.ProcessorAffinity = (IntPtr)0x0001;
然后再次F5, CPU中的曲线如下图所示:

1号CPU成规则的正弦曲线相当完美,2号CPU使用率一直为0.
看来这就是书中想让大家达到的效果了。
这个效果让人感觉非常枯燥乏味。2号CPU的资源就浪费掉了,有没有办法将它利用起来呢?
让我们来分析这两句代码:
while ((Environment.TickCount - startTime) <= busySpan[j]) ;
System.Threading.Thread.Sleep((int)idleSpan[j]);
它的意思是,在规定好的时间间隔内(程序中规定了是100毫秒),满负载运行(通过空循环)构成正弦函数需要运行的时间(由之前的计算得到),其余时间就睡觉。如运行69毫秒,睡觉31毫秒,那么在这100毫秒的时间段,CPU的平均占用率就是69%。由于CPU占用率曲线是1秒钟更新一次的,程序使用的每100毫秒一个平均值节点,曲线的平滑度足够了。
继续发散。刚才的例子,如果是运行69毫秒,睡觉50毫秒呢? 那么当前的100毫秒CPU占用率仍然会是69%,但如果算法不加调整,仍然以100毫秒为一个平均值节点,这就影响到了下一个100毫秒的CPU占用率。前一个影响后一个,如多米诺骨牌一样,整体的曲线就完全错误了。
也就是说,我们用来控制CPU平均占用率的时间段,每段必须相等。
我们考虑到以上的代码中,在1号CPU中运行了69毫秒,剩下了31毫秒如果转到2号CPU中运行,1号CPU的占用就会降到0,但与此同时我们就可以在2号CPU中“画方”了。
概括起来说就是,当1号CPU小寐的时候,程序在2号CPU中“画方”;1号CPU小寐结束的时候,2号CPU中当前周期的“方”必须完成。
需要注意的是,假如1好CPU画了69毫秒,那么2号CPU一定只能画31毫秒。
按照这个思路,我们先实验一下简单的:在1号CPU中画正弦曲线后的休息时间段,我们在2号CPU中画余弦曲线。呵呵,初中学的三角函数你忘了吗?
我们将原代码中的空循环改为:
while (true)
{
j = j % COUNT;
p.ProcessorAffinity = (IntPtr)0x0001;
startTime = Environment.TickCount;
while ((Environment.TickCount - startTime) <= busySpan[j]) ;
p.ProcessorAffinity = (IntPtr)0x0002;
while ((Environment.TickCount - startTime) <= idleSpan[j]) ;
j++;
}
“左手正弦,右手余弦”结果差强人意:
我们注意到这个曲线没有之前单CPU运行时那么精确的。
原因是我们切换CPU的运营占用了时间。例如,以前正弦画了69毫秒,我们计算出余弦应该画31毫秒,但切换CPU的操作可能占用了3毫秒(或更多、或每次耗时都不同),整体就不在100毫秒这个间隔范围了,所以曲线结果有误差。
到这里,我的思路已经说完了,相信大家已经明白。
不过, 走到这一步,追求完美的我们,的确是遇到了一点障碍。
我经过一些周期上的微调,并且将余弦函数改为了简单的方波函数,最终效果正如本文开头展示的一样,还是不错的。
调整办法?呵呵,真不好意思,我暂时还没有想到一个足够说服大家、可以公式化的调整办法,就请各位看官在这里八仙过海,各显神通咯~

posted @ 2010-03-24 00:50 流牛木马 阅读(3092) | 评论(34) |
编辑
1.1 定义
在一些B/S结构的应用系统中,有很多页面是需要有水印的。常见的就是公文系统、合同系统等。大家常常关注的是网站图片增加水印,而很少关注页面水印。刚去Google了一圈,关于页面水印的文章的数量为几乎为0. 本文中,流牛木马就与大家一起交流一下有关制作网页水印的心得。
本文讨论以下的情形: 新增水印的方法需要用Javascript完成,并要求能够方便地加入到原有的页面中,不能影响到已有的功能。
1.2 预期目标
就图片水印实现方案来说,我们预期至少包括以下几个目标:
1. 实现悬浮、半透明的图片水印
2. 包含水印的页面,所有元素均为只读(不可写)
3. 在包含框架页面中,可以控制任意一个子页面或父页面的水印生成
4. 在页面放大、缩小(resize过程)后,需要在保证页面不刷新的前提下,重新生成适应页面大小的新水印,以保证所有内容都被水印覆盖,并且不会因水印图片范围过大而产生滚动条。
5. 支持IE6\7\8浏览器。暂不考虑其他浏览器。
1.3 效果图
加密前:
加密后:
可以打开附件里的文件进行查看。
2 实现步骤
2.1 基本构思
加密的过程是一个Javascript函数执行过程,所以我们首先应该考虑用Javascript操作DOM对象的方式。
在已有的HTML页面中,新建一个DOM对象在Body节点下。该对象的长、宽均经过计算,保证在覆盖全部页面内容的同时又不产生滚动条。将该对象覆盖到原有的页面之上,设置背景图,并设置为透明。
创建新DOM元素:
使用document对象里的createElement方法。创建元素后,设置它的z-index为一个大整数,保证它能够比已有网页的最大z-index大,才能完成“覆盖”。
计算新对象大小:
利用三个DOM对象值: clientWidth 、scrollHeight与clientHeight.
网页中一般不会出现横向滚动条,故不使用scrollWidth.
而纵向的滚动条就很常见了。
为了保证页面内容全部覆盖,在未出现滚动条的时候,使用clientHeight,出现滚动条后,则使用scrollHeight。
设置透明:
利用Alpha值。Alpha是IE支持的css filter。
2.2 应变细节
有一个小细节是很有意思的,前文也提过了,就是resize的过程。
试想,当一个页面打开的时候是550px×400px,那么自然会生成550px×400px大小的水印。但当用户对它进行最大化时,页面没有刷新,不会重新执行生成水印的函数,那么以前生产的水印图片就太小了。
如下图所示的情况。请注意,它的右侧、下侧都是没有水印的。
为了应对这种情况,我们就需要对body的onresize()函数进行处理。如果以前定义没有onresize()函数,则直接添加onresize();如果以前有onresize()函数,则对之进行修改。
2.3 最终代码
考虑到框架页面需要考虑的情况,该方法包括三个参数: 目标页面对象、目标页面字符串、 背景图片。
function GetWaterMarked(targetObj,jpgUrl,targetStr ) {
var windowobj=targetObj;
var waterMarkPicUrl=jpgUrl;
var controlWindowStr=targetStr;
if(windowobj.document.getElementById("waterMark") != null)
return;
var m = "waterMark";
var newMark = windowobj.document.createElement("div");
newMark.id = m;
newMark.style.position = "absolute";
newMark.style.zIndex = "9527";
newMark.style.top = "0px";
newMark.style.left = "0px";
newMark.style.width = windowobj.document.body.clientWidth;
if( parseInt(windowobj.document.body.scrollHeight) > parseInt(windowobj.document.body.clientHeight) )
{
newMark.style.height = windowobj.document.body.scrollHeight;
}else
{
newMark.style.height = windowobj.document.body.clientHeight;
}
newMark.style.backgroundImage = "url("+ waterMarkPicUrl +")";
newMark.style.filter = "alpha(opacity=50)";
windowobj.document.body.appendChild(newMark);
var markStr = "var sobj ="+controlWindowStr+".document.getElementById('waterMark');sobj.style.width ="+controlWindowStr+".document.body.clientWidth;sobj.style.height ="+controlWindowStr+".document.body.clientHeight;";
if(windowobj.document.body.onresize != null)
{
var oldResiae = windowobj.document.body.onresize.toString();
var oldResiaeStr = oldResiae.substr(oldResiae.indexOf("{")+1);
var oldResiaeStr= oldResiaeStr.substr(0,oldResiaeStr.lastIndexOf("}"));
oldResiaeStr+=";"+markStr;
windowobj.document.body.onresize = new Function(oldResiaeStr);
}
else
{
windowobj.document.body.onresize = new Function(markStr);
}
}
3 原有页面处理
需要在原有的<body>标签处加入一个onload方法。如:
<body onload="GetWaterMarked(window,'watermark.jpg','this')">
4 附件
http://files.cnblogs.com/azure/%E6%B0%B4%E5%8D%B0.rar
_________________________________________________________________________________
关于扩展到非IE浏览器:
有很多朋友问起如何扩展到非IE浏览器。
我文中有提到设置水印图片透明的方式,那是一种仅适用于IE的方式。要在其他浏览器中的设置图片透明,也是很容易的。 我还没有做测试,请各位看官自行参考这篇帖子:
http://dancewithnet.com/2009/09/06/css-opacity/
posted @ 2010-02-28 22:56 流牛木马 阅读(2537) | 评论(12) |
编辑
经过特邀评委们艰难的抉择,赢在淘宝的30强、10强终于出炉啦!(排名不分先后)
稍后将在应用展示页面添加特邀评审团评语!敬请期待哦~
第三轮评选——特邀评审团 赵立威 微软(中国)开发工具与平台事业部首席顾问 杨巍
Google中国战略合作总监 白鸦 支付宝首席用户体验规划师 徐毅律 上海新中欧创业投资管理有限公司合伙人 王文彬
淘宝开放平台副总裁
原文见 http://open.taobao.com/bbs/read.php?tid=6310
posted @ 2009-12-29 13:26 流牛木马 阅读(374) | 评论(1) |
编辑
摘要:

Google Wave已经公测一个月了,再全世界范围内都掀起了一股索要Google Wave的热潮。做为一个有思想的程序员,除了为Google Wave自身的新特性感动兴奋和赞叹以外, 我们还好奇Google Wave 提供的API能干些什么。流牛木马在这里为您介绍它。
阅读全文
posted @ 2009-11-13 22:27 流牛木马 阅读(2509) | 评论(15) |
编辑