[转载]构建canvas动画框架(一)

mikel阅读(1806)

[转载]构建canvas动画框架(一) – shawn.xie – 博客园.

最近一直在做canvas动画效果,发现canvas这个东西做动画不是不可以。相对于flash,它太底层。如果有给力的编辑器或者给力的框架的话,它就能发挥出更大的威力。

于是决定自己写一个简单一点的动画框架,以便能更方便地构建出一些动画效果。

我将分几个章节来讲述我这个小动画框架的实现:

1.通用类的提取:动画对象与帧对象

2.灵与肉的结合:便于拆卸的运动方程

3.进度条的实现:canvas的图片预加载

4.demo测试:通过一个demo测试框架

这一节我们先来说说通用类的提取。

其实上一篇文章我已经用到了这种从flash借鉴来的思路:一个动画对象(类似flash中的元件),一个帧对象(类似flash中的帧)。动画就是在不断在当前帧上绘制每个动画对象来实现的。有了这两个对象,再加上一些运动方法,我们就可以构建出动画来。

首先我们先来看看动画对象Aniele:

/*
 *Aniele动画对象
 *所有动画对象的始祖
 */
 var Aniele=function(){
     this.img=new Image();
     //定义动画对象位置
     this.loca={
             x:300,
             y:300
     }
     //定义动画对象的大小(可以实现缩放)
     this.dw;
     this.dh;
     //动画对象的速度属性
     this.speed={
             x:0,
             y:0
     }    
     //设置对象的透明度
     this.alpha=1;
     //设置图像翻转,1为不翻转,-1为翻转
     this.scale={
             x:1,
            y:1
     }
     //定动画对象的运动方法库
     this.motionFncs=[];
 }
 Aniele.prototype={
     //添加运动方法
     addMotionFnc:function (name,fnc) {
        this.motionFncs[name]=fnc;
    },
    //删除运动方法
    deleMotionFnc:function(name){
        this.motionFncs[name]=null;
    },
    //遍历运动方法库里的所有运动方法
    countMotionFncs:function () {
        for (var i=0; i<this.motionFncs.length; i++) {
            if(this.motionFncs[i]==null)
                continue;
            this.motionFncs[i].call(this);
        }
    },
    //把自己绘制出来的方法,包括功能:水平翻转
    draw:function(canvas,ctx){
        //存储canvas状态
        ctx.save();
        //实现透明度的改变
        ctx.globalAlpha=this.alpha;
        //实现水平竖直翻转,定义drawImage的两个位置参数dx,dy
        var dx=this.loca.x;
        var dy=this.loca.y;
        if(this.scale.x!=1||this.scale.y!=1){
            if(this.scale.x<0){
                console.log(this.img.width)
                dx=canvas.width-this.loca.x-this.img.width;
                ctx.translate(canvas.width,1);
                ctx.scale(this.scale.x,1);
            }    
            if(this.scale.y<0){
                dy=canvas.height-this.loca.y-this.img.height;
                ctx.translate(1,canvas.height);
                ctx.scale(1,this.scale.y);
            }    
        }
        if(this.dw==null)
            this.dw=this.img.width;
        if(this.dh==null)
             this.dh=this.img.height;
        //画出对象
        ctx.drawImage(this.img,dx,dy,this.dw,this.dh);
        //恢复canvas状态    
        ctx.restore();
    }
 }

动画对象的主要属性:

this.img=new Image();我们引入一张图片,依附在动画对象上

this.loca.x等等;图片的大小位置透明度等等,便于绘图时调用

this.motionFncs=[];这个比较关键,我们给动画对象定义一个运动方法库,把动画对象的运动规则都放在这个运动方法库中统一管理(每个动画对象都有自己的运动方法库)

动画对象的主要方法:

addMotionFnc: 为动画对象的运动方法库中添加一个运动方法

deleMotionFnc:为动画对象的运动方法库中删除一个运动方法

countMotionFncs:为动画对象遍历运动方法库中的所有运动方法

draw:把动画对象画在画布上,这里我们会把画布作为参数传到这个方法里面去,便于绘图

在draw方法里,我封装了一些对图像的简单操作,这些操作在动画中会经常用到:透明,缩放和翻转。

有了这个,我们就好似获得了flash里的一个元件,我们可以通过修改它的属性来随意改变它。

那么帧对象呢?

帧对象肩负着渲染的任务,并且管理所有动画对象:

/*
*Render渲染对象
*管理所有动画对象和渲染
*参数:画布对象,画布上下文
*/
var Render=function (canvas,ctx) {
    //引入画布
    this.canvas=canvas;
    this.ctx=ctx;
    //创建一个缓冲画布
    this.backBuffer=document.createElement('canvas');
    this.backBuffer.width=this.canvas.width;
    this.backBuffer.height=this.canvas.height;
    this.backBufferctx=this.backBuffer.getContext('2d');
    //所有动画对象
    this.aniEles=[];
}
Render.prototype={
    //初始化画布int
    int:function () {
        clearInterval(this.sint);
        this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
        this.backBufferctx.clearRect(0,0,this.backBuffer.width,this.backBuffer.height);
    },
    //设置开始渲染
    begin:function () {
        this.lastFrame=(new Date()).getTime();
        this.sint=setInterval((function(progra){
            return function(){progra.render();}
        })(this),SECOND);    
    },
    //主渲染方法
    render:function () {
        //在画布和缓存画布上清除历史帧
        this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
        this.backBufferctx.clearRect(0,0,this.backBuffer.width,this.backBuffer.height);
        //保存当前的实时输出帧率this.ftp
        this.nowFrame=(new Date()).getTime();
        this.ftp=1000/(this.nowFrame-this.lastFrame);
        this.lastFrame=this.nowFrame;
        //调用每个动画对象的运动方法
        for (var i=0; i<this.aniEles.length; i++) {
            if(this.aniEles[i]==null)
                continue;
            this.aniEles[i].countMotionFncs();
            //把对象绘制到后台缓冲画布上
            this.aniEles[i].draw(this.backBuffer,this.backBufferctx);
        }    
        //把后台对象绘制到前台
        this.ctx.drawImage(this.backBuffer,0,0);
    },
    //增加动画对象
    addAniEle:function (name,aniEle) {
        this.aniEles[name]=aniEle;
    },
    //删除动画对象
    deleAniEle:function (name) {
        this.aniEles[name]=null;
    }
}

帧对象的主要属性:

this.aniEles=[];用来存储当前画布上所有动画实例的数组

大家用过canvas载入图片的应该知道,由于图片的异步载入,动画过程中图片会出现闪烁的现象,为了避免这种现象,我采用了双缓冲。

首先后台创建一个画布:

this.backBuffer=document.createElement(‘canvas’);

this.backBuffer.width=this.canvas.width;

this.backBuffer.height=this.canvas.height;

this.backBufferctx=this.backBuffer.getContext(‘2d’);

我们所有绘制命令都执行在这个后台画布上,最后把后台画布画在前台画布上:

this.ctx.drawImage(this.backBuffer,0,0);

这种先把图绘在后台画布,再把后台画布复制到前台的方法就叫做双缓冲技术。

帧属性的主要方法:

int:用于初始化画布

begin:开始动画渲染的方法

render:主渲染的方法

addAniEle:为当前帧添加动画对象

deleAniEle:为当前帧删除动画

我们利用帧对象的流程是:先为当前帧添加动画对象,然后让当前帧开始渲染。

[转载]IIS下发布关于Excel导入导出时遇到的问题集锦

mikel阅读(1109)

[转载]IIS下发布关于Excel导入导出时遇到的问题集锦 – guanhp2013 – 博客园.

问题描述

1、Excel每个工作薄(sheet)生成记录行数

2、ASP.NET关于导出Excel的一些问题的集锦

3、下载失败,临时文件或其所在磁盘不可写

4、未能加载文件或程序集“Microsoft.Office.Interop.Excel, Version=14.0.0.0,**

