[转载]InfoQ: 如何查看我的订单-REST的流程API设计案例

mikel阅读(1003)

[转载]InfoQ: 如何查看我的订单-REST的流程API设计案例.

这是一个关于订单的故事。四个月前,我在某刚刚上市不久的网上书城框框网购买了一包纸尿裤,因为尺寸不对,我选择了退货,由此开始了我糟糕的用户体 验:首先是快递公司取回了纸尿裤却没有还款给我,接下来,在两个月的时间里,我不得一次又一次的向框框的客服投诉,客服很客气,她让我说出我的订单号然后 说需要帮我查一查,两分钟后,她说需要和快递公司联系,稍后再打给我;随后的客服都很客气,但无一例外的,她们都不清楚我订单的处理情况,她们甚至很惊 讶,快递公司还没有给你办吗?终于,我失去了耐心,我说,难道你们框框就只负责卖东西吗?送货外包了你们就不需要负责了吗?究竟是谁在处理我的订单,谁是 责任人你们不知道吗?整个流程的处理状态你们清楚吗?客服支支吾吾半天,说,我们这就和快递公司联系。

框框的问题出在什么地方?顾客找不到这个订单的直接负责人,只能和客服打电话,而框框自己也失去该订单的状态了,于是只能再打快递公司的客服,这样 问题透过组织结构层层传递下来,执行力可想而知。那为什么顾客找不到订单的直接负责人呢?因为顾客失去对自己订单的可视化了,我不知道我的订单现在处于一 种什么样的状态,正处于哪一个步骤,谁是这个步骤的责任人,网站上没有,自然无从得知。想想我们的迭代和show case,也正是一种对顾客的可视化手段。

在下面的章节中,我们将一起来应用rest的架构风格逐步搭建一个端到端的流程管理系统,看看如何解决这个问题,这个问题就是:看在上帝的份上,让 我看看我的订单。为什么使用rest?因为在下面的故事中,我们将会有大量的系统集成工作。为什么集成要用rest?因为REST的实质是充分利用 HTTP协议,将其作为一种应用协议,而不仅仅是传输协议,这样在WEB上做集成时能够最大程度上的达成一致,形成大家都能接受的约定,减少集成的工作 量。另外,不可忽视的一点是,我们更多的是做数据的mashup,这很适合rest的应用特点。不能使用web service吗?当然可以使用web service,只是我们这里使用rest而已。

第一个需求,我想随时随地查看我的订单

好吧,很自然,这需要我们的程序支持移动设备。

图 1  昨天我们这样开发程序

似乎就在昨天,我们还在如图1一般开发程序,我们一边打开firebug进行调试,一边诅咒IE的不得好死,那时我们的关注点集中在前端,集中在如何使各个浏览器的行为和样式保持一致。而服务器端则是经典的MVC框架,直接将渲染好的HTML文档扔回客户端。

图 2  今天我们这样开发程序

然而到了今天,一切都发生了变化,我们开发的程序成了图2的样子,随着IE向标准的靠拢,HTML5似乎有一统客户端之势,然而,移动互联网的兴起 让我们编写程序重新变得复杂,昨天我只需要支持浏览器,现在则需要支持各种手机平台上的原生应用。自然,和昨天存在大量的JavaScript和css框 架来抹平不同浏览器之间的差别一样,现在我们也有了PhoneGapTitanium来抹平不同平台之间的差别,尽管目前这些跨平台工具还存在用户体验不理想的问题,但最重要的变化来自两个方面:一是客户端重新变胖;二是服务器端由返回渲染完成的页面退化为返回数据,具体表现就是对客户端暴露出API。

对移动设备的支持使得我可以在写代码时、吃饭时、睡觉时甚至坐马桶时随时查看我的订单(当然前提是你得有这些设备),而本文后续的架构变化也会围绕 着图2逐渐演进。至于明天HTML5是否会最终代替原生应用,我觉得不会,不仅仅是技术原因(对设备硬件的使用)更重要的是商业原因,替代后的苹果变得和 现在的微软/诺基亚一样尴尬,那么,也许后天?

好吧,既然是REST的API设计,我们来看看REST的架构风格。RESTful 架构遵从以下几个原则:

  • 请求是客户-服务器 式的,并很自然地使用一种基于拉的交互风格。
  • 请求是无状态的。每个从客户端到服务器端的请求都必须包含理解此请求所需的全部信息,而且不能利用服务器上所存储的上下文。
  • 客户端和服务器都遵从统一的接口。所有的资源都可通过 Web 的普通接口进行访问 —— HTTP 及 HTTP 方法:GET、POST、PUT 和 DELETE。
  • 客户端通过URI与命名的资源进行交互。
  • 将http 状态码作为系统的状态码。

REST的实质是充分利用HTTP协议,形成大家都能接受的约定。

看例子,我们以订单列表作为整个应用的调用入口,我们首先会GET:http://api.kuangkuang.com/orders,服务器返回以下的数据:

1000
draft

1001
completed

在返回的数据中,我们看到了:

<link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1001"/>
这个链接引导我们查看具体的订单信息,我们GET:http://api.kuangkuang.com/order/1000,服务器返回以下的数据:
1000
draft
88.0

这里我们看到了两个链接:

<link rel="edit" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
<link rel="delete" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>

它们告诉我们可以对这个处于草拟状态的订单进行修改和删除。我们GET另外一个已完成的订单看看:http://api.kuangkuang.com/order/1001,返回数据:

<order>
    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1001"/>
    <content>
        <id>1001</id>
        <state>completed</state>
        <cost>66.0</cost>
    </content>
</order>

没有更多的链接,这意味着我们只能对该订单进行查看。

在这些交互中,最重要的是服务器端返回数据本身已包含了对其他资源访问和对现在资源操作的线索。这样的好处在于客户端只需要一个入口地址,其他所有 的操作地址全部由服务器端确定,这使得客户端与服务器端解耦,客户端不必再硬编码入URI,能够各自独立的进化,服务器端负责数据、权限以及交互URI的 确定,客户端重新回归展现数据的单一职责。

第二个需求,实现一个简单的流程

在上面的例子里,我们看到了订单的CRUD操作,但这并不是实际生活中的真实情况,整个订单的生命周期如下图所示:

图 3  订单的完整流程

在实现这个流程时,我们分为两步:第一步对订单进行资源建模;第二步通过工作流对订单进行流程的生命周期管理。

图 4  订单的资源模型

图 5  使用工作流管理订单的生命周期

工作流的职责在于管理订单的生命周期,在其生命周期的不同阶段,我们会有不同的参与者,对订单不同的操作权限。我们的系统架构演变成下面的样子:

图 6  使用工作流管理资源模型的生命周期

看例子,这次将我们视角转移到框框网这边,看看框框如何处理我们已提交的订单。我们使用GET:http://api.kuangkuang.com/orders?status=waiting-review来获取所有需要审核的订单,服务器返回以下的数据:

<orders>
    <link rel="list" media-type="application/xml" url="http://api.kuangkuang.com/orders"/>
    <order>
        <id>1000</id>
        <state>waiting review</state>
        <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    </order>
</orders>

我们查看具体的订单信息,我们GET:http://api.kuangkuang.com/order/1000,服务器判断出我们是框框网员工,返回以下的数据:

<order>
    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    <content>
        <id>1000</id>
        <cost>88.0</cost>
        <state>waiting review</state>
        <squence>
            <activity rel="review" media-type="application/xml" url="http://api.kuangkuang.com/review/order/1000"/>
        </squence>
    </content>
</order>

注意到这两行:

<state>waiting review</state>
<squence>
    <activity rel="review" media-type="application/xml" url="http://api.kuangkuang.com/review/order/1000"/>
</squence>

这段信息是由工作流加入的,它告诉我们当前订单的状态为等待审核以及下一步需要我们来审核。那么,我们PUT http://api.kuangkuang.com/review/order/1000告诉服务器我们审核通过,服务器返回数据:

<order>
    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    <content>
        <id>1000</id>
        <cost>88.0</cost>
        <state>waiting send</state>
        <squence>
        <activity rel="send" media-type="application/xml" url="http://api.kuangkuang.com/sent/order/1000"/>
        </squence>
    </content>
</order>

同样,工作流加入了这两行数据:

<state>waiting send</state>
<squence>
    <activity rel="send" media-type="application/xml" url="http://api.kuangkuang.com/sent/order/1000"/>
</squence>

告诉我们当前订单状态为等待送货,而下一步需要我们来完成这一步。此时,如果顾客来查看自己的订单会得到什么数据呢?服务器会判断出当前请求的用户是顾客,那么:

<order>
    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    <content>
        <id>1000</id>
        <cost>88.0</cost>
        <state>waiting send</state>
    </content>
</order>

顾客能够看到自己的订单正处于等待送货状态,而不会有下一步的动作。恩,很好,框框网订单处理速度很快,而这正是网购的主要竞争力之一。

等等,如果订单审核不通过呢,继续看流程:

图 7  增加一个网关

作为框框网的员工,我们GET:http://api.kuangkuang.com/order/1000,服务器判断出我们是框框网员工,返回以下的数据:

<order>
    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    <content>
        <id>1000</id>
        <cost>88.0</cost>
        <state>waiting review</state>
        <squence>
            <xor rel="review" media-type="application/xml" url="http://api.kuangkuang.com/review/order/1000">
                <choice>pass</choice>
                <choice>reject</choice>
            </xor>
        </squence>
    </content>
</order>

注意到这两行:

<squence>
    <xor rel="review" media-type="application/xml" url="http://api.kuangkuang.com/review/order/1000">
        <choice>pass</choice>
        <choice>reject</choice>
    </xor>
 </squence>

系统告诉我们这是一个排他网关,我们需要作出选择,在客户端与服务器端就media-type达成一致的情况下(即客户端能够充分理解服务器端返回 的数据格式,这个数据格式被标准化),我们的代码不需要作出任何的修改,pass和reject作为工作流变量被put到服务器,由工作流引擎进行处理, 同样不会影响到订单的资源建模。

在这些交互中,最重要的是我们通过工作流实现了REST社区所呼吁的“将超媒体作为应用状态的引擎(hypermedia as the engine of application state)”。更简单地说,URI代表了状态机里的状态迁移。我们通过<squence>、<activity> 和<xor>标签让客户端是通过跟随链接的方式来操作订单状态机的状态转移。

第三个需求,框框将物流部分外包

在我们实际的生活中,电商们并不自己送货,他们将这部分工作外包给了物流公司。从成本的角度考虑,外包送货是最合适的选择。实际上,整个订单从提交到最后的完成情况还要稍微复杂一些,如下图所示:

图 8  订单从提交到完成的整个流程

从图中我们可以看出,这个流程跨越了两家公司,同时也涉及到了三个系统的集成,这三个系统分别是框框网的前台网站、框框网的后台负责仓储、进出货和 物流的ERP系统以及外包物流公司的ERP系统。三个系统各自有自己的处理流程,整个订单的端到端处理流程由这三个系统的三个流程所共同完成:当我们在框 框网提交订单时,一个消息被发送到框框的后台ERP系统,这个消息触发一个货物的出库流程,当货物打包完毕出库时,一个消息被发送到物流公司的ERP系 统,同时触发物流公司的包裹配送流程,当我们给物流公司的配送员付款完毕时,对我们顾客来说框框的购物流程已经结束,然而整个流程依旧还要继续,配送员回 到公司完款,一个消息被发送回框框的后台ERP,物流公司的包裹配送流程结束,框框网的这个订单这才处理完成。

在本文的一开始,我们提到了那个糟糕的退货故事,问题就在于当订单交由物流公司进行货物配送时,我们包括框框失去了对配送流程的可视化,物流公司的 处理情况在我们的流程中黑盒了。如何解决这部分的问题呢,有两种处理方法:一是在框框网订单处理流程中加入捕获事件,正如图8中所示的,当框框后台ERP 和物流公司ERP对订单进行处理时,每到一个任务节点就给框框网的订单处理流程发送消息,由此给我们标示出订单的实时状态。

图 9  通过系统集成传递订单实时处理消息

现在,让我们来看看自己的订单会得到什么数据呢,GET http://api.kuangkuang.com/order/1000框框网前台网站返回数据:

<order>
    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    <content>
        <id>1000</id>
        <cost>88.0</cost>
        <state>waiting send</state>
        <history>
            <activity rel="submit" time="2011-6-28 14:00" participant="ronghao"/>
            <activity rel="review" time="2011-6-28 14:30" participant="xinpeng"/>
            <activity rel="delivery package" time="2011-6-28 15:00" participant="haorong"/>
            <activity rel="warehouse" time="2011-6-28 17:00" participant="pengxin"/>
        </history>
    </content>
</order>

订单状态为等待物流公司送货,注意到这段数据:

<history>
    <activity rel="submit" time="2011-6-28 14:00" participant="ronghao"/>
    <activity rel="review" time="2011-6-28 14:30" participant="xinpeng"/>
    <activity rel="delivery package" time="2011-6-28 15:00" participant="haorong"/>
    <activity rel="warehouse" time="2011-6-28 17:00" participant="pengxin"/>
</history>

工作流加入了订单处理的历史信息,从这段信息可以看出,我们要明天上午才能收到自己的货物了。

很不错不是吗,但是现实情况又是怎样呢。我们先来看看当当,当当如是说:订单状态变为“已发货”后,您可以登录“我的订单”,点击订单号进入订单详情页查看快递公司的联系方式,用订单号查询即可。我们再来看看卓越,卓越如是说:宅急送配送的订单:登录宅急送网站或拨打020-82252310-802查询;港中能达配送的订单:登录港中能达网站或拨打020-86443920查询。也就是说物流公司的配送流程状态并没有集成到网站中来,如下图所示:

图 10  网站与物流公司系统没有集成

为什么没有集成呢?第一是物流公司的客户往往不止框框一家,第二是框框也不会把鸡蛋放在一家物流公司的篮子里,这些给系统集成带来了难度,我们会突 然发现有大量的系统需要集成,而系统间的集成之间存在太多的集成点,调试以及约定,这些都需要大量的工作和成本,系统们被紧紧的耦合在一起。

既然第一种实现方式使得我们即时查看我们订单状态成本太大,那我们看看第二种方法:使用一个统一的流程管理系统来管理整个端到端的流程。

图 11  使用统一的业务流程管理系统管理端到端流程

业务流程管理系统的职责有两个:一是由其管理起各个系统间的集成工作,这样避免了各个系统间的大量耦合;二是由其跟踪订单状态,完成订单在整个流程中的可视化。

图 12  框框订单的端到端流程

我们来看看具体的api调用,当我们在框框网站提交一个ID为1000的订单时,框框网站会发送一个消息到http://api.kuangkuang-bpm.com/process-definition/1,由此触发整个的流程,启动一个新的流程实例。发送的消息:

<order>
    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    <content>
        <id>1000</id>
        <cost>88.0</cost>
    </content>
</order>

业务流程管理系统给我们返回的消息:

1

1000
88.0

订单提交

返回的消息中给我们指出了该订单所关联的流程实例ID,当前正在执行的任务。流程系统创建流程实例后接下来继续往下执行,它发送一个消息到框框的后 台ERP系统,触发后台ERP系统对订单的处理,同时告诉其访问当前流程实例的URI。现在我们假设流程执行到物流公司的配送任务,我们在框框网站查看订 单的即时状态系统会有哪些动作。第一步我们同样是GET:http://api.kuangkuang.com/order/1000,返回的数据:

<order>
    <link rel="detail" media-type="application/xml" url="http://api.kuangkuang.com/order/1000"/>
    <link rel="process-instance" media-type="application/xml" url="http://api.kuangkuang-bpm.com/process-instance/1"/>
    <content>
        <id>1000</id>
        <cost>88.0</cost>
    </content>
</order>

返回的消息中多了一个访问流程实例的URI,那么我们的客户端程序继续GET:http://api.kuangkuang-bpm.com/process-instance/1,返回的数据:

1

1000
88.0



物流配送
sub-process





我们看到当前正在执行的任务是物流配送,这是一个子流程任务,想具体了解这个子流程执行的情况,我们的客户端程序继续GET:http://api.zjs-erp.com/order/2000,啊哈,框框将配送任务外包给了宅急送啊。

<order>
    <link rel="detail" media-type="application/xml" url="http://api.zjs-erp.com/order/2000"/>
    <link rel="process-instance" media-type="application/xml" url="http://api.kuangkuang-bpm.com/process-instance/1"/>
    <content>
        <id>2000</id>
        <cost>88.0</cost>
        <current-activity>
            <name>配送</name>
        </current-activity>
        <history>
            <activity name="接受包裹配送单" time="2011-6-29 15:40" participant="ronghao"/>
            <activity name="包裹入库" time="2011-6-29 15:45" participant="xinpeng"/>
        </history>
    </content>
</order>

好了,有了这三段数据,我们就可以清楚的看到订单所经过的各个环节以及当前的状态。从中可以看到两点:一是我们通过kuangkuang- bpm.com所提供的流程服务将各个系统进行了数据和流程的集成;二是各个被集成的系统需要实现rest的api以供我们的客户端程序进行数据的 mashup。

故事完了吗?还没有,京东618活动简报:收获订单40多万份,订购金额超2亿,已经发货一个多亿,尚有十几万份订单积压,大约三日左右可以处理完 毕。不足之处:流量多次超过4个G,服务器运行缓慢;图书备货量严重不足。与服务器相比,我更加关心如何及时将这几十万份订单处理完毕以及库存如何应对促 销而产生的水平震荡(图书备货量严重不足,无法预测哪些书籍畅销哪些滞销,由此带来的订单迟迟无货),这显然不是一家物流公司可以完成的,需要多家物流公 司一起消化这些订单,那么,问题来了,当我们mashup数据时,如何对这些物流公司返回的不同的数据格式进行处理?为每家公司实现一个适配器?NO!作 为行业的老大,作为一家一流的企业,我们得制定标准。这时就需要标准化media type了,建立行业标准,企业级rest等于自定义、创造和标准化media type。

第二个问题,kuangkuang-bpm.com算是云服务否?是的,目前算作框框私有云,对自己和业务伙伴提供流程服务。

最后一个需求,框框要开放平台

最后一个需求有些跑题,但是那帮人现在都在搞什么开放平台,框框自然也是要跟风一下的。那么,开放平台都开放了个啥?

图 13  框框的开放平台

第一是用户的开放,这个通过网站实现,我们增加上百货、品牌频道,吸引商家入驻,这些入驻的商家能够分享我们的网站用户,同时,品牌的用户也能够被 吸引到我们的网站上。同时,开心、人人、新浪微博、腾讯微博的账号能够直接登录我们网站,同时,我们网站的产品、评价也能分享到这些SNS网站里。

第二是服务的开放,这个通过流程实现,我们能给商家提供仓储、物流、投诉等一系列的服务。不使用我们的网站,自己有网站?没关系,只要数据格式满足我们定义的media type就行。

图 14  流程服务的开放

这算完了吗?不算!小马哥说,要开放就全面开放。我们将仓储环节和物流配送环节也开放出来,允许任何服务提供商使用我们的流程服务。

图 15  物流环节的开放

只要系统提供数据格式满足我们定义的media type就可以加入到我们的流程中来,分享我们的客户,为上帝提供服务。我们则从中提成。

所以,真正的开放是整个流程服务各个环节的开放,不仅仅对流程消费者,也对流程服务提供商,我们在其中起一个协调的作用。由此,我们将 kuangkuang-bpm.com服务开放出来变成一个公有云服务,后来,我们惊讶的发现,kuangkuang-bpm.com比 kuangkuang.com更赚钱,因为它建立起了一个生态链。

小结

在上面的故事里,我们一步步看到了框框的系统架构演进,不难看出,架构演进的过程实际上系统不断分解的过程。

对移动设备的支持,使得我们将页面渲染逻辑从服务器端剥离出来交由客户端完成,服务器只负责提供数据。通过rest的超媒体特性,客户端和服务器端程序能够各自独立演进。

对订单流程的支持,使得我们采用工作流技术,将资源模型(订单)与其生命周期模型分离,分别交由原有系统和工作流系统管理,这样当某部分需求发生变化(例如增加一个审核不通过步骤),能够隔离变化,容易修改。

对端到端跨系统流程的支持,使得我们引入一个独立的业务流程管理系统,由此来协调各个系统间的集成工作,避免系统间的大量耦合。同时,我们看到定义和标准化一个大家都能理解的media type是如此的重要。

对开放平台的支持,使得我们将业务流程管理系统开放出来,作为公有云服务平台,同时流程中的各个环节能够开放出来,作为服务/应用接入和接出的接口,真正做到松耦合,由此看到开放平台的实质是流程服务的开放。

在同事陈金洲的《架构腐化之谜》中,文章最后提到解决这一问题的方法是:创建应用程序的生态环境,而非单一的项目。深得我心,而我们这里想表达的是,随着系统的演进,我们需要不断进行系统的分解,做到服务的独立演化。

至于rest,它的作用就在于充分利用HTTP协议,将其作为一种应用协议,而不仅仅是传输协议,这样在WEB上做集成时能够最大程度上的达成一致,形成大家都能接受的约定,减少集成的工作量,对外接口一致,内部独立演化。

[转载]FCKEditor源代码分析及基于.Net的简化

mikel阅读(1085)

[转载]FCKEditor源代码分析及基于.Net的简化 – 吴剑-WEB应用 – 博客园.

FCKEditor源代码分析及基于.Net的简化

吴剑 2010-01-08

http://wu-jian.cnblogs.com/

前言
FCKEditor 是一款开源的非常优秀的WEB在线编辑器,它的JS类库几乎匹敌于当前流行的JQuery,目前最新版本为2.65,官方网站http://www.fckeditor.net/ 。本文基于.Net的应用针对FCKEditor的源代码进行分析,同时改造了部分过于复杂的功能,将其简单化。供大家学习讨论之用,个人能力有限,不足之处还请指正。
DEMO环境:.Net Framework 3.5,Visual Studio 2008,Client:FCKEditor 2.6.5,Server:FCKEditor.Net 2.6.3