5、IIS下发布操作Excel程序时遇到{00024500-00***即80070005错误

6、System.ComponentModel.Win32Exception: 拒绝访问

有关这这几个问题详解。

问题所在环境

操作系统WIndows Server2003、Windows7

IIS6.*、IIS7.*

需安装软件Office2010

详细解决方案

1、Excel每个工作薄(sheet)生成记录行数

一个excel文件里最多能放多少条数据?

excel 2003()以下 65535 , excel20071,048,576

 

2、ASP.NET关于导出Excel的一些问题的集锦

a下载不下来:可考虑使用IE自带的下载

b如果使用迅雷,建议先将寻来打开,在进行下载,否则会提示磁盘被占用等的信息

c请设置Templetes/Output/Base下的Excel属性为只读前面的对号去掉

 

3、下载失败,临时文件或其所在磁盘不可写

[解决方案]

C:\Windows下的Temp文件夹修改安全属性Users赋予权限即可解决;可用

或者

尝试将迅雷的安装目录更改:未尝试

4、未能加载文件或程序集“Microsoft.Office.Interop.Excel,Version=14.0.0.0,**

【问题描述】 
未能加载文件或程序集“Microsoft.Office.Interop.Excel, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”或它的某一个依赖项。系统找不到指定的文件。

说明执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。

异常详细信息: System.IO.FileNotFoundException: 未能加载文件或程序集“Microsoft.Office.Interop.Excel,Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”或它的某一个依赖项。系统找不到指定的文件。

源错误:

执行当前 Web 请求期间生成了未处理的异常。可以使用下面的异常堆栈跟踪信息确定有关异常原因和发生位置的信息。

程序集加载跟踪下列信息有助于确定程序集“Microsoft.Office.Interop.Excel, Version=14.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c”无法加载的原因。

【解决方案】警告程序集绑定日志记录被关闭。

要启用程序集绑定失败日志记录,请将注册表值 [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD)设置为 1

注意会有一些与程序集绑定失败日志记录关联的性能损失。

要关闭此功能,请移除注册表值 [HKLM\Software\Microsoft\Fusion!EnableLog]

服务器上没有安装Office

提出以后,管理员安装了Office2003(注意安装以后要重启电脑)

按照网上找到的一些方法,

安装:

vstor.exe

下载地址:

http://www.microsoft.com/downloads/details.aspx?FamilyId=8315654B-A5AE-4108-B7FC-186402563F2B&display#filelist

然后下载安装:

O2003PIA.EXE

http://www.microsoft.com/downloads/details.aspx?FamilyId=3C9A983A-AC14-4125-8BA0-D36D67E0F4AD&display

之后还是没有解决

然后

在服务器上开始程序管理工具-Microsoft .NET Framework 2.0 配置管理程序集缓存将程序集添加到程序集缓存。选中那些dll即可。

然而不幸的是还是没有解决。

最后重启IIS 问题解决了。

具体怎么解决的就是以上两个方法之一或两者都必须做到。

我认为一个很重要的问题是

C:\WINDOWS\assembly目录下面必须要有项目的引用。

未能加载文件或程序集“Microsoft.Office.Interop.Excel, Version=11.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c”或它的某一个依赖项。系统找不到指定的文件。

其中的原理是

计算机上缺少了项目引用的Office类文件Interop.Excel.dll

vstor.exeO2003PIA.EXEoffice可重用开发XXX的安装程序。

5、IIS下发布操作Excel程序时遇到{00024500-00***即80070005错误

【问题没描述】英文版

Retrieving the COM class factory forcomponent with CLSID {00024500-0000-0000-C000-000000000046} failed due to thefollowing error: 80070005.

Description: An unhandled exceptionoccurred during the execution of the current web request. Please review thestack trace for more information about the error and where it originated in thecode.

Exception Details:System.UnauthorizedAccessException: Retrieving the COM class factory forcomponent with CLSID {00024500-0000-0000-C000-000000000046} failed due to thefollowing error: 80070005.

ASP.NET is not authorized to access therequested resource. Consider granting access rights to the resource to theASP.NET request identity. ASP.NET has a base process identity (typically{MACHINE}\ASPNET on IIS 5 or Network Service on IIS 6) that is used if theapplication is not impersonating. If the application is impersonating via<identity impersonate=”true”/>, the identity will be theanonymous user (typically IUSR_MACHINENAME) or the authenticated request user.

 

To grant ASP.NET access to a file,right-click the file in Explorer, choose “Properties” and select theSecurity tab. Click “Add” to add the appropriate user or group.Highlight the ASP.NET account, and check the boxes for the desired access.

【问题描述】中文版

检索 COM 类工厂中 CLSID  {00024500-0000-0000-C000-000000000046} 的组件时失败,原因是出现以下错误: 80070005

说明执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。

异常详细信息: System.UnauthorizedAccessException: 检索 COM 类工厂中 CLSID {00024500-0000-0000-C000-000000000046} 的组件时失败,原因是出现以下错误:80070005

ASP.NET 未被授权访问所请求的资源。请考虑授予ASP.NET 请求标识访问此资源的权限。ASP.NET 有一个在应用程序没有模拟时使用的基进程标识(通常,在 IIS 5 上为 {MACHINE}\ASPNET,在 IIS 6 上为网络服务)。如果应用程序正在通过 <identity impersonate=”true”/> 模拟,则标识将为匿名用户(通常为IUSR_MACHINENAME)或经过身份验证的请求用户。

 

要将 ASP.NET 访问权限授予某个文件,请在资源管理器中右击该文件,选择“属性”,然后选择“安全”选项卡。单击“添加”添加适当的用户或组。突出显示 ASP.NET 帐户,选中所需访问权限对应的框。

源错误:

执行当前 Web 请求期间生成了未处理的异常。可以使用下面的异常堆栈跟踪信息确定有关异常原因和发生位置的信息。

【解决方案1】

1:在服务器上安装officeExcel软件.

2:开始“->”运行中输入dcomcnfg.exe启动组件服务

3:依次双击组件服务“->”计算机“->”我的电脑“->”DCOM配置

4:“DCOM配置中找到“MicrosoftExcel 应用程序“,在它上面点击右键,然后点击属性“,弹出“Microsoft Excel 

用程序属性对话框

5:点击标识标签,选择交互式用户

6:点击安全标签,启动和激活权限上点击自定义“,然后点击对应的编辑按钮,在弹出的安全性对话框中填加

一个“NETWORK SERVICE”用户(注意要选择本计算机名),并给它赋予本地启动本地激活权限.

 

7:依然是安全标签,访问权限上点击自定义“,然后点击编辑“,在弹出的安全性对话框中也填加一个“NETWORK

SERVICE”用户,然后赋予本地访问权限.

这样,我们便配置好了相应的ExcelDCOM权限.

注意:这是在WIN2003上配置的,2000,xp,NETWORK SERVICE用户改为ASP.net用户

【解决方案】

.NET导出Excel遇到的80070005错误的解决方法:

检索 COM 类工厂中 CLSID  {00024500-0000-0000-C000-000000000046}的组件时失败,原因是出现以下错误: 80070005基本上.net导出excel文件,都需要如此配置一下,不配置有的时候没错,而配置后基本应该不会出错。

具体配置方法如下:

1:在服务器上安装officeExcel软件.

2:开始“->”运行中输入dcomcnfg.exe启动组件服务

3:依次双击组件服务“->”计算机“->”我的电脑“->”DCOM配置

4:“DCOM配置中找到“Microsoft  Excel 应用程序“,在它上面点击右键,然后点击属性“,弹出“MicrosoftExcel 应用程序属性对话框

5:点击标识标签,选择交互式用户

6:点击安全标签,启动和激活权限上点击自定义“,然后点击对应的编辑按钮,在弹出的安全性对话框中填加一个“NETWORK  SERVICE”用户(注意要选择本计算机名),并给它赋予本地启动本地激活权限.

7:依然是安全标签,访问权限上点击自定义“,然后点击编辑“,在弹出的安全性对话框中也填加一个“NETWORK  SERVICE”用户,然后赋予本地访问权限.

8.如果交互式用户设置后出现错误8000401a,可取消交互式用户,指定为administratr,可暂时解决此问题。进一步的解决方式还有待探讨。

9.采用第8点的设置后,打开Excel可能会出现“无法使用对象引用或链接”,并且不能进行单元格粘贴。原因不明,取消设置后即可消失。

 

另外说明:光按以上设置并不能完全保证不出问题,因为调用OFFICE的操作可能涉及其他的系统资源,很可能依然权限不够而导致问题,只是设置EXCEL应用程序后,还是可能会报80070005的错误,最保险的是应该在dcomcnfg中选中我的电脑然后右键属性->com安全,将里面的访问权限,启动和激活权限全都编辑默认,在当中加上network service(iis 5.0的话,要加的是asp.net用户),并如前面对networkservice设置上相同的权限,这样基本上能保证不会再出什么问题。

以上为基本出路方法,但是我也遇到过应用以上方法还是报错的情况,我的环境是windows2003 + office2003+office2007,在测试服务器上都可以导出无任何问题!但是更新了正式环境就是报错,权限都设置了还是报错,因为我的测试服务器是默认office启动的,所以我怀疑是office版本环境问题!于是我卸载了office2007,马上测试ok了,但是重启服务器后就产生了have not been Pre-compiled错误,导出页面都无法打开了,于是我又重新安装了office2007,页面可以打开了,但是导出excel时又开始报错!我又把所有的权限重新设置一遍,问题依旧!后来发现两个服务器默认打开excel的程序不一样,测试环境是默认office2003打开,正式环境是office2007打开,于是我在正式环境重新注册office2003,使其同样也是默认office2003打开程序,靠!问题终于解决了!!!

注册方法执行    开始—-运行—-输入excel2003的安装路径,例如“C:\Program Files\Microsoft Office\OFFICE11\excel.exe”/regserver    注意/符号前面有一个空格,其中“C:\ProgramFiles\Microsoft Office\OFFICE11\excel.exe” excel2003的安装程序的路径,参数regserver表示注册的意思!

运行后,会启动Office 2003的安装程序,进行修复,重新注册。

excel2007为默认的启动程序的方法类同,可以执行   开始—-运行—-“D:\ProgramFiles\Microsoft Office\Office12\excel.exe” /regserver

综上所述,再遇到Excel导出、导入问题时,先考虑权限问题,再考虑office的版本环境!如此基本能够解决这些问题了

6System.ComponentModel.Win32Exception:拒绝访问

【问题描述】

拒绝访问。

说明执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。

异常详细信息System.ComponentModel.Win32Exception:拒绝访问。

源错误:

执行当前 Web 请求期间生成了未处理的异常。可以使用下面的异常堆栈跟踪信息确定有关异常原因和发生位置的信息。

堆栈跟踪:

 [Win32Exception (0x80004005): 拒绝访 问。]   System.Diagnostics.ProcessManager.OpenProcess(Int32 processId, Int32 access, Boolean throwIfExited) +964480   System.Diagnostics.Process.GetProcessHandle(Int32 access, Boolean throwIfExited) +396   System.Diagnostics.Process.GetProcessTimes() +67   System.Diagnostics.Process.get_StartTime() +31   Hosentic.CommonHelper.ExcelHelper.KillExcelProcess() in D:\Workspaces\visual studio 2010\hxtr\Soft_Hisense_MobileSales\Hosentic.CommonHelper\ExcelHelper.cs:1852   Hosentic.CommonHelper.ExcelHelper.Dispose() in D:\Workspaces\visual studio 2010\hxtr\Soft_Hisense_MobileSales\Hosentic.CommonHelper\ExcelHelper.cs:1897   Hosentic.CommonHelper.ExcelHelper.SaveFile() in D:\Workspaces\visual studio 2010\hxtr\Soft_Hisense_MobileSales\Hosentic.CommonHelper\ExcelHelper.cs:1651   Soft_Hisense_MobileSales.BLL.Base.DeliveryBatchOutputHandler.ProcessRequest(HttpContext context) in D:\Workspaces\visual studio 2010\hxtr\Soft_Hisense_MobileSales\Soft_Hisense_MobileSales.BLL\Base\DeliveryBatchOutputHandler.cs:123   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +181   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75

 

【解决方案】

添加一句代码

<identity impersonate=”true” userName=”accountname”password=”password” />

加在System.web

<system.web>

<identity impersonate=”true”userName=”Administrator” password=”1234568@abc.1″ />

<customErrorsmode=”Off”/>

 

<httpRuntimemaxRequestLength=”102400″ executionTimeout=”720″/>

<!–

设置 compilation Debug=”true” 可将调试符号插入到

已编译的页面。由于这会

影响性能,因此请仅在开发过程中将此值

设置为 true

【Internet注解】

前两天在VS2005下做个项目,用里面集成的Web服务调试没问题,但当把该项目在IIS下配置后,用浏览器浏览结果则不正确,这个问题郁闷了一天,后来我想不出招就让我同事调试了一下,他在Web.config中加了一句话:“<identity impersonate=”true”  userName=”操作系统用户“password=”用户密码“/>”,浏览,结果正确,后来我就在网上查了下这句话的作用,MSDN是这样说的:
1、模拟 IIS 验证的帐户或用户

若要在收到 ASP.NET 应用程序中每个页的每个请求时模拟 Microsoft Internet 信息服务 (IIS) 身份验证用户,必须在此应用程序的Web.config 文件中包含 <identity> 标记,并将 impersonate 属性设置为 true

2、为 ASP.NET 应用程序的所有请求模拟特定用户

若要为 ASP.NET 应用程序的所有页面上的所有请求模拟特定用户,可以在该应用程序的 Web.config 文件的 <identity> 标记中指定userName  password 属性。例如:
<identityimpersonate=”true” userName=”accountname”password=”password” />

[转载]memcached源码分析之线程池机制(一)

mikel阅读(1125)

[转载]memcached源码分析之线程池机制(一) – Moon_Bird – 博客园.
已经个把月没有写长篇博文了,最近抽了点时间,将memcached源码分析系列文章的线程机制篇给整出来,在分析源码的过程中参考了网上的一些资源。

该文主要集中于两个问题:(1)memcached线程池是如何创建的,(2)线程池中的线程又是如何进行调度的。一切从源码中找答案。

memcached的线程池模型采用较典型的Master-Worker模型:

(1)主线程负责监听客户端的建立连接请求,以及accept 连接,将连接好的套接字放入连接队列;

(2)调度workers空闲线程来负责处理已经建立好的连接的读写等事件。

1 关键数据抽象

(1)memcached单个线程结构的封装

//memcached线程结构的封装结构
 typedef struct {
     pthread_t thread_id;        /* unique ID of this thread */
     struct event_base *base;    /* libevent handle this thread uses */
     struct event notify_event;  /* listen event for notify pipe */
     int notify_receive_fd;      /* receiving end of notify pipe */
     int notify_send_fd;         /* sending end of notify pipe */
     struct thread_stats stats;  /* Stats generated by this thread */
     struct conn_queue *new_conn_queue; /* queue of new connections to handle */
     cache_t *suffix_cache;      /* suffix cache */
 } LIBEVENT_THREAD;

这是memcached里的线程结构的封装,可以看到每个线程都包含一个CQ队列,一条通知管道pipe ,一个libevent的实例event_base等。

(2)线程连接队列

/* A connection queue. */
 typedef struct conn_queue CQ;
 struct conn_queue {
     CQ_ITEM *head;
     CQ_ITEM *tail;
     pthread_mutex_t lock;
     pthread_cond_t  cond;
 };

每个线程结构体中都指向一个CQ链表,CQ链表管理CQ_ITEM的单向链表。

(3)连接项结构体

/* An item in the connection queue. */
 typedef struct conn_queue_item CQ_ITEM;
 struct conn_queue_item {
     int               sfd;
     enum conn_states  init_state;
     int               event_flags;
     int               read_buffer_size;
     enum network_transport     transport;
     CQ_ITEM          *next;
 };

CQ_ITEM实际上是主线程accept后返回的已建立连接的fd的封装,由主线程创建初始化并放入连接链表CQ中,共workers线程使用。

(4)网络连接的封装结构体

/**
  * The structure representing a connection into memcached.
  */
  //memcached表示一个conn的抽象结构
 typedef struct conn conn;
 struct conn {
 ..................   
 };

由于这个结构太大,就略去中间的成员不展示了,与我们线程池相关的有一个成员则非常关键,那就是state,它是memcached中状态机驱动的关键(由drive_machine函数实现)。

2 线程池的初始化:

main()中线程池初始化函数入口为:

/* start up worker threads if MT mode */

thread_init(settings.num_threads, main_base);

函数的定义在thread.c实现,源码如下所示:

/*
  * Initializes the thread subsystem, creating various worker threads.
  *
  * nthreads  Number of worker event handler threads to spawn
  * main_base Event base for main thread
  */
 void thread_init(int nthreads, struct event_base *main_base) {
     int         i;
 
     pthread_mutex_init(&cache_lock, NULL);
     pthread_mutex_init(&stats_lock, NULL);
 
     pthread_mutex_init(&init_lock, NULL);
     pthread_cond_init(&init_cond, NULL);
 
     pthread_mutex_init(&cqi_freelist_lock, NULL);
     cqi_freelist = NULL;
 
     //分配线程池结构数组
     threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
     if (! threads) {
         perror("Can't allocate thread descriptors");
         exit(1);
     }
 
     dispatcher_thread.base = main_base;
     dispatcher_thread.thread_id = pthread_self();
 
     //为线程池每个线程创建读写管道
     for (i = 0; i < nthreads; i++) {
         int fds[2];
         if (pipe(fds)) {
             perror("Can't create notify pipe");
             exit(1);
         }
 
         threads[i].notify_receive_fd = fds[0];
         threads[i].notify_send_fd = fds[1];
 
         //填充线程结构体信息
         setup_thread(&threads[i]);
     }
 
     /* Create threads after we've done all the libevent setup. */
     for (i = 0; i < nthreads; i++) {
         //为线程池创建数目为nthreads的线程,worker_libevent为线程的回调函数,
         create_worker(worker_libevent, &threads[i]);
     }
 
     /* Wait for all the threads to set themselves up before returning. */
     pthread_mutex_lock(&init_lock);
     while (init_count < nthreads) {
         pthread_cond_wait(&init_cond, &init_lock);
     }
     pthread_mutex_unlock(&init_lock);
 }

线程池初始化函数由主线程进行调用,该函数先初始化各互斥锁,然后使用calloc分配nthreads*sizeof(LIBEVENT_THREAD)个字节的内存块来管理线程池,返回一个全局static变量 threads(类型为LIBEVENT_THREAD *);然后为每个线程创建一个匿名管道(该pipe将在线程的调度中发挥作用),接下来的setup_thread函数为线程设置事件监听,绑定CQ链表等初始化信息,源码如下所示:

/*
  * Set up a thread's information.
  */
 static void setup_thread(LIBEVENT_THREAD *me) {
     me->base = event_init();
     if (! me->base) {
         fprintf(stderr, "Can't allocate event base\n");
         exit(1);
     }
 
     /* Listen for notifications from other threads */
     //为管道设置读事件监听,thread_libevent_process为回调函数
     event_set(&me->notify_event, me->notify_receive_fd,
               EV_READ | EV_PERSIST, thread_libevent_process, me);
     event_base_set(me->base, &me->notify_event);
 
     if (event_add(&me->notify_event, 0) == -1) {
         fprintf(stderr, "Can't monitor libevent notify pipe\n");
         exit(1);
     }
 
     //为新线程创建连接CQ链表
     me->new_conn_queue = malloc(sizeof(struct conn_queue));
     if (me->new_conn_queue == NULL) {
         perror("Failed to allocate memory for connection queue");
         exit(EXIT_FAILURE);
     }
     //初始化线程控制器内的CQ链表
     cq_init(me->new_conn_queue);
 
     if (pthread_mutex_init(&me->stats.mutex, NULL) != 0) {
         perror("Failed to initialize mutex");
         exit(EXIT_FAILURE);
     }
     //创建cache
     me->suffix_cache = cache_create("suffix", SUFFIX_SIZE, sizeof(char*),
                                     NULL, NULL);
     if (me->suffix_cache == NULL) {
         fprintf(stderr, "Failed to create suffix cache\n");
         exit(EXIT_FAILURE);
     }
 }

memcached使用libevent实现事件循环,关于libevent,不熟悉的读者可以查看相关资料,这里不做介绍,源码中的这句代码:

event_set(&me->notify_event, me->notify_receive_fd,EV_READ | EV_PERSIST, thread_libevent_process, me);

在me->notify_receive_fd(即匿名管道的读端)设置可读事件,回调函数 为thread_libevent_process,函数定义如下:

static void thread_libevent_process(int fd, short which, void *arg) {
     LIBEVENT_THREAD *me = arg;
     CQ_ITEM *item;
     char buf[1];
 
     //响应pipe可读事件,读取主线程向管道内写的1字节数据(见dispatch_conn_new()函数)
     if (read(fd, buf, 1) != 1)
         if (settings.verbose > 0)
             fprintf(stderr, "Can't read from libevent pipe\n");
 
     //从链接队列中取出一个conn
     item = cq_pop(me->new_conn_queue);
 
     if (NULL != item) {
         //使用conn创建新的任务
         conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
                            item->read_buffer_size, item->transport, me->base);
         if (c == NULL) {
             if (IS_UDP(item->transport)) {
                 fprintf(stderr, "Can't listen for events on UDP socket\n");
                 exit(1);
             } else {
                 if (settings.verbose > 0) {
                     fprintf(stderr, "Can't listen for events on fd %d\n",
                         item->sfd);
                 }
                 close(item->sfd);
             }
         } else {
             c->thread = me;
         }
         cqi_free(item);
     }
 }

使用setup_thread设置线程结构体的初始化信息之后,现在我们回到thread_init函数,thread_init中接着循环调用(循环调用nthreads次)create_worker(worker_libevent, &threads[i]); 创建真正运行的线程,create_worker是对pthread_create()简单的封装,参数worker_libevent作为每个线程的运行体,&threads[i]为传入参数。

worker_libevent为线程体,源码如下:

/*
  * Worker thread: main event loop
  */
 static void *worker_libevent(void *arg) {
     LIBEVENT_THREAD *me = arg;
 
     /* Any per-thread setup can happen here; thread_init() will block until
      * all threads have finished initializing.
      */
     pthread_mutex_lock(&init_lock);
     init_count++;     //每创建新线程,将全局init_count加1
     pthread_cond_signal(&init_cond);  // 发送init_cond信号
     pthread_mutex_unlock(&init_lock);
 
     //新创建线程阻塞于此,等待事件
     event_base_loop(me->base, 0); //Libevent的事件主循环
     return NULL;
 }

worker_libevent中给init_count加1的目的在thread_init函数的这段代码可以看出来,

/* Wait for all the threads to set themselves up before returning. */
     pthread_mutex_lock(&init_lock);
     while (init_count < nthreads) {
         pthread_cond_wait(&init_cond, &init_lock);
     }
     pthread_mutex_unlock(&init_lock);

即主线程阻塞如此,等待worker_libevent发出的init_cond信号,唤醒后检查init_count < nthreads是否为假(即创建的线程数目是否达到要求),否则继续等待。 至此,线程池创建的代码已分析完毕,由于篇幅较长,将分析线程池中线程的调度流程另立一篇。

[转载]OAuth 2.0 - 访问新浪、腾讯的资源服务器

mikel阅读(1332)

[转载]OAuth 2.0 – 访问新浪、腾讯的资源服务器 – dotNetDR_ – 博客园.

I:访问资源服务器需要些什么?

访问资源服务器最最重要的前提条件就是你必须要有Access Token。而关于Access Token的取得原理已经在前面两篇(第1篇第2篇)随笔当中有介绍,在这里就略过介绍Access Token的获取方法了。

首先我们还是先看看新浪、腾讯他们的API文档上提供的参考内容。

新浪资源服务器API调用规范
image
也就是说新浪目前提供两种方式去调用API.
第一种是以QueryString参数的形式去调用,例子:
https://api.weibo.com/2/{API接口}?access_token={yourAccessToken}

第二种是往header里添加Authorization:OAuth2 {yourAccessToken}的形式去调用。

腾讯资源服务器API调用规范
image
根 据腾讯方的文档资料来看!腾讯目前提供的API调用方法跟新浪的第一种方式类似,但没有提供类似新浪第二种把Access Token放到Header里面的方式(或许有但是我未能从文档站上搜索到具体要求)。接着GRD的腾讯又搞了几个属于自己的参数如:openid, clientip, oauth_version,微微地创新了一把!关于OpenID腾讯的介绍是OpenID可以唯一标识一个用户。在同一个应用下,同一个QQ号码的OpenID是相同的;但在不同应用下,同一个QQ号码可能有不同的OpenID。另外楼主可以推断这个oauth_version参数的设计能够证明目前腾讯接口不是很稳定,到时候估计会闹2.b/2.c/2.d/2.e/2.*,或者过一定时间后oauth.net推出3.0版本时,可以通过修改oauth_version去支持。

腾讯的例子:
https://open.t.qq.com /api/{API接口}?oauth_consumer_key={AppKey}&access_token={AccessToken}& amp;openid={Openid}&clientip={ClientIP}&oauth_version=2.a&scope=all

在这说一下,腾讯的参数中客户端ip(clientip)可以不填(博主未确认应用部署上线后是否需要提供,因为应用都在开发期。。。)

ok,关于腾讯跟新浪的资源服务器API调用规范的重要部分已经介绍完毕了。接下来放送新浪腾讯API列表,因为调用他们的API不单单只是提供Access Token还需要根据接口的说明文档区确认是GET还是POST那些参数是可选的,那些参数是必选的之类。

新浪API接口地址腾讯API接口地址

附:腾讯接口Q&A新浪接口Q&A

II:访问资源服务器API示例

首先下载dotNetDR_OAuth2程序集 codeplex下载地址介绍

然后新建一个MVC3的应用程序!将刚刚下载的OAuth2组件引用进来。 (WebForm示例
image

接着在项目代码文件里添加你的AppKey, AppSecret
image

然后再Home控制器的Index Action加上跳转到新浪和腾讯微博授权页面的超级链接。

HomeController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Dynamic;

//引入dotNetDR_OAuth2组件命名空间
using dotNetDR_OAuth2;
using dotNetDR_OAuth2.AccessToken;

namespace dotNetDR_OAuth2.Sample.MVC.Controllers
{
public class HomeController : Controller
{
//获取新浪、腾讯的IAuthorizationCodeBase接口实例
private IAuthorizationCodeBase sina = AccessTokenFactory.Create(DefaultAppConfigs.Sina);
private IAuthorizationCodeBase tencent = AccessTokenFactory.Create(DefaultAppConfigs.Tencent);

public ActionResult Index()
{
dynamic model = new ExpandoObject();

//生成主机头例如:http://www.yourhost.com:8081 (注:默认80端口则不会显示:80)
var hostPath = AccessTokenToolkit.GenerateHostPath(Request.Url);

//定义授权成功后返回的url地址
var sinaRedirectUrl = hostPath + Url.Action("Index", "Sina");
var tencentRedirectUrl = hostPath + Url.Action("Index", "Tencent");

//设置超级链接
model.SinaLink = sina.GenerateCodeUrl(sinaRedirectUrl);
model.TencentLink = tencent.GenerateCodeUrl(tencentRedirectUrl);

return View(model);
}

public ActionResult About()
{
return View();
}
}
}

然后Index.cshtml:

@{
ViewBag.Title = "Home Page";
}
@model dynamic
<h2>dotNetDR_OAuth2 微博API访问组件示例</h2>
<div>@if (Model != null)
{
<a href="@Model.SinaLink"><img src="http://www.cnblogs.com/Content/Images/xlwb.gif" alt="" />新浪微博登陆</a>|
<a href="@Model.TencentLink"><img src="http://www.cnblogs.com/Content/Images/txwb.gif" alt="" />腾讯微博登陆</a>
}
else
{
<h3>Error: Model没有值</h3>
}

</div>

image

上图是效果图

接着我们建立各自的实现:SinaController, TencentController.

新浪部分 – SinaController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
//导入组件命名空间
using dotNetDR_OAuth2;
using dotNetDR_OAuth2.AccessToken;
using dotNetDR_OAuth2.APIs.Providers.Sina;

namespace dotNetDR_OAuth2.Sample.MVC.Controllers
{
public class SinaController : Controller
{
private IAuthorizationCodeBase _authCode = AccessTokenFactory.Create(DefaultAppConfigs.Sina);

public ActionResult Index(string code)
{
if (Session["accessToken"] == null)
{
if (!string.IsNullOrEmpty(code))
{
var redirectUrl = AccessTokenToolkit.GenerateHostPath(Request.Url) + Url.Action("Index");

var accessToken = _authCode.GetResult(_authCode.GenerateAccessTokenUrl(redirectUrl, code));

if (Session["accessToken"] != null)
{
Session.Remove("accessToken");
}

Session.Add("accessToken", accessToken);

var hasAccessToken = new object();
return View(hasAccessToken);
}
else
{
return GotoIndex();
}
}

return View(new object());
}

public ActionResult ShowUserInfo()
{
if (Session["accessToken"] == null)
{
return GotoIndex();
}

var accessTokenObj = Session["accessToken"] as dynamic;
var uid = accessTokenObj.uid;
var accessToken = accessTokenObj.access_token;

var model = SinaApi.CallGet("users/show.json?uid=" + uid, accessToken);

SinaError err;

if (!SinaApi.HasError(model, out err))
{
return View(model);
}
else
{
Session["err"] = err;
return RedirectToAction("Error");
}
}

public ActionResult PublishMsg()
{
if (Session["accessToken"] == null)
{
return GotoIndex();
}

var accessTokenObj = Session["accessToken"] as dynamic;
var uid = accessTokenObj.uid;
var accessToken = accessTokenObj.access_token;

var msg = "Time: " + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fffff") + ": 这是一条来自dotNetDR_OAuth2组件发出的1条测试微博信息!";
var formData = new Dictionary();
formData.Add("status", Server.UrlEncode(msg));

SinaError err;

var result = SinaApi.CallPost("statuses/update.json", accessToken, formData);
if (!SinaApi.HasError(result, out err))
{
return View();
}
else
{
Session["err"] = err;
return RedirectToAction("Error");
}
}

public ActionResult Error()
{
var err = Session["err"] as SinaError;

return View(err);
}

#region NonAction
[NonAction]
private ActionResult GotoIndex()
{
return RedirectToAction("Index", "Home");
}
#endregion
}
}

Index.cshtml:

@{
ViewBag.Title = "Index";
}

@model object
<h2>操作</h2>
@Html.ActionLink("返回", "Index", "Home")

@if (Model != null)
{
@Html.ActionLink("显示用户信息", "ShowUserInfo") |
@Html.ActionLink("发送测试微博", "PublishMsg")
}

PublishMsg.cshtml:

@{
ViewBag.Title = "PublishMsg";
}
<h2>发送成功</h2>

ShowUserInfo.cshtml:

@{
ViewBag.Title = "ShowUserInfo";
}
<h2>用户:@Model.screen_name</h2>
用户UID: @Model.id

用户昵称: @Model.screen_name

友好显示名称: @Model.name

用户所在地区ID: @Model.province

用户所在城市ID: @Model.city

用户所在地: @Model.location

用户描述: @Model.description

用户博客地址: @Model.url

用户头像地址: @Model.profile_image_url @MvcHtmlString.Create(string.Format("<img src="{0}" alt="" />", Model.profile_image_url))

用户的个性化域名: @Model.domain

性别(m:男、f:女、n:未知): @Model.gender

粉丝数: @Model.followers_count

关注数: @Model.friends_count

微博数: @Model.statuses_count

收藏数: @Model.favourites_count

创建时间: @Model.created_at

当前登录用户是否已关注该用户: @Model.following

是否允许所有人给我发私信: @Model.allow_all_act_msg

是否允许带有地理信息: @Model.geo_enabled

是否是微博认证用户,即带V用户: @Model.verified

是否允许所有人对我的微博进行评论: @Model.allow_all_comment

用户大头像地址: @Model.avatar_large @MvcHtmlString.Create(string.Format("<img src="{0}" alt="" />", @Model.avatar_large))

认证原因: @Model.verified_reason

该用户是否关注当前登录用户: @Model.follow_me

用户的在线状态,0:不在线、1:在线: @Model.online_status

用户的互粉数: @Model.bi_followers_count

Error.cshtml:

@{
ViewBag.Title = "Error";
}

@model dotNetDR_OAuth2.APIs.Providers.Sina.SinaError
<h2>Error</h2>
error_code: @Model.error_code

error: @Model.error

request: @Model.request

腾讯部分 – TencentController.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

using dotNetDR_OAuth2;
using dotNetDR_OAuth2.AccessToken;
using dotNetDR_OAuth2.APIs.Providers.Tencent;

namespace dotNetDR_OAuth2.Sample.MVC.Controllers
{
public class TencentController : Controller
{
private IAuthorizationCodeBase _authCode = AccessTokenFactory.Create(DefaultAppConfigs.Tencent);

//
// GET: /Tencent/
public ActionResult Index(string code, string openid, string openkey)
{
if (Session["accessToken"] == null)
{
if (!string.IsNullOrEmpty(code))
{
var redirectUrl = AccessTokenToolkit.GenerateHostPath(Request.Url) + Url.Action("Index");

var accessToken = _authCode.GetResult(_authCode.GenerateAccessTokenUrl(redirectUrl, code));

if (Session["accessToken"] != null)
{
Session.Remove("accessToken");
}

accessToken.openid = openid; //注意GRD腾讯自家的微创新
accessToken.openkey = openkey; //注意GRD腾讯自家的微创新

Session.Add("accessToken", accessToken);

var hasAccessToken = new object();
return View(hasAccessToken);
}
else
{
return GotoIndex();
}
}

return View(new object());
}

public ActionResult ShowUserInfo()
{
if (Session["accessToken"] == null)
{
return GotoIndex();
}

var accessTokenObj = Session["accessToken"] as dynamic;
var uid = accessTokenObj.name;
var accessToken = accessTokenObj.access_token;
var openid = accessTokenObj.openid;

var model = TencentApi.CallGet("user/info?format=json", accessToken, openid);

TencentError err;

if (!TencentApi.HasError(model, out err))
{
var realModel = model.data;
return View(realModel);
}
else
{
Session["err"] = err;
return RedirectToAction("Error");
}
}

public ActionResult PublishMsg()
{
if (Session["accessToken"] == null)
{
return GotoIndex();
}

var accessTokenObj = Session["accessToken"] as dynamic;
var uid = accessTokenObj.name;
var accessToken = accessTokenObj.access_token;
var openid = accessTokenObj.openid;

var msg = "Time: " + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss.fffff") + ": 这是一条来自dotNetDR_OAuth2组件发出的1条测试微博信息!";
var formData = new Dictionary();
formData.Add("content", Server.UrlEncode(msg));

TencentError err;

var result = TencentApi.CallPost("t/add?format=json", accessToken, openid, formData);
if (!TencentApi.HasError(result, out err))
{
return View();
}
else
{
Session["err"] = err;
return RedirectToAction("Error");
}
}

public ActionResult Error()
{
var err = Session["err"] as TencentError;

return View(err);
}

#region NonAction
[NonAction]
private ActionResult GotoIndex()
{
return RedirectToAction("Index", "Home");
}
#endregion
}
}

Index.cshtml:

@{
ViewBag.Title = "Index";
}
@model object
<h2>操作</h2>
@Html.ActionLink("返回", "Index", "Home")

@if (Model != null)
{
@Html.ActionLink("显示用户信息", "ShowUserInfo") |
@Html.ActionLink("发送测试微博", "PublishMsg")
}

PublishMsg.cshtml:

@{
ViewBag.Title = "PublishMsg";
}
<h2>发送成功</h2>

ShowUserInfo.cshtml:

@{
ViewBag.Title = "ShowUserInfo";
}
<h2>用户昵称: @Model.nick</h2>
出生天: @Model.birth_day

出生月: @Model.birth_month

出生年: @Model.birth_year

城市id: @Model.city_code

国家id: @Model.country_code

邮箱: @Model.email

听众数: @Model.fansnum

收藏数: @Model.favnum

头像url: @Model.head @MvcHtmlString.Create(string.Format("<img src="\&quot;{0}/50\&quot;" alt="" />", @Model.head))

家乡所在城市id: @Model.homecity_code

家乡所在国家id: @Model.homecountry_code

个人主页: @Model.homepage

家乡所在省id: @Model.homeprovince_code

家乡所在城镇id: @Model.hometown_code

收听的人数: @Model.idolnum

行业id: @Model.industry_code

个人介绍: @Model.introduction

是否企业机构: @Model.isent

是否在当前用户的黑名单中,0-不是,1-是: @Model.ismyblack

是否是当前用户的听众,0-不是,1-是: @Model.ismyfans

是否是当前用户的偶像,0-不是,1-是: @Model.ismyidol

是否实名认证,0-老用户,1-已实名认证,2-未实名认证: @Model.isrealname

是否认证用户: @Model.isvip

所在地: @Model.location

互听好友数: @Model.mutual_fans_num

用户帐户名: @Model.name

用户唯一id,与name相对应: @Model.openid

地区id: @Model.province_code

注册时间: @Model.regtime

是否允许所有人给当前用户发私信,0-仅有偶像,1-名人+听众,2-所有人: @Model.send_private_flag

用户性别,1-男,2-女,0-未填写: @Model.sex

发表的微博数: @Model.tweetnum

认证信息: @Model.verifyinfo

Error.cshtml:

@{
ViewBag.Title = "Error";
}
@model dotNetDR_OAuth2.APIs.Providers.Tencent.TencentError
<h2>Error</h2>
ret: @Model.ret

errcode: @Model.errcode

msg: @Model.msg

----------------------

errcode=1 无效TOKEN,被吊销

errcode=2 请求重放

errcode=3 access_token不存在

errcode=4 access_token超时

errcode=5 oauth 版本不对

errcode=6 oauth 签名方法不对

errcode=7 参数错

errcode=8 处理失败

errcode=9 验证签名失败

errcode=10 网络错误

errcode=11 参数长度不对

errcode=12 处理失败

errcode=13 处理失败

errcode=14 处理失败

errcode=15 处理失败

在这里重复上一下效果图吧!

image

image

发送微博的效果我就不贴了!!

这里附上一个各位OAuth开发者或许需要的流程图(专家请尽情喷小菜)

image

visio文件下载

III:dotNetDR_OAuth2 组件介绍

在上一节里面的代码!大家都可以看到这个组件已经隐藏了System.Net.HttpWebRequest, System.Net.HttpWebResponse这些细节,而且返回的值都是dynamic类型的,这样一下。我们就仅需要对这新浪或者腾讯的 API文档来逐步调试了,因为博主不可能在组件里把每一个接口返回值得都定义成一个C#类文件:



如果都定义成claas我太累了,所以用.NET 4.0 提供的dynamic算了,更加具体内容我打算另外用一遍随笔去介绍!!转载的请声明及保留好出处!!

组件作者:博客园dotNetDR_ http://www.cnblogs.com/highend/

IV:Access Token的过期时间

新浪

image

腾讯

image

V:示例项目代码

注意:当你在测试环境下时,必须要把windows系统的hosts文件添加好具体的域名地址指向你本机,例如博主的:

image

然后就是需要打开dotNetDR_OAuth2.Sample.MVC.csproj手动更改IIS路径

image

代码下载

广告:ASP.NET MVC 3.0 QQ交流群:33353329 (可用于讨论当前的OAuth 2.0) 询问C#以外的OAuth 2.0 sdk同学勿入!谢谢合作。

[转载]Android中接入微信客户端心得

mikel阅读(1204)

[转载][原]Android中接入微信客户端心得 – 雪夜&流星 – 博客园.

最近因为工作的项目中要接入微信开放平台,于是一直在做这方面的工作,再加上比较忙,所以只有现在才有机会来记录一下关于程序中接入微信开放平台的事情。

目前Android版只支持SDK接入微信,朋友圈分享功能只有ios支持,相信不久就会有Android版本的支持的。好了,废话不多说,进入话题,详情请移步http://open.weixin.qq.com

关于程序接入微信客户端,只需要以下四步:

**********华丽的分割线**********

特别要注意签名,一定要和你的APP签名保持一致,否则在你分享的过程中弹不出选择联系人的界面。

*****************************

第一步:申请APP_ID,请移步http://open.weixin.qq.com/app/list/.在这里需要注意的是Android应用除了获取AppID外,还需要在网站上填写包名和签名两个字段,签名的具体生成方法如下:

    这里以windows平台的cygwin环境和命令行环境为例,且默认开发者已经安装了jdk,能够正常使用keytool

    1、cygwin环境下

cygwin环境下运行如下命令,输出结果即为signature

keytool -exportcert -alias [alias] -keypass [alias password] -keystore [keystore file path] -storepass [keystore password] | md5sum

例如:当前路径下包含用于对app签名的test.keystore文件,且keystore密码为123456,别名为openapi,别名密码为654321,则运行如下命令:

keytool -exportcert -alias openapi -keypass 654321 -keystore ./test.keystore -storepass 123456 | md5sum

输出结果为:8f88de9693d22430ad7ce55047ec7946

  

    2、命令行环境下

由于命令行下系统没有提供默认的md5sum,建议开发者自行选择合适的md5生成工具(http://sourceforge.net/directory/os:windows/freshness:recently-updated/?q=md5 ),生成方法跟cygwin环境下类似,可以先将keytool的输出重定向到文件,然后再用md5生成工具对该文件进行md5处理,获得signature,结果跟cygwin环境下相同。

keytool -exportcert -alias openapi -keypass 654321 -keystore ./test.keystore -storepass 123456 > out.txt

二、下载微信终端开发工具包

三、在代码中使用开发工具包(以上两步详情请移步:http://open.weixin.qq.com/document/gettingstart/android/);

这里主要说明的是本人碰到的几个问题:

1、在接入微信客户端的过程中一定要注意:首先判断是否安装有微信(使用IWXAPI类的isWXAppInstalled()方法),否则你会发现程序是正确的,但是无论如何都不会得到想要的结果,也不会报任何错误。

2、在接入微信客户端的过程中一定要注意:当前的微信版本是否是4.0(使用IWXAPI类的isWXAppSupporAPI()方法),后果如上。

3、在接入微信客户端的过程中一定要注意:分享图片的时候,SDK协议中对缩略图的大小作了限制,大小不能超过32K。另外限制的还有title、description等参数的大小,否则后果如上。

4、如果你的程序需要接收微信发送的请求,或者接收发送到微信请求的响应结果,需要下面3步操作:

        a、在你的包名相应目录下新建一个wxapi目录,并在该wxapi目录下新增一个WXEntryActivity类,该类继承自Activity,并在manifest文件里面加上exported属性,设置为true。

b、 实现IWXAPIEventHandler接口,微信发送的请求将回调到onReq方法,发送到微信请求的响应结果将回调到onResp方法

c、在WXEntryActivity中将接收到的intent及实现了IWXAPIEventHandler接口的对象传递给IWXAPI接口的handleIntent方法。

d、当微信发送请求到你的应用,将通过IWXAPIEventHandler接口的onReq方法进行回调,类似的,应用请求微信的响应结果将通过onResp回调。

最后应该注意:微信开放平台分享图片Url是一个bug,貌似是分享不了的,现在不知道这个bug修复了没有。

[转载]HTMLParser1.6 源代码阅读

mikel阅读(1069)

[转载]HTMLParser1.6 源代码阅读 – S面B面 – 博客园.

看到博客园的大牛们都喜欢发系列的文章,我也发一篇。不过我不打算写什么spring hibernate配置什么的,我只想写写自己阅读别人代码的一些笔记。欢迎大家拍砖。

 

从开始进行阅读,第一个包是:org.htmlparser.里面的类包括

Attribute.java

Node.java

NodeFactory.java

NodeFilter.java

Parser.java

PrototypicalNodeFactory.java

Remark.java

Tag.java

Text.java

可以看出都是针对基本数据结构的类。

一个一个进行分析,Attribute.java是记录网页元素的属性的,

                                <a href="EditPosts.aspx" id="TabPosts">随笔</a>

这个是博客园的,那Attribute应当可以记录 href=“MySubscibe.aspx”这样的元素。看他的构造方法:

public Attribute (String name, String assignment, String value, char quote)
    {
        setName (name);
        setAssignment (assignment);
        if (0 == quote)
            setRawValue (value);
        else
        {
            setValue (value);
            setQuote (quote);
        }
    }

看见分为是否含有引号的情况。setName,和setValue没有什么可以看的,如果不含引号的情况,value怎么设置

/**
     * Set the value of the attribute and the quote character.
     * If the value is pure whitespace, assign it 'as is' and reset the
     * quote character. If not, check for leading and trailing double or
     * single quotes, and if found use this as the quote character and
     * the inner contents of <code>value</code> as the real value.
     * Otherwise, examine the string to determine if quotes are needed
     * and an appropriate quote character if so. This may involve changing
     * double quotes within the string to character references.
     * @param value The new value.
     * @see #getRawValue
     * @see #getRawValue(StringBuffer)
     */
    public void setRawValue (String value)
    {
        char ch;
        boolean needed;
        boolean singleq;
        boolean doubleq;
        String ref;
        StringBuffer buffer;
        char quote;

        quote = 0;
        if ((null != value) && (0 != value.trim ().length ()))
        {
            if (value.startsWith ("'") && value.endsWith ("'")
                && (2 <= value.length ()))
            {
                quote = '\'';
                value = value.substring (1, value.length () - 1);
            }
            else if (value.startsWith ("\"") && value.endsWith ("\"")
                && (2 <= value.length ()))
            {
                quote = '"';
                value = value.substring (1, value.length () - 1);
            }
            else
            {
                // first determine if there's whitespace in the value
                // and while we're at it find a suitable quote character
                needed = false;
                singleq = true;
                doubleq = true;
                for (int i = 0; i < value.length (); i++)
                {
                    ch = value.charAt (i);
                    if ('\'' == ch)
                    {
                        singleq  = false;
                        needed = true;
                    }
                    else if ('"' == ch)
                    {
                        doubleq = false;
                        needed = true;
                    }
                    else if (!('-' == ch) && !('.' == ch) && !('_' == ch)
                       && !(':' == ch) && !Character.isLetterOrDigit (ch))
                    {
                        needed = true;
                    }
                }

                // now apply quoting
                if (needed)
                {
                    if (doubleq)
                        quote = '"';
                    else if (singleq)
                        quote = '\'';
                    else
                    {
                        // uh-oh, we need to convert some quotes into character
                        // references, so convert all double quotes into &#34;
                        quote = '"';
                        ref = "&quot;"; // Translate.encode (quote);
                        // JDK 1.4: value = value.replaceAll ("\"", ref);
                        buffer = new StringBuffer (
                                value.length() * (ref.length () - 1));
                        for (int i = 0; i < value.length (); i++)
                        {
                            ch = value.charAt (i);
                            if (quote == ch)
                                buffer.append (ref);
                            else
                                buffer.append (ch);
                        }
                        value = buffer.toString ();
                    }
                }
            }
        }
        setValue (value);
        setQuote (quote);
    }

如果没有设置分割字符的话,需要进行判断,首先判断value的字符是哪种?单引号,双引号,还是其他。如果是单引号开头,单引号结尾,那么分割是单引号。如果是双引号,就是双引号。如果不是这样,可能需要修复,如果里面有单引号,那么我们用双引号进行包装,里面含有双引号,我们用单引号进行包装。或者其中含有一些特别的字符(不是数字,字符,-,_,:),我们需要用引号引用起来。这样属性就可以保存下来。

属性暂时分析到这里,有兴趣的可以自己阅读剩下的部分。

Node.java

其实htmlParser就是一个词法语法分析器,学过自动机的同学应当对此很熟悉,(ps,本人自动机挂掉了。。)而HTML的元素有三种类型,text,Tag,Remark(remark是不是这种,这个不确定)。 我们进行语法解析的时候肯定要返回相应的node,那这个node应当设计成抽象类或者接口,的确,也是这样设计的。看代码

package org.htmlparser;

import org.htmlparser.lexer.Page;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.visitors.NodeVisitor;

public interface Node
    extends
        Cloneable
{
    
    String toPlainTextString ();  
    String toHtml ();
    String toHtml (boolean verbatim);
    String toString ();    
    void collectInto (NodeList list, NodeFilter filter);   
    int getStartPosition ();    
    void setStartPosition (int position);   
    int getEndPosition ();   
    void setEndPosition (int position);   
    Page getPage ();
    void setPage (Page page); 
    void accept (NodeVisitor visitor);   
    Node getParent ();
    void setParent (Node node);
    NodeList getChildren ();  
    void setChildren (NodeList children);
    Node getFirstChild ();
    Node getLastChild (); 
    Node getPreviousSibling ();      
    Node getNextSibling ();     
    String getText ();  
    void setText (String text); 
    void doSemanticAction ()
        throws
            ParserException;  
    Object clone ()
        throws
            CloneNotSupportedException;
}

这里光看这个也没办法领略精髓,大致就是一个开始克隆,可以转换成html元素,转换成string的类型,并且可以迭代的一系列方法。不过下一步我们应当从lexer中寻找相关答案,保持我们的阅读顺序,我们继续进行下一个java分析

NodeFactory

先看代码

package org.htmlparser;

import java.util.Vector;

import org.htmlparser.lexer.Page;
import org.htmlparser.util.ParserException;

public interface NodeFactory
{
   
    Text createStringNode (Page page, int start, int end)
        throws
            ParserException;
 
    Remark createRemarkNode (Page page, int start, int end)
        throws
            ParserException;
   
    Tag createTagNode (Page page, int start, int end, Vector attributes)
        throws
            ParserException;
}

就是创建上面三种HTML页面的基本元素。

NodeFilter

package org.htmlparser;

import java.io.Serializable;

/**
 * Implement this interface to select particular nodes.
 */
public interface NodeFilter
    extends
        Serializable,
        Cloneable
{

    boolean accept (Node node);
}

进行一个Node的合理性验证

Parser

终于看到重点了

package org.htmlparser;

import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URLConnection;

import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.filters.NodeClassFilter;
import org.htmlparser.http.ConnectionManager;
import org.htmlparser.http.ConnectionMonitor;
import org.htmlparser.http.HttpHeader;
import org.htmlparser.lexer.Lexer;
import org.htmlparser.lexer.Page;
import org.htmlparser.util.DefaultParserFeedback;
import org.htmlparser.util.IteratorImpl;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.htmlparser.util.ParserFeedback;
import org.htmlparser.visitors.NodeVisitor;


public class Parser
    implements
        Serializable,
        ConnectionMonitor
{
   
    public static final double
    VERSION_NUMBER = 1.6
    ;

   
    public static final String
    VERSION_TYPE = "Release Build"
    ;

   
    public static final String
    VERSION_DATE = "Jun 10, 2006"
    ;

   
    public static final String VERSION_STRING =
            "" + VERSION_NUMBER
            + " (" + VERSION_TYPE + " " + VERSION_DATE + ")";

  
    protected ParserFeedback mFeedback;

    protected Lexer mLexer;

    public static final ParserFeedback DEVNULL =
        new DefaultParserFeedback (DefaultParserFeedback.QUIET);

    public static final ParserFeedback STDOUT = new DefaultParserFeedback ();

    static
    {
        getConnectionManager ().getDefaultRequestProperties ().put (
            "User-Agent", "HTMLParser/" + getVersionNumber ());
    
    }


    public static String getVersion ()
    {
        return (VERSION_STRING);
    }

    public static double getVersionNumber ()
    {
        return (VERSION_NUMBER);
    }

    public static ConnectionManager getConnectionManager ()
    {
        return (Page.getConnectionManager ());
    }

    public static void setConnectionManager (ConnectionManager manager)
    {
        Page.setConnectionManager (manager);
    }

    public static Parser createParser (String html, String charset)
    {
        Parser ret;

        if (null == html)
            throw new IllegalArgumentException ("html cannot be null");
        ret = new Parser (new Lexer (new Page (html, charset)));

        return (ret);
    }


    public Parser ()
    {
        this (new Lexer (new Page ("")), DEVNULL);
    }

    public Parser (Lexer lexer, ParserFeedback fb)
    {
        setFeedback (fb);
        setLexer (lexer);
        setNodeFactory (new PrototypicalNodeFactory ());
    }

    public Parser (URLConnection connection, ParserFeedback fb)
        throws
            ParserException
    {
        this (new Lexer (connection), fb);
    }

    public Parser (String resource, ParserFeedback feedback)
        throws
            ParserException
    {
        setFeedback (feedback);
        setResource (resource);
        setNodeFactory (new PrototypicalNodeFactory ());
    }

    public Parser (String resource) throws ParserException
    {
        this (resource, STDOUT);
    }

    public Parser (Lexer lexer)
    {
        this (lexer, STDOUT);
    }

    public Parser (URLConnection connection) throws ParserException
    {
        this (connection, STDOUT);
    }

    public void setResource (String resource)
        throws
            ParserException
    {
        int length;
        boolean html;
        char ch;

        if (null == resource)
            throw new IllegalArgumentException ("resource cannot be null");
        length = resource.length ();
        html = false;
        for (int i = 0; i < length; i++)
        {
            ch = resource.charAt (i);
            if (!Character.isWhitespace (ch))
            {
                if ('<' == ch)
                    html = true;
                break;
            }
        }
        if (html)
            setLexer (new Lexer (new Page (resource)));
        else
            setLexer (new Lexer (getConnectionManager ().openConnection (resource)));
    }

    public void setConnection (URLConnection connection)
        throws
            ParserException
    {
        if (null == connection)
            throw new IllegalArgumentException ("connection cannot be null");
        setLexer (new Lexer (connection));
    }

    public URLConnection getConnection ()
    {
        return (getLexer ().getPage ().getConnection ());
    }


    public void setURL (String url)
        throws
            ParserException
    {
        if ((null != url) && !"".equals (url))
            setConnection (getConnectionManager ().openConnection (url));
    }

    public String getURL ()
    {
        return (getLexer ().getPage ().getUrl ());
    }

    public void setEncoding (String encoding)
        throws
            ParserException
    {
        getLexer ().getPage ().setEncoding (encoding);
    }

    public String getEncoding ()
    {
        return (getLexer ().getPage ().getEncoding ());
    }


    public void setLexer (Lexer lexer)
    {
        NodeFactory factory;
        String type;

        if (null == lexer)
            throw new IllegalArgumentException ("lexer cannot be null");
        // move a node factory that's been set to the new lexer
        factory = null;
        if (null != getLexer ())
            factory = getLexer ().getNodeFactory ();
        if (null != factory)
            lexer.setNodeFactory (factory);
        mLexer = lexer;
        // warn about content that's not likely text
        type = mLexer.getPage ().getContentType ();
        if (type != null && !type.startsWith ("text"))
            getFeedback ().warning (
                "URL "
                + mLexer.getPage ().getUrl ()
                + " does not contain text");
    }

    public Lexer getLexer ()
    {
        return (mLexer);
    }

    public NodeFactory getNodeFactory ()
    {
        return (getLexer ().getNodeFactory ());
    }

    public void setNodeFactory (NodeFactory factory)
    {
        if (null == factory)
            throw new IllegalArgumentException ("node factory cannot be null");
        getLexer ().setNodeFactory (factory);
    }

 
    public void setFeedback (ParserFeedback fb)
    {
        if (null == fb)
            mFeedback = DEVNULL;
        else
            mFeedback = fb;
    }

    public ParserFeedback getFeedback()
    {
        return (mFeedback);
    }

    public void reset ()
    {
        getLexer ().reset ();
    }

 
    public NodeIterator elements () throws ParserException
    {
        return (new IteratorImpl (getLexer (), getFeedback ()));
    }
   
    public NodeList parse (NodeFilter filter) throws ParserException
    {
        NodeIterator e;
        Node node;
        NodeList ret;

        ret = new NodeList ();
        for (e = elements (); e.hasMoreNodes (); )
        {
            node = e.nextNode ();
            if (null != filter)
                node.collectInto (ret, filter);
            else
                ret.add (node);
        }

        return (ret);
    }

    public void visitAllNodesWith (NodeVisitor visitor) throws ParserException
    {
        Node node;
        visitor.beginParsing();
        for (NodeIterator e = elements(); e.hasMoreNodes(); )
        {
            node = e.nextNode();
            node.accept(visitor);
        }
        visitor.finishedParsing();
    }

    public void setInputHTML (String inputHTML)
        throws
            ParserException
    {
        if (null == inputHTML)
            throw new IllegalArgumentException ("html cannot be null");
        if (!"".equals (inputHTML))
            setLexer (new Lexer (new Page (inputHTML)));
    }

    public NodeList extractAllNodesThatMatch (NodeFilter filter)
        throws
            ParserException
    {
        NodeIterator e;
        NodeList ret;

        ret = new NodeList ();
        for (e = elements (); e.hasMoreNodes (); )
            e.nextNode ().collectInto (ret, filter);

        return (ret);
    }


    public void preConnect (HttpURLConnection connection)
        throws
            ParserException
    {
        getFeedback ().info (HttpHeader.getRequestHeader (connection));
    }


    public void postConnect (HttpURLConnection connection)
        throws
            ParserException
    {
        getFeedback ().info (HttpHeader.getResponseHeader (connection));
    }

    public static void main (String [] args)
    {
        Parser parser;
        NodeFilter filter;

        if (args.length < 1 || args[0].equals ("-help"))
        {
            System.out.println ("HTML Parser v" + getVersion () + "\n");
            System.out.println ();
            System.out.println ("Syntax : java -jar htmlparser.jar"
                    + " <file/page> [type]");
            System.out.println ("   <file/page> the URL or file to be parsed");
            System.out.println ("   type the node type, for example:");
            System.out.println ("     A - Show only the link tags");
            System.out.println ("     IMG - Show only the image tags");
            System.out.println ("     TITLE - Show only the title tag");
            System.out.println ();
            System.out.println ("Example : java -jar htmlparser.jar"
                    + " http://www.yahoo.com");
            System.out.println ();
        }
        else
            try
            {
                parser = new Parser ();
                if (1 < args.length)
                    filter = new TagNameFilter (args[1]);
                else
                {
                    filter = null;
                    // for a simple dump, use more verbose settings
                    parser.setFeedback (Parser.STDOUT);
                    getConnectionManager ().setMonitor (parser);
                }
                getConnectionManager ().setRedirectionProcessingEnabled (true);
                getConnectionManager ().setCookieProcessingEnabled (true);
                parser.setResource (args[0]);
                System.out.println (parser.parse (filter));
            }
            catch (ParserException e)
            {
                e.printStackTrace ();
            }
    }
}

首先我们要看看构造方法:通常,我们使用HTMLParser的时候是这样new的 Parser a = Parser.createParser(content,”UTF-8″);而这个方法就是通过new Parser(New Lexer(new Page(html,charset)));方法创建一个Parser,也就是核心是Page对象,Lexer对象和Parser对象。

看其他的构造方法,空构造方法,我们略去。

public Parser(Lexer lexer,ParserFeedback fb),里面无非就是设置词法解析器,设置异常信息接收器,和设置一个nodeFactory。

public Parser (URLConnection connection, ParserFeedback fb)网页
public Parser (String resource, ParserFeedback feedback) 对内容进行解析

public Parser (String resource)

public Parser (Lexer lexer)

public Parser (URLConnection connection)

无非就是用默认的fd,之后会对FeedBack类进行相关说明。

到这里,parser完成的无非就是对给定的url,或者content,或者urlconnection对象,创建相应的词法分析器,feedback,和根据条件创建相应的Page对象。同时,我们看到Nodefactory实际上的设置是在Lexer中进行的。这里先不管。

这里有几个我们经常用的方法

public NodeList parse (NodeFilter filter) throws ParserException 根据过滤条件返回相应的nodelist

public void visitAllNodesWith (NodeVisitor visitor) throws ParserException 运用迭代器的方式进行遍历,这里涉及了NodeVisitor,IteratorImpl 这里还没有看。暂时不知道为什么这样设计。

public void setInputHTML (String inputHTML) 可以对html元素进行解析

public NodeList extractAllNodesThatMatch (NodeFilter filter) 根据过滤条件返回满足条件的NodeList

最后是测试方法。parser类读完了,其实parser类就是一个大的入口,将与页面相关的信息传递给parser类,parser调用其他类对其进行解析,返回nodelist。nodelist里面有若干node,而实例化的node里面存有我们相用的各种信息,到这里没有看到词法分析器的影子。。

PrototypicalNodeFactory

对Text,Remark,Tag进行标准化的约束。这个类暂时不做过多介绍,等用到的时候进行相关解释。

Remark

Text

Tag 这三个接口类,实现node,为具体的Node提供接口。

至此,org.htmlParser 阅读完毕,看见还是比较简单的。为什么我要阅读这个代码,一时项目中用到了这个HTMLParser,因为不放心,想知道内部是如何实现的。二是,我自动机以前挂掉过,我想自己实现一个Parser。三是,我的时间和充裕。

下一章节,我们将介绍另一个核心包:org.htmlparser.lexer 。同时,我们要对html页面标准进行学习,不然如何自己实现一个Parser呢?

[转载]MVC已死,该是用MOVE的时候了

mikel阅读(983)

[转载]MVC已死,该是用MOVE的时候了 – Ron Ngai – 博客园.

MVC已死,该是用MOVE的时候了

//可以略过部分
文章原文来自Conrad Irwin的MVC is dead, it’s time to MOVE on.”。可能存在不准确翻译,推荐阅读“MVC模式已死?何不试试MOVE”。这个学期刚学了软件体系结构,刚刚考完试,我才开始认真对待这一课程。学习不能为了考试,特别是读工科的。真正想学习也不能指望课堂。自学还是蛮重要的。本篇博客有以下几个目的:
  1. 学习新技术,希望与博友共勉之
  2. 提升总结和翻译
  3. 记录自己学习的过程
//可以略过部分

 

MVC模式是一种不一般的设想。MVC模式包含有封装业务逻辑和数据处理的数据模型层(Models),显示用户界面的视图层(Views)和控制和连接模型层和视图层的控制层(Controllers)。
什么?
Conrad Irwin很肯定他不是第一个发现下面这一点的。当你不清楚在那里写代码的时候,MVC会带来让你将过多的代码写在控制层(Controllers)的问题。
为了解决这个问题,我采用一种新的模式:MOVE。模型层(Models),操作层(Operations),视图层(Views)和事件层(Events)。

概念

MOVE模式图示

图片显示了MOVE模式的基本结构,下面是对每个层的解释:

  • 模型层(Models)封装应用程序所知道的一切。
  • 操作层(Operations)封装应用程序所作的一切。
  • 视图层(Views)完成用户与应用程序的交互。
  • 事件层(Events)被用于安全地连接组件。

模型层(Models)

创建一个原型模型即一个“user”对象。它至少有一个用户名(email),或许还有一个名字(name)和电话号码(number)。

在一个MOVE模型应用程序中,模型层(Models)只用于包装知识。意思是,它包含让 你验证“这是否是用户密码?”的函数来让获取(getters)和设置(setters)属性值。但是它不包含让你保存它们到数据库或者上传到一个外部 API的函数。这是操作层(Operations)的事情。

操作层(Operations)

一个基本的操作例子就是让用户登录。这分两个字操作来完整。第一,获取用户的用户名(email)和密码(password)。第二,加载调用从数据库查询出数据而设置好的的“user”模型,验证密码是否正确。

操作层(Operations)是MOVE模型世界的执行者。它的职责是设置的模型层 (Models),在正确的时间调用显示正确的视图层(Views)以及相应用户触发引起的事件层(Events)。在一个好的应用程序中,每一个子操作 都可以在父操作下独立运行。这也是为什么图表中事件层中流往上走和改变往下走。

用操作层(Operations)这种方式让人惊讶之处在于,当程序重启开始的时候,你的整个应用程序可以视为是一个操作(Operation)。根据需要被分为多个子操作。同时,每一个字操作可以并行存在运行。另外,当所有字操作运行完成时,程序退出。

视图层(Views)

登录界面是一个显示若干文本框给用户的一个视图(View)。当用户点击“Login”按钮,视图(View)会产生一个包含用户输入用户名和密码的“loginAttempt”的事件(event)。
用 户能看到和能交互的所有事情应该被建设为一个视图(view)。它们不但不显示应用程序在不明方式下的状态,而且将用户产生的交互简化为有意义的事件 (Events)。重要的是,视图(views)不会直接改变模型(models),它们简单地触发事件到操作,然后等待由模型触发事件所引起的改变。

事件层(Events)

事件“loginAttempt”是由于用户点击登录的视图触发的。另外,当登录操作完 成,“currentUser”模型会触发事件,将模型引起的改变通知应用程序。监听事件是让MOVE模式(和MVC模式)的一种逆控制。这种控制是在模 型没有直接意识到视图在更新的时候,你允许模型更新视图。这是一种高度抽象的技术。这种技术允许组件相互存在而又相互不影响。

为什么该是时候了?

Conrad Irwin不希望被误解成这表示MVC已死。在 过去的十几年中,MVC在大型结构的应用程序当中确实获取了令人难以置信的成功。它出现十几年了,但是,新的编程技术已经越来越流行。没有它的自闭性(或 者匿名块),事件绑定变得非常乏味。没有它的延期和承诺,这种用各自权限处理当对象看的各自层次操作的思想不会有什么意义。

再次重申:MVC很棒,但是这是几十年前设计的老技术了。MOVE是一种更好用的新工具。

备注:Conrad Irwin不是唯一开始思考这种模式的人。如果你喜欢MOVE,你可以查看objectifyinteractions文章。里面除了阐述MVC程序之外还试图解释了MOVE的好处。如果你有其他的连接应该出现在这里,你可以告诉我。
再次备注:这篇文章被翻译为日语不止两次:d.hatena.ne.jp 还有 blog.neo.jp. 谢谢!

 

 //坐等吐槽….

文章原文来自“MVC is dead, it’s time to MOVE on.”。可能存在不准确翻译,推荐阅读“MVC模式已死?何不试试MOVE”。

[转载]FastReport.Net v1.9版本功能介绍

mikel阅读(851)

[转载]FastReports中文网-FastReports技术指南,FAQ,技术文章,.

.NET报表控件FastReport.Net 正式更新至v1.9版本,该版本新增QR码,改进WebReport,兼容Visual Studio2012等,接下来我们一起看看FastReport.Net的新特征。

.FastReport.Net 1.9更新概要:

  • 新增QR码
  • 改进WebReport
  • 为所有报表对象增加导出属性
  • 兼容Visual Studio2012
  • 新增UNC路径支持
  • 添加Config.PreviewSettings.PreviewOpened事件
  • 为报表页面新增BackPage属性
  • 新增WebReport.XmlExcelDataOnly属性
  • 改进TableObject性能
  • 改进了页眉/页脚的数据打印

详细更新,请参考:http://www.fast-report.com/en/news/9522.html

[转载]Howard's Startup Game @meditic » 降级论

mikel阅读(940)

[转载]Howard’s Startup Game @meditic » 降级论.

Howard’s Startup Game @meditic

 

 

几乎一年没有写博客了,说没时间那是借口,唯一的原因是,年纪越大越发觉自己肤浅。有些想法还没提笔,就发现很幼稚,就不敢发出来贻笑大方了。这次先给大家说个小故事:

 

从前有三个屌丝,聚在一起做网络,提供免费的网络服务,砸锅卖铁,通宵达旦,除了卖肾啥都做了。3年后终于做到了五百万用户,对于年轻人来说,能把 五百万人玩弄于鼓掌之间,已经是很牛逼轰轰的事了,不过用户越多,成本越高,每年服务器、带宽租金、房租水电、广告运营等成本,已经达到了十七八万,屌丝 们不得不面对一个终极问题:如何盈利?

 

屌丝们定了三盘沙县水饺,围着一箱子的冰啤酒开始计算:按照最近一月的登陆情况来看,四百万个账号已经不活跃了,真正有商业价值的只有一百万人,如 果开通xx功能,收点高级会员费,让其中1%的人升级为高级会员,每年付30块钱年费,那么每年收入就是100万x1%x30元=30万元!不错嘛, 扣除十七八万的运营成本,还剩毛利润12万,每个屌丝年底能分到4万大洋,如果按照打工者的算法,这三个人每人月薪3333元,木有奖金,木有津贴、木有 任何福利,上班还得带自家的电脑。

 

尽管如此,屌丝们还是激动得咬了一口水饺:感谢苍天!我们终于要盈利啦!!!那一夜,人们看到三个发疯的屌丝在屋顶翩翩起舞。

 

韩寒说,中国人民是最有忍耐力的族群,一点好处就感激涕零。他一定不知道,IT创业界里的屌丝,才是这群傻逼中的战斗机。他们可以平静地忍受每年都 持续亏钱,而且还能信心十足的对所有人说公司的状态非常好,如果有一天居然收支平衡了,他们会激动的趁夜难眠,比北朝鲜倒掉还开心。

 

本文开头的三个屌丝,其实是非常幸运的,至少能做到月薪3333元。大部分的屌丝在第一年做到几万用户的时候就会挂掉,原因众多,最主要要的是意志 太弱,受不了最初的寂寞;意志稍微坚强点的会在第二年第三年慢慢挂掉,原因主要是资金断裂、团队分裂;能成功熬到第四年还没饿死、还没被口水淹死、还没被 肠胃病颈椎病腰肌劳损折磨死的,甚至员工不减反增的,基本上属于神仙级别了。

 

我为什么要说三个屌丝的故事呢。首先是因为这是身边每天都在发生的故事,其次是因为感到可惜,IT界在我眼里一直是一个无比高级的职业,聚集着全球最聪明、最富有的人类精英。以IT创业界的青年们的智商,他们可以做成任何一件事情,包括改造银行制造汽车发射航天飞机 。结果这帮人却整天在蓬头垢面得为3k的月薪而挣扎,太悲催了。

 

为什么用悲催这个词? 如果一个人生下来就在山沟沟里,一辈子都没机会去见什么好东西,这不叫悲催,这只叫苦难;而如果一个人生出来有一个奇怪的特异功能:皮肤出来的汗水会凝结 成昂贵的水晶,本来只靠出汗就能赚钱,结果这傻逼居然觉得出汗这个行为太低级,做手术把自己的汗腺全给切了,而且丝毫没有意识到他做了什么傻事,这才叫真 的悲催。

 

我们IT界中的很多人,生下来就是有这个出汗成水晶的特异功能的,正是因为这种与众不同,这群人能混入牛逼的大学,整天打网游还能写出像样的毕业论 文, 拿到学位,进外企,考CPA,做咨询、做证券分析,研究高分子材料,做电子商务,做云计算。。。一级一级的上升,直到有一天,发现身边的人里,已经没有一 个不是CPA,不是咨询师,不是高级研究员了,身边的人全是业界精英,个个都超级强悍。在这个所谓的高级圈子里,自己并没有任何过人之处,只不过是 just another analyst而已。在高级圈子里拼的头破血流,最后也只能混到给台湾人整理数据而已。莫然回首,发现当年的血气方刚、年少时的无限梦想,进化成了一身肥 胖的赘肉。这个时候,有个旁观者说:“升级到头了,该降级了”

 

当一个社会疯狂鼓吹快节奏的时候,一定需要有人来宣扬慢生活;当全社会跟打了鸡血似的吹捧升级的时候,一定需要有人来说说降级论。

 

IT青年们喜欢打游戏,喜欢升级。他们的人生也和游戏一样,沉醉于不停的升级中,不仅喜欢升级自己手上的技术,把MySQL改成MongoDB,把 Apache升级为Nginx,在Mac上装Ubuntu,Ubuntu里再装个虚拟机去跑Mac OS。。。IT青年们也喜欢升级自己的人生,从程序员升级到项目经理,再升级到技术总监或产品总监,再升级到合伙人。。。

 

在不断追求升级的过程中,所面临的一个很大事实是:当一个人从A刚升级到A+级的时候,其实这个人的能力层级依然只是A的层级,还未胜任A+的层 级,他必须要到A+的后期,才可以胜任A+。就好像一个高中生,高考完之后,虽然理论上已经属于大学生了,但是他的实际能力依然只是高三毕业的水平,除非 他全部pass了大一的期末考试。同样的道理,这个世界上有很多人的身份和称谓,都是在描述“未来的自己”,而不是现在的自己。当你从销售员升级为销售经 理的时候,你自我感觉很好:“我现在是销售经理了”,但是这个时候 ,你并未通过公司对你作为销售经理这一年的工作成果的考核,你只是一个“未来可能是合格的销售经理”的前身。如果年终考核你失败了,那么这一年最准确的描 述是:一个销售员,占了整整一年销售经理的位子,最后失败了。而且这一年一定会过的很累,因为通过考核的其他销售经理,才是真正胜任这个层级的人,跟一帮 真正属于这个圈子的人厮杀,就好像拳击馆里当陪练的小角色,去和泰森比了一年的武,怎么可能不累呢?

 

当我07年进入互联网行业的时候,就是那个拳击馆里陪练的小角色,我被迫去跟全国各地的泰森比拼,结果累的半死。后来我开始反思最初的目标,为什么 要在自己身上挂一个“拳击高手”的招牌,被那么多泰森追着打? 我把这块招牌卸了,找个完全没练武的人去比拼,不是更容易赢么?于是果断照做,去找了一个没人懂拳击的小乡村,做了纯英文的Tucia.com(需 翻墙),只做国外的业务。在那个地方,作为一个知名武馆的拳击小陪练,我成了村子里拳击技术最高超的人,受人仰慕,还开武馆教人拳击,活的非常滋润,而且 在教人拳击的过程中,自己的拳术也比以前提高了很多,发展出一套属于自己的拳法,我虽然进不了泰森们的大圈子,但他们也进不了我的小圈子。

 

关于圈子,有一个很赤裸裸的现实:不会是你进入圈子,只能是圈子进入你。很多人会四处找关系,“帮我介绍给xxx吧,我想进入你们的圈子”,这样的 人是永远进不去这个圈子的,因为圈子的天性是,永远追求更高一个层级的人。而我们的大部分人,其实都在以低一级的属性,占着更高一级的位子,徘徊在更高一 级的圈子边缘,与更高一级的人竞争,幻想着自己可以升级到那个圈子里去。也许永远进不去,悲催的努力一辈子;也许运气好,某一天真的进入这个圈子了,但那 个时候又会有下一个目标,希望进入更高级的圈子,这是一场没有终点的战斗。永远的追求升级,永远的累。

 

有没有想过降级呢?

 

如果一个来自微软的高级工程师,辞职去一个养猪场做开放平台经理,那么他的到来不仅会让养猪圈感到无比荣幸,更是意味着,利用他在IT界训练出来的 高效工作方式和逻辑思维能力,他可以掀起一场养猪行业的革命,使得20年后才会出现的人性、高效、开放、协作、健康的养殖方式提前到达。在这场革命中,他 会活的非常有价值。这种价值,在原先的圈子里,是完全体验不到的,因为他此前的所有工作,只是在满身疮痍的windows系统上不停的打补丁,无论打多少 都逃不开产品衰落、被人鄙视的命运。

 

很多人的命运,都像是上面那个微软工程师。只需要降级,就能创造更大的价值,也能获得更大的满足。那为什么不呢?为什么要死死抱着那个所谓的“高级职业”不放呢?

 

去年我曾犯贱去趟了移动互联网的浑水,做了个手机app,刚开始的时候感觉很高级,但很快,铺天盖地的竞争对手就出现了,我又发现自己陷入了07年 一样的场景:作为一个小小陪练,我他妈的又被一帮泰森们给围住了。当泰森中的战斗机—微信,变得无比牛逼之后,我就知道,战胜这群泰森是绝对不可能的 事情了。于是我再次投靠了“降级论”,把自己从牛逼哄哄的移动互联网行业,降级到了一个被人不齿的低级项目:Tucia Baby

 

这个项目虽然是传统行业,但是我们基本上是按照互联网产品的思路去做的,除了拍摄需要来店里以外,其他一切,包括营销、预约、客服、后期、选片、取片、客户关系等,所有环节都放在网络上,尤其是微博(@tuciababy官网)。 当然,最重要的是,作为一个脑残的果粉,我按照iPhone的做工去要求每一张作品,必须达到我们能力可以做到的最好水准,不计成本的最好水准,才允许送 给客户。正式接客不到两个月时间,虽然还远未达到成功,但目前已做到每天都有客户订单,财务上已实现盈利,未来相信一定会比大部分app开发者更光明。 (ps:我们没有请公务员吃饭喝酒泡桑拿,也没有塞钱给任何政府机关。当你的产品真的用心做到很好的时候,其实你不需要讨好任何人的。)

 

这个项目让我沉思了很久:07年我曾把一个纯纯的web2.0网站做到了alexa中国区前1000名(如有质疑,请查询2010年附近的tucia.com排名),结果一路亏损,到最后只剩下一个员工;11年我把那个纯纯的app做到苹果官方推荐区免费榜第一位(点此看截图),那段时间每天四五千iPhone安装量,结果一路烧钱,到最后濒临关闭;而如今,我只需把自己从纯纯的互联网降级下来,做一些看起来有些“低级”的项目,居然就能立即实现收支平衡。

 

除此以外,我还发现一个现象,中国消费者在与奸商们的长期斗争中,已经培养出了一种非常苦B的品质:只要不被坑,他就谢天谢地。如果商家严格做到了 承诺的每一件事情,客户就会感动的泪如泉涌。如果商家不仅做到了所有承诺的事情,还很贴心的提供了一些额外的服务(比如我们给每位客户赠送非常好吃的樱桃 和进口巧克力作为甜点),那么客户就会激动的哭天喊地、奔走相告,推荐给他认识的每一个人。

 

其实这片肮脏的国土,就是上天赐予IT青年们的最好机会。

 

在一个不会练武的村子里,只要你会打两拳,你就是拳术最厉害的人;在一个没有服务意识、忽视产品质量的土地上,只要你用心做服务,用最高的标准去要 求自己,你就会成为这块土地上最出色的商家;在一个没有现代管理意识,不懂网络、不懂微博、不懂用户体验、不懂口碑传播的粗犷社会里,你只需要把之前花在 IT产品上的心思的10%拿过来用,就可以秒杀一切天朝对手。

 

所以,

 

IT青年们,当你在为网站的转化率苦苦思索的时候,当你在为app的活跃度辗转反侧的时候,当你在为融资计划苦苦哀求各界大佬引荐的时候,也许犯了 一个错误,也许你们的脑子最值得闪光的地方,不是去悲催的IT界当炮灰,而应该是去按摩界、餐饮界、烧烤界、早餐界、理发界、按摩界、送花界、纺织界、成 人用品界、现代化养殖界、有机蔬果界、个人护理界、汽车修理界。。。。与IT界相比,这些行业的确无比低级,他们的老板连qq都会发音成“抠抠”,他们的 员工一辈子都没用过Email;跟他们解释什么是SEO,什么是用户体验,什么是数据挖掘,他们会在听你说完之前就开枪自杀掉。正是因为如此,这些行业才 是如此的不堪一击。正是因为如此,当智商高达147的IT青年还在为3k薪水拼命、而智商不到50的烧烤店老板正坐在porsche里玩着前面那位青年开 发的app的时候,我就忍不住仰望星空。

 

这些原始而纯粹的行业,正在等待IT精英们的降级,如同蒲公英一般的伞兵,在黑夜里从天而降,长驱直入,用最智慧的产品、最优质的服务拯救这些早就该死的行业,屌丝的生命将会绽放出银色的羽翼,无比丰满,无比性感。

 

最后注意,请珍惜生命,远离我的微博:@meditic

 

.
ShareThis
This entry was posted on July 03rd, 2012 and is filed under , . You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response or Trackback from your own site.

[原创]EasyUI的searchbox组件使用

mikel阅读(1510)

最近项目中用到easyUI的searchbox组件,输入查询的值后弹出窗口按照输入的值查询数据,代码如下:

            $('#Supplier').searchbox({
                searcher: function(value, name) {
                    alert(value);//获取输入的值
                    $('#request-supplier-window').window({
                        href: '/Supplier/SelectSupplier?supplier=' + value + '&r=' + Math.random()
                    });
                    $('#request-supplier-window').window('open');
                }
            });

searchbox的searcher函数中的function(value,name)参数分别是:

  • value:searchbox中输入的值,注意是输入的值,在form加载后对searchbox赋值的不是输入的值,这时候searcher函数是获得不到value的
  • name:searchbox组件的名字

上面已经说到value值是输入的值,当form加载后赋值给searchbox中的不是输入的值,是以灰色显示的,需要在form的onLoadSuccess事件中赋值给searchbox,这样searcher才能获取到value的值,代码如下:

            $('#request-editForm').form({
                onLoadSuccess: function(data) {
                   $('#Supplier').searchbox('setValue',data.Supplier);

                }
            });