下载安装

FCKEditor包含了两部分:应用于客户端的FCKEditor 2.6.5 和 应用于.Net服务端的FCKEditor.Net 2.6.3,从官网下载解压后如下:

2.6.3是C#的源代码,如果不需要自定义修改对DLL进行重新编译,那么只要引用项目中的 bin\Debug\2.0\FredCK.FCKeditorV2.dll即可。因为FCKEditor提供了对多语言的支持,在2.6.4中能看 到.PHP,.ASP,.PL,.CFC,.CFM等开发源文件后缀,在此我们只进行FCKEditor与.Net的结合应用,所以精减后如下:

fckconfig.js配置

工具栏菜单配置:

FCKConfig.ToolbarSets["Default"] = [
['Source','DocProps','-','Save','NewPage','Preview','-','Templates'],
['Cut','Copy','Paste','PasteText','PasteWord','-','Print','SpellCheck'],
['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
'/',
['Bold','Italic','Underline','StrikeThrough','-','Subscript','Superscript'],
['OrderedList','UnorderedList','-','Outdent','Indent','Blockquote','CreateDiv'],
['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
['Link','Unlink','Anchor'],
['Image','Flash','Table','Rule','Smiley','SpecialChar','PageBreak'],
'/',
['Style','FontFormat','FontName','FontSize'],
['TextColor','BGColor'],
['FitWindow','ShowBlocks','-','About']    // No comma for the last row.
] ;

/*
-                :    分隔线
/                :    换行

Source            :    源代码
DocProps        :    页面属性
Save            :    保存
NewPage            :    新建
Preview            :    预览
Templates        :    模版

Cut                :    剪切
Copy            :    复制
Paste            :    粘贴
PasteText        :    粘贴为无格式文本
PasteWord        :    从MS-WORD粘贴
Print            :    打印
SpellCheck        :    拼写检查
Undo            :    撤消
Redo            :    重做
Find            :    查找
Replace            :    替换
SelectAll        :    全选
RemoveFormat    :    清除格式

Form            :    表单
Checkbox        :    复选框
Radio            :    单选按钮
TextField        :    单行文本
Textarea        :    多行文本
Select            :    列表/菜单
Button            :    按钮
ImageButton        :    图像域
HiddenField        :    隐藏域

Bold            :    加粗
Italic            :    倾斜
Underline        :    下划线
StrikeThrough    :    删除线
Subscript        :    下标
Superscript        :    上标

OrderedList        :    插入/删除编号列表
UnorderedList    :    插入/删除项目列表
Outdent            :    减少缩进量
Indent            :    增加缩进量
Blockquote        :    块引用
CreateDiv        :    新增DIV标签

JustifyLeft        :    左对齐
JustifyCenter    :    居中对齐
JustifyRight    :    右对齐
JustifyFull        :    两端对齐

Link            :    插入/编辑超链接
Unlink            :    取消超链接
Anchor            :    插入/编辑描点链接

Image            :    插入/编辑图像
Flash            :    插入/编辑FLASH
Table            :    插入/编辑表格
Rule            :    插入水平线
Smiley            :    插入表情图标
SpecialChar        :    插入特殊符号
PageBreak        :    插入分页符

Style            :    样式
FontFormat        :    格式
FontName        :    字体
FontSize        :    大小

TextColor        :    文本颜色
BGColor            :    背景颜色

FitWindow        :    全屏编辑
ShowBlocks        :    显示区块
About            :    关于FCKeditor
*/

其它配置:

/*回车键 [ p | div | br ]*/
FCKConfig.EnterMode = 'br' ;
/*Shift + 回车键 [ p | div | br ]*/
FCKConfig.ShiftEnterMode = 'p' ;

已发现BUG修复

修复文件上传脚本执行权限错误

参见:http://dev.fckeditor.net/attachment/ticket/2115/2115.patch

使用VS2005打开FredCK.FCKeditorV2.vs2005.csproj,打开FileBrowser > FileWorkerBase.cs,在第118行

//原始代码
Response.Write( @"(function(){var d=document.domain;while (true){try{var A=window.top.opener.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();" );
//修改后的代码
Response.Write(@"(function(){var d=document.domain;while (true){try{var A=window.parent.OnUploadCompleted;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();");

插入/编辑超链接

原始:

简化:

如上图所示,对“插入/编辑超链接”功能进行了彻底简化,不可否认源代码中考虑了超链接的几乎所有应用情景,非常之完善和全面,甚至提供了服务器目录的浏览和文件上传功能,但这也恰恰暴露了更多的安全隐患,参考了各大网站的应用,简化为仅显示文本和链接地址。

这部分功能的源代码主要分布在两个文件中:

UI部分 /editor/dialog/fck_link.html

功能函数 /editor/dialog/fck_link/fck_link.js

因为原始版本包含了过于复杂的功能,所以在分析和修改js代码时也费了不少功夫,删除了多余的功能函数,支持了文本的显示,缩减大约90%的代码。具体细节可查看DEMO。

插入/编辑FLASH

原始:

简化:

如上图所示, 去除了服务器浏览和文件上传功能;去除了“高级”里的多余选项,将功能整合在一个标签下。

这部分功能源代码分布在三个文件中:

UI界面 /editor/dialog/fck_flash.html

功能函数 /editor/dialog/fck_flash/fck_flash.js

Flash预览 /editor/dialog/fck_flash/fck_flash_preview.html

插入/编辑表格

原始:

简化:

该部分功能主要包含在 /editor/dialog/fck_table.html 中,如上图,去除了“标题”、“摘要”、“标题单元格”三项,通过生成的HTML代码可以发现,FCKEditor是一款非常严谨的软件,它严格遵循了 W3C。但大部开发人员都不熟悉的HTML标签对于用户来说使用就更较少了,固去除了这三项,原始与简化后控件生成的HTML差别如下:
原始:

<table style="height: 200px;" border="1" cellspacing="1" cellpadding="1" width="200" align="center" summary="summary"><caption>caption</caption>
<thead>
<tr>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>

简化:

<table style="height: 200px;" border="1" cellspacing="1" cellpadding="1" width="200" align="center">
<tbody>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>

插入/编辑图片

原始:

简化:

一直对 FCKEditor的上传功能不满意,首先是安全问题,“服务器浏览”这项功能之前就曾暴露过严重安全漏洞,所以完全屏蔽了这项功能,彻底删除了 /editor/filemanager/browser 目录;

其次是上传代码的扩展受限,比如上传图片时生成缩略图,实现这样一个常见的附加功能在FCKEditor现有结构下困难重重;

再有配置复杂且冗余,比如我的项目中十个页面用到了FCKEditor,每处的上传处理逻辑均不相同,这种需求难道要放十个FCK客户端十个配置?

在此并非刻意将FCK的上传批的一无是处,恰恰相反,我倒觉得FCK的开发团队是绝对的完美主义者,如果仔细研究了源代码你就会毫不怀疑这一点。他们要把这个控件做的支持多语言,多功能,又要保持绝对一至性。过于追求完美必然就会带来其它的牺牲。

OK,我决定重写FCKEditor的图片上传代码框架,仅争对.Net,目标是让开发者最简单最便捷的处理用户的上传。

概述一下我的想法:删除FCKEditor 中一切上传相关的配置,而只需指定一个参数:“我的上传处理类”。FCK会将上传交由这个类来处理,在这个类里你可以随心所欲的添加任何代码,权限控制、 与数据库交互、图片处理、存放位置……所有逻辑均在这个类里实现,然后告诉FCK这个类的位置即可。下面详细描述现实的过程:

Step1.让FCKEditor将上传交由“我的上传处理类”

首先为FCKEditor添加属性“ImageUploadAssembly” ,这个属性告诉FCK“我的上传处理类”的位置,格式为“程序集名称,类名称”,后面将会在FCK中扩展代码利用反射来创建该类的对象,并将上传交由这个 对象处理。在服务端代码FCKEditor.cs中添加属性:

///
/// 用于指明当前控件上传图片处理的类
/// 格式为“程序集名称,类名称”
///
[DefaultValue("")]
public string ImageUploadAssembly
{
get
{
object o = ViewState["ImageUploadAssembly"];
return (o == null ? "" : (string)o);
}

set
{
ViewState["ImageUploadAssembly"] = value;
}
}

接着在页面上添加FCKEditor控件时就能指定该属性了,如下代码所示:


然后是一个关键:如何将属性值传递到FCK中的Upload.cs中?花了不少时间对FCK源代码进行研究,整理出如下步骤:

1、首先将属性值以URL方式传递到 /editor/fckeditor.html中,如 /editor/fckeditor.html?ImageUploadAssembly=FCKEditorSimpleNet.Web,FCKEditorSimpleNet.Web.MyImageUploadHandler.Default ,这一步需要修改服务端Fckeditor.cs,如下:

protected override void Render(HtmlTextWriter writer)
{
//....部分代码略
if ( _IsCompatible )
{
//....部分代码略

//将图片上传处理程序集,类名称作为参数传递到 Fckeditor.html
if (this.ImageUploadAssembly.Length &gt; 0)
sLink += "&amp;ImageUploadAssembly=" + this.ImageUploadAssembly;
}
}

2、从 fckeditor.html 将参数传递到 /editor/fckdialog.html ,这个过程包含在 /editor/js/fckeditorcode_gecko.js 和 /editor/js/fckeditorcode_ie.js 中,这两个js是经过min处理的,分别对应了IE和非IE浏览器,两个文件基本一至,只是JS写法上稍有差别,修改如下:

O.src=FCKConfig.BasePath+'fckdialog.html?ImageUploadAssembly='+(FCKURLParams['ImageUploadAssembly']||'');

3、从 fckdialog.html 将参数传递到 /editor/dialog/fck_image.html

在fckdialog.html定义函数getUrlParameter,该函数供iframe子页面调用,以获取Url参数值:

//获取fckdialog.html的参数
function getUrlParameter(pName){
var pVal = "";
var aParams = document.location.search.substr(1).split('&amp;');
if(aParams.length &gt; 0){
for(var i = 0; i &lt; aParams.length; i++ ){
var aParam = aParams[i].split('=') ;
var sParamName  = decodeURIComponent( aParam[0] );
var sParamValue = decodeURIComponent( aParam[1] );
if(pName.toUpperCase() == sParamName.toUpperCase()){
pVal = sParamValue;
}
}
}
return pVal;
}

4、从 fck_image.html 将参数传递到 Uploader.cs

fck_image.html 会将图片放在一个INPUT中,然后POST到服务端(ASPX),INPUT的名称为NewFile,服务端ASPX对应的Class就是Uploader.cs,现在要添加一个ImageUploadAssembly属性值一并提交给服务端,在此使用GET方式在URL后附加一个参数,修改 /editor/dialog/fck_image/fck_image.js 的 window.onload 函数,将参数值附加到 form.action,如下:

window.onload = function()
{
//....部分代码略
GetE('frmUpload').action = FCKConfig.BasePath + "filemanager/image/upload.aspx?ImageUploadAssembly=" + dialog.getUrlParameter("ImageUploadAssembly");
}

OK,至此在Uploader.cs里用Request.QueryString[“ImageUploadAssembly”]就能获取到我们设置的属性值了。

接下来是根据该属性的值动态构造出对象,这里利用了.Net的反射,帖出Uploader.cs代码:

using System;
using System.Reflection;
using System.Web;

namespace FredCK.FCKeditorV2.ImageUpload
{
///
/// 图片上传接口
/// 接收GET参数 ImageUploadAssembly,根据参数值反射出自定义图片处理对象
///
public class Uploader : System.Web.UI.Page
{
protected override void OnLoad(EventArgs e)
{
if (Request.QueryString["ImageUploadAssembly"] != null)
{
string ass = Request.QueryString["ImageUploadAssembly"].Trim();
if (ass != string.Empty)
{
string[] assArray = ass.Split(',');

if (assArray.Length == 2)
{
if (assArray[0].Trim() != string.Empty &amp;&amp; assArray[1].Trim() != string.Empty)
{
//获取上传文件
HttpPostedFile hpf = Request.Files["NewFile"];
if (hpf != null)
{
FredCK.FCKeditorV2.ImageUpload.Base customUploader = null;
bool ok = true;

try
{
customUploader = (FredCK.FCKeditorV2.ImageUpload.Base)this.createObject(assArray[0].Trim(), assArray[1].Trim());
customUploader.PostFile = hpf;
}
catch
{
ok = false;
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "自定义处理类不存在或设置的值不正确!&lt; /span&gt;");
}

if (ok)
{
//调用自定义类中的Save方法&lt; /span&gt;
customUploader.Save();
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "未获取上传文件!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "参数有误!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "参数有误!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "参数有误!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "参数有误!");
}
}

///
/// 反射创建对象
///
///
<span> </span>程序集名称         ///
<span> </span>完整类名称         ///
private object createObject(string assemblyName, string classFullName)
{
return Assembly.Load(assemblyName).CreateInstance(classFullName);
}
}
}

至此上传逻辑告一段落,因前面的描述和代码即有客户端的又有服务端的,并且只是核心代码片断,不知各位有没有看明白。总结一下这部分的逻辑:在客户端定义一个参数,将该参数传递到服务端,服务端根据参数值构造对象。如下图所示:

Step2.自定义上传处理类的约束

很庆幸可以在.Net里用一个类来处理用户的上传,并且有反射这样的技术来动态构造对象。这段时间在写一些ANSI C,我想也只有在写C的时候才能越发体会C#、高级语言、OOP的好。所有代码都是对现实一件事情的抽象,用C#你的代码可以跟思维同步,而如果用C你就 得考虑计算机是否明白和接受你的思维和代码了。

进入正题,分析一下自定义上传处理类的逻辑:首先得接收文件,.Net中是一个HttpPostedFile对象;然后对文件进行处理、保存等系列 操作;最后将结果告诉FCKEditor,由它来控制UI及用户交互。把这些必需的逻辑封装成一个抽象父类,每个用户自定义的上传处理类必需从该类继承, 并实现相应的抽象方法:

using System;
using System.Web;

namespace FredCK.FCKeditorV2.ImageUpload
{
///
/// 图片上传的抽象基类
///
public abstract class Base : System.Web.UI.Page
{
private System.Web.HttpPostedFile mPostFile;

///
/// 上传文件
///
public System.Web.HttpPostedFile PostFile {
get { return this.mPostFile; }
set { this.mPostFile = value; }
}

///
/// 文件上传结束回调客户端
///
///
<span> </span>上传是否成功         ///
<span> </span>上传完成后该文件的URL&lt; /span&gt;         ///
<span> </span>自定义消息         public static void SendFileUploadResponse(bool isSucceed, string fileUrl, string customMsg)
{
System.Web.HttpContext.Current.Response.Clear();
System.Web.HttpContext.Current.Response.Write("

<script type="text/javascript">// <!&#91;CDATA&#91;
");
            System.Web.HttpContext.Current.Response.Write(@"(function(){var d=document.domain;while (true){try{var A=window.top.opener.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();");
            System.Web.HttpContext.Current.Response.Write("window.parent.OnUploadCompleted(" + isSucceed.ToString().ToLower() + ", '" + fileUrl + "', '" + customMsg + "');");
            System.Web.HttpContext.Current.Response.Write("
// &#93;&#93;></script>");
System.Web.HttpContext.Current.Response.End();
}

///
/// 图片文件处理及保存
/// 无论结果如何,类的逻辑终点处必需调用 SendFileUploadResponse 以响应FCK客户端
///
public abstract void Save();

}//end class
}

最后是自定义上传处理类,里面可以根据自己情况添加任何逻辑,但它必须继承于FredCK.FCKeditorV2.ImageUpload.Base,并且在处理终点调用SendFileUploadResponse方法,以告之Fck客户端进行后继处理,如下代码示例了对上传图片大小、文件类型验证、文件保存:

using System;
using System.Web;

namespace FCKEditorSimpleNet.Web.MyImageUploadHandler
{
public class Default : FredCK.FCKeditorV2.ImageUpload.Base
{
///
/// 允许的上传图片大小(KB)
///
private const int FILE_MAX = 1024;

public override void Save()
{
if (this.PostFile != null)
{
if (this.PostFile.ContentLength &gt; 0)
{
if (this.PostFile.ContentLength &lt;= FILE_MAX * 1024)
{
if (this.PostFile.ContentType == "image/pjpeg" || this.PostFile.ContentType == "image/jpeg" || this.PostFile.ContentType == "image/gif" || this.PostFile.ContentType == "image/bmp" || this.PostFile.ContentType == "image/png")
{
//图片处理扩展

//保存
string fileName = Guid.NewGuid().ToString() + ".jpg";
string path = HttpRuntime.AppDomainAppPath + @"fck_upload\";
this.PostFile.SaveAs(path + fileName);

//客户端响应
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(true, "/fck_upload/" + fileName, "图片上传成功!");
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "图片格式不正确!目前支持JPG、GIF、BMP与PNG格式");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "图处大小不能超过" + FILE_MAX.ToString() + "KB!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "未获取图片数据!");
}
}
else
{
FredCK.FCKeditorV2.ImageUpload.Base.SendFileUploadResponse(false, "", "未获取图片对象!");
}
}

}//end class
}

DEMO下载

点击下载FCKEditorSimpleNet 2.65

结束语

从学习的角度对FCKEditor原代码进行分析和作了部分修改,因FCKEditor是一个复杂的基于JS的开源软件,更多细节难以在文章中描 述,可下载查看DEMO深入了解,希望对有需要的朋友带来帮助。同时因个人能力、时间、精力有限,不足之处还请指正,后继也会进一步完善本文,包括 FCKEditor的JS框架、配置、Dialog实现、JS拖拽实现、窗体关系、多语言实现……每个细节几乎都能作为一个独立项来研究,之后再 逐渐补充完整。

[转载]LINQ学习之旅——第一站Linq To Objects基础

mikel阅读(916)

[转载]LINQ学习之旅——第一站”LTO”基础 – Rookie_J – 博客园.

通过前几节对LINQ中所涉及到的C#语言新特性的学习,我们已经做好了LINQ学习之旅的准备。接着我们踏入LINQ学习之旅的第一 站”LTO”,即Linq To Object,它是用于操作内存对象的LINQ编程接口,针对内存中的集合进行操作。所以从今天开始以及之后的几节内容都将围绕Linq To Object这个主题展开讲解。那么今天所讲的主要内容是有关于Linq To Object中的一些基本概念:IEnumerable<T>泛型接口、序列以及标准查询操作符。其中标准查询操作符在这里只做概念上的统一 讲解,而对于单个具体的标准查询操作符将在后续几节里进行详细地讲解。

泛型接口IEnumerable<T>与IEnumerable接口类似,允许对接口内部的元素进行列举操作,集合只要是实现了接口IEnumerable<T>或IEnumerable就可以对其进行查询。而序列则是表示一个实现了接口IEnumerable<T>的集合对象。同时Linq To Object中的标准查询操作符都是针对序列的,标准查询操作符实质上就是一些扩展方法, 这些扩展方法在静态类System.Linq.Enumerable中,其原型的第一个参数(即带this修饰符的参数)是 IEnumerable<T>类型,所以这些扩展方法可以直接在IEnumerable<T>的实例对象上直接调用。但并不是所 有的标准查询操作符都是扩展方法,也有一些是普通的静态方法,而这些静态方法一般是用来实现更为复杂的功能。

另外在早期的C#语言里的一些集合类型,它们实现的是IEnumerable接口,而并不支持IEnumerable<T>接口,这就使得不能在这些类型集合对象上使用标准查询操作符,为了解决这个问题,C#中提供个Cast和OfType标准查询操作符把这些不支持IEnumerable<T>接口的集合类型转化为支持IEnumerable<T>接口的集合类型。这样标准查询操作符就可以适用于C#语言的所有集合类型。下面用标准查询操作符Select来说明标准查询操作符的一些重要特性:

static void Main(string[] args)
{
char[] ABC = new char[] { 'A', 'B', 'C', 'D', 'E' };

var ABCs = ABC.Select(i =&gt; i);//调用标准查询操作符Select

foreach (char c in ABCs)
{
Console.WriteLine(c);
}

Console.Read();
}

结果

示例中标准查询操作符Select返回了一个IEnumerable<T>集合对象(也称序列),但需要注意的是它所返回的序列对象内部包含的元素并不是在标准查询操作符被调用时立刻创建的

var ABCs = ABC.Select(i => i);//返回的是空序列

而是在执行列举该序列对象元素的代码时

foreach (char c in ABCs)
{
Console.WriteLine(c);
}

系统动态利用yield关键字来创建的。我们把这个特性称为延时查询。因此在标准查询操作符里有分延时标准查询操作符和非延时标准查询操作符。Select方法就属于延时标准查询操作符。接下来我用一个示例来具体说明延时标准查询操作符和非延时标准查询操作符在查询数据时的区别:

static void Main(string[] args)
{
//延时查询
Console.WriteLine("延时查询:");
Console.WriteLine("-----------------------------");

char[] ABC = new char[] { 'A', 'B', 'C', 'D', 'E' };
var ABCs = ABC.Select(i =&gt; i);//调用标准查询操作符Select

//第一次列举
Console.WriteLine("修改前:");
foreach (char c in ABCs)
{
Console.WriteLine(c);
}

ABC[0] = 'H';
ABC[1] = 'E';
ABC[2] = 'L';
ABC[3] = 'L';
ABC[4] = 'O';

//第二次列举
Console.WriteLine("修改后:");
foreach (char c in ABCs)
{
Console.WriteLine(c);
}

//非延时查询
Console.WriteLine("非延时查询:");
Console.WriteLine("-----------------------------");

char[] ABC1 = new char[] { 'A', 'B', 'C', 'D', 'E' };
var ABCs1 = ABC1.Select(i =&gt; i).ToArray();//调用标准查询操作符Select及ToArray

//第一次列举
Console.WriteLine("修改前:");
foreach (char c in ABCs1)
{
Console.WriteLine(c);
}

ABC1[0] = 'H';
ABC1[1] = 'E';
ABC1[2] = 'L';
ABC1[3] = 'L';
ABC1[4] = 'O';

//第二次列举
Console.WriteLine("修改后:");
foreach (char c in ABCs1)
{
Console.WriteLine(c);
}

Console.Read();
}

结果

其中方法ToArray()属于非延时标准查询操作符。从运行结果中可以清楚地看到,延时查询在数据源修改前后发生变化,而非延时查询则相同。那么为什么会出现这样的情况呢?原 来当调用Select操作符返回时,返回的是一个空的IEnumerable<T>集合对象,在执行foreach语句时相应的元素才一个一 个地生成,并填充到这个空的IEnumerable<T>集合对象里去,所以在第一次列举完成后,修改原始数据,在执行第二次列举时,修改的 数据便马上呈现出来。而ToArray()操作符其实返回的是T[]集合,而非IEnumerable<T>集合对象,来缓存数据集合,所以 尽管对数据源进行了修改,但列举的集合对象是同一个,结果也就没有变化了

最后讲解一下LINQ中这些标准查询操作符的方法原型里的一个委托类型的参数,这些委托类型定义在System.Linq中。以下是System.Linq空间里定义的委托:

public detegate T Func<T>();

public detegate T Func<A0,T>(A0 arg);

public detegate T Func<A0,A1,T>(A0 arg0,A1 arg1);

public detegate T Func<A0,A1,A2,T>(A0 arg0,A1 arg1,A2 arg2);

public detegate T Func<A0,A1,A2,A3,T>(A0 arg0,A1 arg1,A2 arg2,A3 arg3);

这些定义的委托在今后所要讲到的大部分标准查询操作符里都会涉及到,比如示例中所提到Select操作符,它的方法原型定义如下:

public static IEnumerable<T> Select<T,S>(

this IEnumerable<T> source,

Func<T,S> selector);

原型中的委托类型Func<T,S>就与上述委托类型的第二种相对应,其中的T就与public detegate T Func<A0,T>(A0 arg)中的A0相对应,而S则与public detegate T Func<A0,T>(A0 arg)中的T相对应,从中可以看出操作符Select能够接受一个匿名方法(或者Lambda表达式),且该匿名方法返回一个类型为S的值,正如上述代 码Select操作符接受的一个Lambda表达式i=>i,就是代入一个Char类型的值i,并返回同样类型的一个值i。从而可知,这些在 System.Linq空间里定义的委托的重要性。

[转载]简单解决Linq多条件组合问题

mikel阅读(978)

[转载]简单解决Linq多条件组合问题 – 51CTO.COM.

本文笔者用清晰的实例,解决了Linq多条件问题,思路十分的清晰,笔者也很细心的做了描述,希望能给你带来帮助。

最近有个项目准备功能改版,师兄吩咐:尽可能地做到万般皆Linq,所以很多东西都要从存储过程搬过来..昨天写评价功能的时候,碰到个Linq多条件叠加组合的问题,其需求如下:

多种查询评价的条件:

1.Linq多条件之查询类型:

收到的评价_买家给我的评价,收到的评价_卖家给我的评价,给出的评价_我给买家的评价,给出的评价_我给卖家的评价

public enum OpinionSearchType
{
收到的评价_买家给我的评价 = 0,
收到的评价_卖家给我的评价 = 1,
给出的评价_我给买家的评价 = 2,
给出的评价_我给卖家的评价 = 3
}

2.Linq多条件之评价类型:

全部,好评,中评,差评

public enum OpinionType
{
全部 = 0,
好评 = 1,
中评 = 2,
差评 = 3
}

3.Linq多条件之评价查询时间:

全部,一个星期内,一个月以内,六个月以内,六个月以外

public enum OpinionTime
{
全部 = 0,
一个星期内 = 1,
一个月以内 = 2,
六个月以内 = 3,
六个月以外 = 4
}

由于缓存的需要,要把Expression完成之后再传到接口那边获取相应的List<评价意见>.按照这样的看的话,

总共3个条件, 13个子条件, 排列组合之后, 会有80种的组合. – – 真的一个个组合去写的话,还真是累死人了..

左思右想,最好的方法就是把3个条件都拆开来,完成不同的Expression,到最后再把三个条件组合在一起成为一个新的Expression. 网上找到的比较都只是单条件的Parameter, 查了MSDN,才知道有个Expression.And(left, right)可以完成我的需求.利用.net3.5的扩展方法写成了一个组合Expression的方法,再重载了几个多参数的表达式,如下:

#region 表达式
public static Expression ContactExpressions(this Expression exp, params Expression[] exps) {
foreach (var e in exps) {
if (null == e) continue;
exp = Expression.And(exp, e);
}
return exp;
}
public static Expression&gt; ContactExpressions(this Expression exp, params Expression[] exps) {
foreach (var e in exps) {
if (null == e) continue;
exp = Expression.And(exp, e);
}
return (Expression&gt;)exp;
}

public static Expression&gt; ContactExpressions(this Expression exp, params Expression[] exps) {
foreach (var e in exps) {
if (null == e) continue;
exp = Expression.And(exp, e);
}
return (Expression&gt;)exp;
}
public static Expression&gt; ContactExpressions(this Expression exp, params Expression[] exps) {
foreach (var e in exps) {
if (null == e) continue;
exp = Expression.And(exp, e);
}
return (Expression&gt;)exp;
}

public static Expression&gt; ContactExpressions(this Expression exp,
params Expression[] exps) {
foreach (var e in exps) {
if (null == e) continue;
exp = Expression.And(exp, e);
}
return (Expression&gt;)exp;
}

#endregion

有了这几个方法进行Linq多条件查询,原本的需求就可以迎刃而解了:

    Expression< span="">bool>> expSearchType = null;  <>
     Expression< span="">bool>> expOpinionType = null;  <>
    Expression< span="">bool>> expOpinionTime = null;  <>
    switch (searchType) {  
        case OpinionSearchType.给出的评价_我给买家的评价:  
            expSearchType = Y => Y.UserID == userID && !Y.IsSeller;  
             break;  
        case OpinionSearchType.给出的评价_我给卖家的评价:  
          expSearchType = Y => Y.UserID == userID && Y.IsSeller;  
            break;  
         case OpinionSearchType.收到的评价_买家给我的评价:  
            expSearchType = Y => Y.ToUserID == userID && !Y.IsSeller;  
            break;  
        case OpinionSearchType.收到的评价_卖家给我的评价:  
            expSearchType = Y => Y.ToUserID == userID && !Y.IsSeller;  
             break;  
     }  
     switch (opinType) {  
         case OpinionType.好评:  
             expOpinionType = Y => Y.OpinionType == 0;  
             break;  
        case OpinionType.中评:  
             expOpinionType = Y => Y.OpinionType == 1;  
             break;  
         case OpinionType.差评:  
             expOpinionType = Y => Y.OpinionType == 2;  
             break;  
     }   
     switch (opinTime) {  
         case OpinionTime.一个星期内:  
             expOpinionTime = Y => (DateTime.Now - Y.OpinionTime).Days <= 7;  
             break;  
         case OpinionTime.一个月以内:  
             expOpinionTime = Y => (DateTime.Now - Y.OpinionTime).Days <= 30;  
             break;  
         case OpinionTime.六个月以内:  
             expOpinionTime = Y => (DateTime.Now - Y.OpinionTime).Days <= 180;  
             break;  
         case OpinionTime.六个月以外:  
             expOpinionTime = Y => (DateTime.Now - Y.OpinionTime).Days > 180;  
             break;  
     }  
      //GetPaged(params) 这个方法是用来获取列表并支持缓存保存的.  
     return GetPaged(expSearchType.ContactExpressions< span="">bool>(expOpinionType, expOpinionTime),  <>
         userID.UserTablePrefx(), true, pageIndex, pageSize); 

以上就是通过Linq实例解析Linq的另类用法,然后解决Linq多条件组合问题。

[转载]FlexPaper+SWFTools 实现仿百度文库及一些小问题

mikel阅读(1011)

[转载]FlexPaper+SWFTools 实现仿百度文库及一些小问题 – 不会飞的猪 – 博客园.

网上已有很多FlexPaper仿百度文库的一些文章,园子里也有很多大牛的详细教程。

结合这次做的例子,在这里详细记录一下使用Flexpaper实现仿百度文库的效果,及自己在跟着园子里的教程做的时候,遇到的一些小问题。希望能给初次 接触或者遇到同样问题的同学们提供一些小小的帮助。(描述不足之处,请大家多多见谅,毕竟是第一次在园子里写文章)。

1.准备工作:下载FlexPaper及PDF转换工具pdf2swf.exe

Flexpaper下载地址:下载 (我下的是1.4.5 Flash Version (release notes))

Pdf2swf工具下载:下载 (我下的是swftools-2011-01-23-1815.exe下载后,安装)

2.运行官方的DEMO及Flexpaper的用法

打开刚刚下载好的Flexpaper文件夹,打开里面的例子 index.html,

(1)运行官方的例子,不能加载任何的.swf(包括例子中的swf)(如果能正常显示的话,请跳过这一步)

运行官方例子的时起,连所带的Paper.swf都不能加载成功,只见到右上角的圈圈一直在动,处于等待的状态。如下图:

这种情况,是由于Flexpaper还没获得Adobe Flash的信任,这时你可以前住http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04a.html#119065 为FlexPaper添加信任.进入网页之后 ,点击左边的 Global Security Settings papel 这个选项,然后在右边(如下图)

为你的例子所在的文件夹添加信任。然后你再运行官方的例子,就会发现可以运行成功啦。

(2)Flexpaper的用法

我们可以查看index.html源代码,这里不多说,直接说它的用法吧:

在前端的用法如下:

<!--首先要引入jquery库及相关的js-->

<script src="js/jquery.js" type="text/javascript"></script> <script src="js/flexpaper_flash.js" type="text/javascript"></script>
<script src="js/flexpaper_flash_debug.js" type="text/javascript"></script> 

html中的代码,只需声明一个a标签即可


<div style="position: absolute; left: 10px; top: 10px;"><a id="viewerPlaceHolder" style="width: 660px; height: 480px; display: block;"></a> <script type="text/javascript">// <!&#91;CDATA&#91;
                 var fp = new FlexPaperViewer(                          'FlexPaperViewer',                          'viewerPlaceHolder',     对应于a 标签的id                          { config : {                          SwfFile : escape('ajax.swf'),  这句是关键: SwfFile: 指示导入的.swf的路径                          Scale : 0.6,                          ZoomTransition : 'easeOut',                          ZoomTime : 0.5,                          ZoomInterval : 0.2,                          FitPageOnLoad : true,                          FitWidthOnLoad : false,                          PrintEnabled : true,                          FullScreenAsMaxWindow : false,                          ProgressiveLoading : false,                          MinZoomSize : 0.2,                          MaxZoomSize : 5,                          SearchMatchAll : false,                          InitViewMode : 'Portrait',                          ViewModeToolsVisible : true,                          ZoomToolsVisible : true,                          NavToolsVisible : true,                          CursorToolsVisible : true,                          SearchToolsVisible : true,                            localeChain: 'en_US'                          }}); // --&gt;
// &#93;&#93;></script></div>

基本上,这样就可以在前端显示你的Flexpaper了.

(3)只能加载官方例子所带的swf,但不能加载其他或自己的swf

当我们想要FlexPaper加载我们自己的swf时,发现一直处于等待状态,无法显示,检查swf的路径也没错,但就是一直无法加载。这种情况,很有可 能就是.swf文件的版本问题啦。我们运行SWFTools的图形工具(gpdf2swf.exe)把pdf转换为swf,但是转换后的swf也不能被 Flexpaper正常加载,如果你确保路径没错的话,那就是版本的问题啦。

我们可以查看转换时swf的版本,我们可以点击 Edit->Options->Viewer

可以看到它帮我们转换为SWF的版本有 7,8两项,但这都是不能正常在FlexPaper中加载的,我们必须把pdf转换为版本为9的swf,才能让它在Flexpaper中显示。那么如何转换为9的呢?我自己是使用cmd命令行工具来手工转换的.只须在命令行添加 “-s flashversion=9” 这句,就可以转换为9的版本啦.示例如下:

参数说明如下:

(SWFTools我的默认安装路径是: E:\Program Files\)

SWFTools:pdf2swf.exe工具所在的文件夹,

-t: 源文件路径,即待转换的pdf文件路径。

-s: 设置参数,这里我们设置为 flashversion=9 ,即可以转换为9 的版本啦。

-o: 输出文件的路径,这里我输出到F:盘下

好了,这样就转换成功啦,你就可以得到一个.swf的文件,你只需拷到你下载的Demo里,配置好路径后,你就会发现FlexPaper可以正常加载你的swf啦.

在这里稍微强调一个小问题文件夹名字带有空格

对于 -t ,-o 这两个参数,就是对于源文件路径来输出文件路径的问题,如果你的文件夹的名字中,有空格的话,这样是不能找到你的文件的。

比如说:我要转换的pdf源文件在 E:\Program Files\SWFTools\这个文件夹下,当我这样写 -t E:\Program Files\SWFTools\ajax.pdf 或 者是输出文件路径写成这样 – o E:\Program Files\SWFTools\ajax.swf  这样都是不能成功的,因为这里面有文件夹名含有空格,这应该是cmd命令的问题(这个我也不是很懂,希望有高手能说一下),这里你只有在路径的前后加上 又引号就能成功啦,如: -t “E:\Program Files\SWFTools\ajax.pdf” 或  -o “E:\Program Files\SWFTools\ajax.swf” 这样就OK啦.

3. ASP.NET 调用pdf2swf.exe把PDF转换为SWF的用法

代码如下:

private static void ConvertCmd(string fileName)
{

using (Process p = new Process())
{
string cmdStr = HttpContext.Current.Server.MapPath("~/SWFTools/pdf2swf.exe");
string savePath = HttpContext.Current.Server.MapPath("~/TestSWF/");
// @"""" 相当于一个双引号,之所以要加@"""" 就是为了防止要转换的过程中,文件夹名字带有空格,导致失败
string sourcePath = @"""" + savePath + fileName + @"""";
string targetPath = @"""" + savePath + fileName.Substring(0, fileName.LastIndexOf(".")) + ".swf" + @"""";
string argsStr = "  -t " + sourcePath + " -s flashversion=9 -o " + targetPath;
//调用新进程 进行转换
ProcessStartInfo psi = new ProcessStartInfo(cmdStr, argsStr);
p.StartInfo = psi;
p.Start();
p.WaitForExit();
}
}

 其中,我的pdf文件存放在~/TextSWF/文件夹下,转换后的swf也是存放在同一文件夹下。

按照以上的做法,应该没什么问题,就可以做出类似于百度文库的效果了。

好了,第一次写文章,难免会有疏漏不足这处,希望各位高手不吝啬指出与赐教.

最后附上一个简单的小例子源码:PDFViewer.zip

[转载]基于Jquery+Ajax+Json+高效分页

mikel阅读(993)

[转载]基于Jquery+Ajax+Json+高效分页 – 欧西 – 博客园.

摘要

分页我相信大家存储过程分页已经很熟悉了,ajax更是耳熟能详了,更别说我们的json,等等。

如果说您没用过这些东东的话,我相信看完这篇博文会对您有帮助的,,如果有任何问题不懂或者有bug没问题,欢迎随时联系我,

同时也欢迎高手多给点意见,我不建议在喷子中成长。

本人QQ:364175837

前言

相信很多朋友都用过,JQuery的分页插件,我之前就用的JQuery.paper这个,如果有兴趣可以留下QQ,我发份简单的实例源码给您。

该代码是晚上匆忙中完成的,所以没怎么优化,但是主要作为实例来结合这些知识的一个综合运用。好了废话不多说,直接上代码。

vs2010+SQL2005express

正文

首先我们创建一般处理程序,来读取数据库中内容,得到返回值.

创建文件,GetData.ashx.

我这里是用的存储过程,存储过程会再下面粘出来,至于数据只是实例,你们可根据需求自行读取数据

代码如下

&lt;%@ WebHandler Language="C#" Class="GetData" %&gt;

using System;
using System.Web;
using System.Data.SqlClient;
using System.Data;
using System.Collections.Generic;
using System.Web.Script.Serialization;
public class GetData : IHttpHandler {

public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
var pageIndex = context.Request["PageIndex"];
string connectionString = @"Data Source=KUSE\SQLEXPRESS;Initial Catalog=bookshop;Integrated Security=True";
//判断当前索引存不存在,如果不存在则获取记录的总数。
if (string.IsNullOrEmpty(pageIndex))
{
//获取查询记录总数的sql语句
string sql = "select count(-1) from books";
int count = 0;
int.TryParse(SqlHelper.ExecuteScalar(connectionString, System.Data.CommandType.Text, sql, null).ToString(), out count);
context.Response.Write(count);
context.Response.End();
}
//当根据索引获取数据
else
{
int currentPageIndex = 1;
int.TryParse(pageIndex, out currentPageIndex);
SqlParameter[] parms = new SqlParameter[] {
new SqlParameter("@FEILDS",SqlDbType.NVarChar,1000),
new SqlParameter("@PAGE_INDEX",SqlDbType.Int,10),
new SqlParameter("@PAGE_SIZE",SqlDbType.Int,10),
new SqlParameter("@ORDERTYPE",SqlDbType.Int,2),
new SqlParameter("@ANDWHERE",SqlDbType.VarChar,1000),
new SqlParameter("@ORDERFEILD",SqlDbType.VarChar,100)
};
parms[0].Value = "*";//获取所有的字段
parms[1].Value = pageIndex;//当前页面索引
parms[2].Value = 10;//页面大小
parms[3].Value = 0;//升序排列
parms[4].Value = "";//条件语句
parms[5].Value = "ID";//排序字段
List list = new List();
using (SqlDataReader sdr = SqlHelper.ExecuteReader(connectionString, CommandType.StoredProcedure, "PAGINATION", parms))
{
while (sdr.Read())
{
list.Add(new Book { Title = sdr[2].ToString(), Auhor = sdr[2].ToString(), PublishDate = sdr[4].ToString(), ISBN = sdr[5].ToString() });
}
}

context.Response.Write(new JavaScriptSerializer().Serialize(list).ToString());//转为Json格式
}
}

public bool IsReusable {
get {
return false;
}
}

}

public class Book
{

public string Title { get; set; }

public string Auhor { get; set; }

public string PublishDate { get; set; }

public string ISBN { get; set; }
}

显示数据页面—-异步请求获取数据,基于JQuery

创建页面Show.htm

<div>/*显示数据内容*/
<table id="content"></table>
/*显示分页条*/

</div>

js代码:

$(function () {

$.post("GetData.ashx", null, function (data) {

var total = data;
PageClick(1, total, 3);
});

PageClick = function (pageIndex, total, spanInterval) {
$.ajax({
url: "GetData.ashx",
data: { "PageIndex": pageIndex },
type: "post",
dataType: "json",
success: function (data) {

//索引从1开始
//将当前页索引转为int类型
var intPageIndex = parseInt(pageIndex);

//获取显示数据的表格
var table = $("#content");
//清楚表格中内容
$("#content tr").remove();

//向表格中添加内容
for (var i = 0; i &lt; data.length; i++) {
table.append(
$("
" +
data[i].Title
+ " " +
data[i].Auhor
+ " " +
data[i].PublishDate
+ " " +
data[i].ISBN
+ "

")
);
} //for

//创建分页
//将总记录数结果 得到 总页码数
var pageS = total
if (pageS % 10 == 0) pageS = pageS / 10;
else pageS = parseInt(total / 10) + 1;
var $pager = $("#pager");

//清楚分页div中的内容
$("#pager span").remove();
$("#pager a").remove();

//添加第一页
if (intPageIndex == 1)
$pager.append("<span class="disabled">第一页</span>");
else {
var first = $("<a href="javascript:void(0)">第一页</a>").click(function () {
PageClick($(this).attr('first'), total, spanInterval);
return false;
});
$pager.append(first);
}

//添加上一页
if (intPageIndex == 1)
$pager.append("<span class="disabled">上一页</span>");
else {
var pre = $("<a href="javascript:void(0)">上一页</a>").click(function () {
PageClick($(this).attr('pre'), total, spanInterval);
return false;
});
$pager.append(pre);
}

//设置分页的格式  这里可以根据需求完成自己想要的结果
var interval = parseInt(spanInterval); //设置间隔
var start = Math.max(1, intPageIndex - interval); //设置起始页
var end = Math.min(intPageIndex + interval, pageS)//设置末页

if (intPageIndex &lt; interval + 1) {                             end = (2 * interval + 1) &gt; pageS ? pageS : (2 * interval + 1);
}

if ((intPageIndex + interval) &gt; pageS) {
start = (pageS - 2 * interval) &lt; 1 ? 1 : (pageS - 2 * interval);

}

//生成页码
for (var j = start; j &lt; end + 1; j++) {
if (j == intPageIndex) {
var spanSelectd = $("<span class="current">" + j + "</span>");
$pager.append(spanSelectd);
} //if
else {
var a = $("<a href="javascript:void(0)">" + j + "</a>").click(function () {
PageClick($(this).text(), total, spanInterval);
return false;
});
$pager.append(a);
} //else
} //for

//上一页
if (intPageIndex == total) {
$pager.append("<span class="disabled">下一页</span>");

}
else {

var next = $("<a href="javascript:void(0)">下一页</a>").click(function () {
PageClick($(this).attr("next"), total, spanInterval);
return false;
});
$pager.append(next);
}

//最后一页
if (intPageIndex == pageS) {
$pager.append("<span class="disabled">最后一页</span>");

}
else {
var last = $("<a href="javascript:void(0)">最后一页</a>").click(function () {
PageClick($(this).attr("last"), total, spanInterval);
return false;
});
$pager.append(last);
}

} //sucess

}); //ajax

}; //function

});   //ready

分页样式—-如果有感兴趣的,我这里还有20几套分页样式,可以留下QQ

<!--
DIV.yahoo2 {
PADDING-RIGHT: 3px; PADDING-LEFT: 3px; FONT-SIZE: 0.85em; PADDING-BOTTOM: 3px; MARGIN: 3px; PADDING-TOP: 3px; FONT-FAMILY: Tahoma,Helvetica,sans-serif; TEXT-ALIGN: center
}
DIV.yahoo2 A {
BORDER-RIGHT: #ccdbe4 1px solid; PADDING-RIGHT: 8px; BACKGROUND-POSITION: 50% bottom; BORDER-TOP: #ccdbe4 1px solid; PADDING-LEFT: 8px; PADDING-BOTTOM: 2px; BORDER-LEFT: #ccdbe4 1px solid; COLOR: #0061de; MARGIN-RIGHT: 3px; PADDING-TOP: 2px; BORDER-BOTTOM: #ccdbe4 1px solid; TEXT-DECORATION: none
}
DIV.yahoo2 A:hover {
BORDER-RIGHT: #2b55af 1px solid; BORDER-TOP: #2b55af 1px solid; BACKGROUND-IMAGE: none; BORDER-LEFT: #2b55af 1px solid; COLOR: #fff; BORDER-BOTTOM: #2b55af 1px solid; BACKGROUND-COLOR: #3666d4
}
DIV.yahoo2 A:active {
BORDER-RIGHT: #2b55af 1px solid; BORDER-TOP: #2b55af 1px solid; BACKGROUND-IMAGE: none; BORDER-LEFT: #2b55af 1px solid; COLOR: #fff; BORDER-BOTTOM: #2b55af 1px solid; BACKGROUND-COLOR: #3666d4
}
DIV.yahoo2 SPAN.current {
PADDING-RIGHT: 6px; PADDING-LEFT: 6px; FONT-WEIGHT: bold; PADDING-BOTTOM: 2px; COLOR: #000; MARGIN-RIGHT: 3px; PADDING-TOP: 2px
}
DIV.yahoo2 SPAN.disabled {
DISPLAY: none
}
DIV.yahoo2 A.next {
BORDER-RIGHT: #ccdbe4 2px solid; BORDER-TOP: #ccdbe4 2px solid; MARGIN: 0px 0px 0px 10px; BORDER-LEFT: #ccdbe4 2px solid; BORDER-BOTTOM: #ccdbe4 2px solid
}
DIV.yahoo2 A.next:hover {
BORDER-RIGHT: #2b55af 2px solid; BORDER-TOP: #2b55af 2px solid; BORDER-LEFT: #2b55af 2px solid; BORDER-BOTTOM: #2b55af 2px solid
}
DIV.yahoo2 A.prev {
BORDER-RIGHT: #ccdbe4 2px solid; BORDER-TOP: #ccdbe4 2px solid; MARGIN: 0px 10px 0px 0px; BORDER-LEFT: #ccdbe4 2px solid; BORDER-BOTTOM: #ccdbe4 2px solid
}
DIV.yahoo2 A.prev:hover {
BORDER-RIGHT: #2b55af 2px solid; BORDER-TOP: #2b55af 2px solid; BORDER-LEFT: #2b55af 2px solid; BORDER-BOTTOM: #2b55af 2px solid
}

-->

分页存储过程—PAGINATION

CREATE PROCEDURE [dbo].[PAGINATION]
@FEILDS  VARCHAR(1000),--要显示的字段
@PAGE_INDEX INT,--当前页码
@PAGE_SIZE INT,--页面大小
@ORDERTYPE BIT,--当为0时 则为 desc 当为1 时 asc
@ANDWHERE VARCHAR(1000)='',--where语句 不用加where
@ORDERFEILD VARCHAR(100) --排序的字段
as
DECLARE @EXECSQL VARCHAR(2000)
DECLARE @ORDERSTR VARCHAR(100)
DECLARE @ORDERBY VARCHAR(100)
BEGIN
set NOCOUNT on
IF @ORDERTYPE = 1
BEGIN
SET @ORDERSTR = ' &gt; ( SELECT MAX(['+@ORDERFEILD+'])'
SET @ORDERBY = 'ORDER BY '+@ORDERFEILD+' ASC'
END
ELSE
BEGIN
SET @ORDERSTR = ' &lt; ( SELECT MIN(['+@ORDERFEILD+'])'
SET @ORDERBY = 'ORDER BY '+@ORDERFEILD+' DESC'
END

IF @PAGE_INDEX = 1 --当页码是第一页时直接运行,提高速度
BEGIN
IF @ANDWHERE=''
SET @EXECSQL = 'SELECT TOP '+STR(@PAGE_SIZE)+' '+@FEILDS+' FROM [books] '+@ORDERBY
ELSE
SET @EXECSQL = 'SELECT TOP '+STR(@PAGE_SIZE)+' '+@FEILDS+' FROM [books] WHERE '+@ANDWHERE+' '+ @ORDERBY
END
ELSE
BEGIN
IF @ANDWHERE=''
BEGIN      --以子查询结果当做新表时 要给表名别名才能用
SET @EXECSQL = 'SELECT TOP'+STR(@PAGE_SIZE)+' '+@FEILDS+' FROM [books] WHERE '+@ORDERFEILD+
@ORDERSTR+' FROM (SELECT TOP '+STR(@PAGE_SIZE*(@PAGE_INDEX-1))+' '+@ORDERFEILD+
' FROM [books] '+@ORDERBY+') AS TEMP) '+ @ORDERBY
END
ELSE
BEGIN
SET @EXECSQL = 'SELECT TOP'+STR(@PAGE_SIZE)+' '+@FEILDS+' FROM [books] WHERE '+@ORDERFEILD+
@ORDERSTR+' FROM (SELECT TOP '+ STR(@PAGE_SIZE*(@PAGE_INDEX-1))+' '+@ORDERFEILD+
' FROM [books] WHERE '+@ANDWHERE+' '+@ORDERBY+') AS TEMP) AND '+@ANDWHERE+' '+ @ORDERBY
END
END
EXEC (@EXECSQL)--这里要加括号
END

运行效果图

补充:

终于,大功告成,不容易啊!有几个地方忘记给注释说明了,大家可能不理解。

PageClick(1, total, 3); 这个函数,第一个参数是当前页码,第一调用为第一页,这个不用管,total:表示总记录数,第三个参数表示:当前索引和旁边个间隔几页

OK,今天到此为止,第一次写东东,写的不好,技术含量也有限,忘读此博文者见谅。

[转载]IIS日志分析工具 IISLogViewer V2.0 发布

mikel阅读(909)

[转载]IIS日志分析工具 IISLogViewer V2.0 发布 – 路过秋天 – 博客园.

本次发布IIS日志分析工具[IISLogViewer] V2.0版本。

下面单刀直入,看下 IIS日志分析工具 V2.0 版本所带来新的教程:

1:运行IISLogViewer.exe,启动IIS日志分析工具,界面如下图:

IIS日志访问工具[IISLogViewer]

2:可以选择单个IIS日志文件分析或整个站点文件夹进行分析,这里点击“批量文件夹”,选择要分析的IIS日志文件目录,如下图:

IIS日志访问工具[IISLogViewer]

2-1:选择好要分析的IIS日志文件或文件夹后,默认产生日志列表,列表包括日志文件名称和文件大小,如下图:

IIS日志访问工具[IISLogViewer]

3:点击“汇总统计”,可以统计列出的文件的基本访问情况信息,如下图,正在加载中:

IIS日志访问工具[IISLogViewer]

3-1:“汇总统计”加载完成后,将显示出所有文件各搜索引擎和非搜索引擎的基本访问信息,如下图:

IIS日志访问工具[IISLogViewer]

4:切换“查看明细”,可以选择日志文件可以针对单个IIS日志文件进行分析,这里示例按状态码进行分析,右侧显示日志的状态码统计,并有针对状态码的基本说明,如下图:

IIS日志访问工具[IISLogViewer]

5:双击“状态码”单元格,将进入“状态码明细”分析,可针对单个状态码进行分析,同时右侧,还可以按“搜索引擎分类”显示,点击右侧“列表单元格”,将在左侧下方显示IP详情,双击“IP详情”项,将弹出网页显示IP的所在地,如下图:

IIS日志访问工具[IISLogViewer]

6:除了按状态码分析,切换“24小时”,还可以按时段进行分析,右侧按时段显示24小时的各搜索引擎和非搜索引擎访问情况,如下图:

IIS日志访问工具[IISLogViewer]

7:双击“24小时”列表单元格内的信息,将弹出“24小时明细”,进行时段内更详细的分析,如下图,也可以按“搜索引擎分类”显示,单击列表单元格信息,也可显示IP详情,双击IP详情,同样会弹出网页显示IP地址。

IIS日志访问工具[IISLogViewer]

PS:在帮助菜单下,有状态码帮助文档,仅供参考。

本次版本介绍及教程到此为止,欢迎下载使用并提供反馈意见。

直接提供本地下载:点击下载

下载地址2:http://www.cyqdata.com/download/article-detail-42518

[转载]让Android应用程序支持安装到SD卡(APP2SD)

mikel阅读(924)

[转载]让Android应用程序支持安装到SD卡(APP2SD) – 熊猫82 – 博客园.

Android系统在2.1版本之前,应用程序是只能安装到机身内存(RAM)中,这一特性从某种角度上讲,阻止了Android的发展,因为RAM的空间是有限的,所以这一特性限制了应用程序的体积,也就限制了应用程序的功能。

自从Android 2.2版本开始,Android系统引入了一个全新的功能——APP2SD,这个功能让Android系统允许将应用程序安装到SD卡上面,一方面节约出更多的RAM空间,另一方面也允许应用程序扩大自身的体积。

但是仅仅Android系统支持APP2SD,这样还是不够的,应用程序如果不经过处理,仍然会一如既往的转进RAM里。这是因为Android系统保持向后兼容的特性。那么,如果让应用程序不装入RAM,而是装入SD卡呢?这需要在应用程序的Android清单文件manifest.xml的manifest元素里加入android:installLocation属性。其值为:perferExtenal或auto。

perferExtental——意味着此应用程序安装到扩展存储(通常就是SD卡),但是系统不能保证应用肯定会安装到扩展存储。如果扩展存储没有空间或者不可用,那么系统仍然会将应用程序安装到RAM里。

auto——意味着此应用程序可能安装到扩展存储,但是对安装位置没有特别偏好,将有系统根据自身算法,参考很多因素之后决定将此应用程序安装到哪里。

internalOnly——意味着此应用程序只能被安装到RAM。

一旦加入android:installLocation,不论其值为什么,用户都可以将应用程序在RAM和SD卡之间自由移动。当应用程序安装到SD卡后,需要注意以下几点:

1. 仅apk文件保存在SD卡上,其他所有用户的私有数据、数据库、经过优化的dex文件和释放的原生代码仍然保存在RAM上。

2. SD卡里的应用程序在进行安装时会被加密,因此安装在SD卡上的应用程序仅对安装这个应用程序的设备起效,在其他设备上将不能运行。

3. 当用户启用USB大容量存储以共享文件给计算机、或者卸载SD卡,那么所有安装在SD卡上的应用都会被立即结束。

由于安装在SD卡上的应用程序会受到SD卡状态的影响,所以为了确保应用程序的健壮性,在决定应用程序是否可以安装在SD卡上时,应该做到以下原则:

1. 有后台服务(Service)的应用程序,不应该支持APP2SD上。因为Service是前台界面不可见的,在卸载SD卡或者启用USB时,用户并无法知道该应用程序的Service是否处于活动状态。所以可能造成Service的意外终止。

2. 输入法程序不应该支持APP2SD。如果卸载SD或者启用USB,那么该输入法将被终止,并由系统默认输入法所替代。

3. 壁纸和包含桌面小工具(Widget)等常显于界面的应用程序不应该支持APP2SD。

4. AccountManager等与用户数据有关的应用程序不应该支持APP2SD,因为在SD卡正常挂载之前,程序都无法看到通过AccountManager创建的用户。

如果应用程序有Service、或支持桌面小工具等以上提到的功能时,应该尽可能避免被安装到SD卡上。一种保险的做法是指定android:installLocation的值为internalOnly。

由于APP2SD是Android 2.2版本的新特性,如果该应用程序被计划支持2.2之前的版本,做到向后兼容的话,那么需要进行以下步骤:

1. 在manifest元素里加入android:installLocation属性,并指定其值为perferExternal或者auto。

2. 指定andorid:minSdkVersion值,其值可以小于8(2.2版本之前)。

3. 更改该应用程序的build target为API Level 8,以使编译器能编译此应用程序。(必须)如果不指定build target,那么旧的Android库将无法理解android:installLocation属性,也就无法编译此应用。

实现以上三个步骤之后,那么如果此应用被安装到API Level低于8的设备上时,android:installLocation属性将被忽略。并且该应用会被安装于RAM上。

[转载]下拉框可输入+自动提示+支持键盘事件兼容IE,FF

mikel阅读(1200)

[转载]【下拉框可输入+自动提示+支持键盘事件兼容IE,FF】 – 从小就很拽 – 博客园.

下载DEMO:

今天公司一同事跑过来问我说会不会做下拉框可以输入,并且要输入一个 字就就要提示,要匹配的数据是下拉框里面拥有的值,我说以前做过,他就要求我帮他做一下,反正也没事就做了下,本来也没什么事,但是他一句话我石化了,他 说下拉框只有固定的8到10个值,我心想那有必要做这种吗,用手选还快,还提示过球球啊。最终明白了,万恶的客户,既然写了就放到园子里做下记录,可能对 某些朋友有用,上图看下。

没做美化,自己随便写的,有那里写的不够给力的,请大神们多指点,小弟在此先谢了。

$(document).ready(function() {
    var cus = 0;
    var classname = "";
    var arry = new Array();
    var $autocomplete = $("<ul class='autocomplete'></ul>").hide().insertAfter("#box4");
    $("#hoho").find("option").each(function(i, n) {
        arry[i] = $(this).text()
    });
    $("#box4").keyup(function(event) {
        if ((event.keyCode != 38) && (event.keyCode != 40) && (event.keyCode != 13)) {
            $autocomplete.empty();
            var $SerTxt = $("#box4").val().toLowerCase();
            if ($SerTxt != "" && $SerTxt != null) {
                for (var k = 0; k < arry.length; k++) {
                    if (arry&#91;k&#93;.toLowerCase().indexOf($SerTxt) >= 0) {
                        $("<li title=" + arry&#91;k&#93; + " class=" + classname + "></li>").text(arry[k]).appendTo($autocomplete).mouseover(function() {
                            $(".autocomplete li").removeClass("hovers");
                            $(this).css({
                                background: "#3368c4",
                                color: "#fff"
                            })
                        }).mouseout(function() {
                            $(this).css({
                                background: "#fff",
                                color: "#000"
                            })
                        }).click(function() {
                            $("#box4").val($(this).text());
                            $autocomplete.hide()
                        })
                    }
                }
            }
            $autocomplete.show()
        }
        var listsize = $(".autocomplete li").size();
        $(".autocomplete li").eq(0).addClass("hovers");
        if (event.keyCode == 38) {
            if (cus < 1) {
                cus = listsize - 1;
                $(".autocomplete li").removeClass();
                $(".autocomplete li").eq(cus).addClass("hovers");
                var text = $(".autocomplete li").eq(cus).text();
                $("#box4").val(text)
            } else {
                cus--;
                $(".autocomplete li").removeClass();
                $(".autocomplete li").eq(cus).addClass("hovers");
                var text = $(".autocomplete li").eq(cus).text();
                $("#box4").val(text)
            }
        }
        if (event.keyCode == 40) {
            if (cus < (listsize - 1)) {
                cus++;
                $(".autocomplete li").removeClass();
                $(".autocomplete li").eq(cus).addClass("hovers");
                var text = $(".autocomplete li").eq(cus).text();
                $("#box4").val(text)
            } else {
                cus = 0;
                $(".autocomplete li").removeClass();
                $(".autocomplete li").eq(cus).addClass("hovers");
                var text = $(".autocomplete li").eq(cus).text();
                $("#box4").val(text)
            }
        }
        if (event.keyCode == 13) {
            $(".autocomplete li").removeClass();
			$("#HTML").html("你选择的是<font color='red'>" + $(".autocomplete li").eq(cus).text()+"</font>");
			$autocomplete.hide();

        }
    }).blur(function() {
        setTimeout(function() {
            $autocomplete.hide()
        },
        3000)
    })
});
function setValue(index) {
    var ddl = document.getElementById("hoho");
    var Value = ddl.options[index].text;
    document.getElementById("box4").value = Value
	$("#HTML").html("你选择的是<font color='red'>" +Value+"</font>");
}
.hoho{width:197;height:20px!important; height:17px;margin-left:-180px!important; margin-left:-179px}
.sp{margin-left:179px;width:18px;overflow:hidden; }
.bo4{width:178px;position:absolute;left:0px!important;height:20px!important;top:0.5px!important; left:1px; top:0px; height:20px}
.autocomplete{list-style-type:none; margin:0px; padding:0px; border:#008080 1px solid }
.autocomplete li{font-size:12px; font-family:"Lucida Console", Monaco, monospace; font-weight:bold; cursor:pointer; height:20px; line-height:20px}
.hovers{ background-color:#3368c4; color:fff}
<table width="440" border="0" align="center">
  <tr>
    <td><div style="position:relative;">   
      <span  class="sp">   
            <select id="hoho" name="hoho"  class="hoho" onChange="setValue(this.selectedIndex)" >
            	  <option> ===请选择===</option>
                  <option value='0' >Java EE</option>
                  <option value='1' >Java SE</option>
                  <option value='2' >Java ME</option>
                  <option value='3' >Net</option>
                  <option value='4' >PHP</option>
                  <option value='5' >Ajax</option>
                  <option value='6' >JQuer</option>
             </select>
             
     </span>
     <input name="box4" id="box4" value="===请选择==="  class="bo4" >   
</div>
	</td>
    <td id="HTML" width="350">输入A试试</td>
  </tr>
</table>

[转载]Open API的资源集

mikel阅读(1018)

[转载]Open API的资源集 – 2012 – 博客园.

现在经常听到和使用到各种开放API,因此笔者对这些进行概要的汇总和整理,希望对有这些需求的有一定的参考价值。

什么是开放平台(Open Platform

互联网时代,把网站的服务封装成一系列计算机易识别的数据接口开放出去,供第三方开发者使用,这种行为就叫做Open API,提供开放API的平台本身就被称为开放平台。通过开放平台,网站不仅能提供对Web网页的简单访问,还可以进行复杂的数据交互,将它们的Web网站转换为与操作系统等价的开发平台。第三方开发者可以基于这些已经存在的、公开的Web网站而开发丰富多彩的应用。

开放平台分类

在众多的开放平台之中,根据所服务的主体不同,也将开放平台分为两类,一类是中心化开放平台,以Facebook、百度等为代表的开放平台,平台所提供的API主要是针对自身的网站提供应用开发接口,与之对接的应用只为自身网站服务;还有一种是分布式开放平台,在国外以Google提出的开放标准为代表,在国内则以Manyou为代表,这类平台在提供一个标准API后,即可将平台上的多个应用推向所有支持该标准的网站。

国内

http://open.taobao.com/index.htm

对外开放的API涵盖了淘宝核心交易和各项垂直业务的主要流程,同时API调用量保持快速增长趋势。

http://dev.app.360.cn/

360web应用开放平台还提供丰富的开放API扩展,以满足开发者批量上传应用的需求,目前支持API如下所示:

游戏类应用 视频类应用 小说类应用 杂志类应用

http://open.soft.360.cn

360软件开放平台是为所有软件企业和开发者提供的发布推广平台。

http://open.qq.com/

网站主推广 应用接入 商务营销 3大类

http://open.baidu.com/

百度数据开放平台 百度应用开放平台 地图、文库、知道等多个方面的开放

http://open.weibo.com/

帐号、应用等开放

国外

http://www.programmableweb.com/apis

截至目前有:3742 APIS 6014 Mashups

Advertising (52)

Feeds (23)

News (50)

Shopping (156)

Answers (11)

File Sharing (17)

Office (46)

Social (242)

Blog Search (8)

Financial (152)

Other (166)

Sports (41)

Blogging (49)

Food (15)

Payment (54)

Storage (43)

Bookmarks (31)

Games (50)

Photos (106)

Tagging (12)

Calendar (14)

Goal Setting (2)

PIM (24)

Telephony (154)

Chat (38)

Government (82)

Project Management (44)

Tools (181)

Database (29)

Internet (287)

Real Estate (33)

Travel (97)

Dating (2)

Job Search (28)

Recommendations (46)

Utility (72)

Dictionary (4)

Mapping (218)

Reference (184)

Video (106)

Email (83)

Media Management (15)

Retail (3)

Weather (15)

Enterprise (143)

Medical (26)

Search (107)

Widgets (26)

Events (40)

Messaging (106)

Security (62)

Wiki (13)

Fax (8)

Music (115)

Shipping (11)

基于各种API,可以进行Mashup

mashup是糅合,是当今网络上新出现的一种网络现象,将两种以上使用公共或者私有数据库的web应用,加在一起,形成一个整合应用。一般使用源应用的API接口,或者是一些rss输出(含atom)作为内容源,合并的web应用用什么技术,则没有什么限制。详细参考 http://baike.baidu.com/view/241257.htm

总结

现在做应用,真是有福了,这么多的开放资源可以共享和购买,节省了很多的自身开发投入,看来这些应用云是越来越进了。