[转载]如何走出信息黑洞?放掉那头大象! | GTDLife

mikel阅读(958)

[转载]如何走出信息黑洞?放掉那头大象! | GTDLife.

有兴趣的话,我们可以做个实验:

1、手头工作刚刚告一段落,想放松一下,看看新闻(或微博)

2、按照你习惯的方式开始浏览

3、等你下次回过神儿的时候看看消耗掉的时间,惊讶吗?

我在朋友不知情的情况下帮他做过统计,大约是48分钟,结果是:他的眼睛快瞪出来了,因为比他刚忙的正事儿还多用了10分钟。

我把这个叫做“进入信息黑洞”,即不由自主的花大量的时间在持续接受信息上

关键字是:不由自主+持续接受信息

打个比方的话就是:我只想找段绳子,最后却拉出一头大象

NewImage

如何走出信息黑洞,放掉那头大象呢?

我喜欢李中莹先生的一段话:

人事物归类能使得我们定出它们在我们人生里的意义与地位,以及让我们能有效地运用它们

–《重塑心灵》李中莹

这和我阅读信息的理念一致,就是:归类阅读,行动内化

这篇文章就分享我对这八个字的实践,开始吧……

分类阅读的方法

先上图:

NewImage

再来解释,我把每天接收到的信息分为三类:

  • 新闻资讯
  • 增进理解
  • 实践内化

同时也为这三类信息准备了三个抽屉:

  • 略读
  • 分析阅读
  • 行动阅读

现在分别打开每一个抽屉和大家分享下:

1、新闻资讯:包括社会新闻,我感兴趣领域的新闻等等,可以增加自己信息的广度,比如《HTC ONE发布会外媒评论汇总》,

略读:要领是5W,包括讲的什么事儿(What)?在哪里(Where)?何时发生(When)?牵扯到谁(Who)?为什么会这样(Why)?

目标:输入

2、增进理解:包括针对某个点的解释、分析文章,可以增加自己信息的深度,比如《从邮箱到网盘–看网盘的盈利方式》

分析阅读:要领是FM,包括框架结构(Framework),这里面又包括吸引我的观点和句子、文章的逻辑线、它在想说明什么问题, 我的观点( My opinion),最好的方式是用自己的语言再复述一次信息,就像让小孩子复述故事一样,加深印象、增强理解、加强表达。这种阅读信息的方式我通常都会在 手头准备好笔纸。

目标:联结

3、实践内化:包括自己觉得有趣,并且想亲身试试的信息,比如《Evernote技巧系列文章》

行动阅读:要领是PN,包括计划(plan)和下一步行动(next action),我会把这系列文章中我想学习的技巧列成一个清单,放到omni focus里面,设置一个时间挨个尝试。

目标:输出

归类阅读只有三步:

  • 判断信息的类型:看到一条信息,脑袋里面首先判断应该放到哪个抽屉里
  • 采用对应的方法:通常就是针对信息类型的不同问不同的问题,是拉开抽屉,把信息放进去的过程
  • 实现对应的目标:收放自如,实现预期的目标就是成功
归类阅读,行动内化 这种处理信息的方法让我头脑更加清晰,提取知识更加高效,虽然不同的人有不同的分类,归类原则也不一样,甚至使用的工具也不大相同,这些并不是最重要的。

管理信息的原则

我的原则有三个:

1、占主动:你可以感觉一下,每时每刻我们似乎都在和另一个自己对话:“买这件还是买那件?”“这件吧,颜色比 较亮”,要小心这种对话哦,它是一份觉察,也是一种引导,例如:“地铁上人真多……”,因为这个句式缺少具体的行动,所以大脑会选择做最舒服的事情,比如 游戏之类,那如果你有意识的在对话中加入行动:“地铁上人真多,读一下RSS吧”,那么你获取信息就更加主动,我们每时每刻都在不可避免的获取信息,那为 什么不主动一点?

2、可流动:同一领域的信息源我会让它们流动起来,这样有流入有流出,就成了活水保持新鲜,拿我读书来说,我书 架上固定的一个区域只放十本书,我把它定义为“可以反复阅读的top10”,一方面这10本书会越来越经典,越来越适合我,另一方面也不会枯燥,这种投票 选出最棒内容的模式从几年前Digg到豆瓣、知乎,一直都存在。所以比如说我的RSS订阅就会随时添加和修改,同样是科技类的博客,我最多保留2-3个, 多了就会焦虑,但这2-3个会随时调整。

3、加限制:庄子说:“吾生也有涯,而知也无涯,以有涯随无涯,殆已”,成人集中注意力的时间大约是20分钟,而我们要接收的信息是无限,所以我会给自己设置时间底线,这样一来就会驱动我们去筛选信息,阅读不同类型信息用不同的方法。

NewImage

假设我们的精力是油门,处理信息的方法是档位,那无原则、无方法的接收信息就是挂空挡踩油门,也就容易进入黑洞

如果你只是要找一段绳子而已,那么拿到了你所期待的长度,就请果断剪断绳子,放掉后面那头大象

结语:

我在一本书里读到“戒盈杯”这个概念:戒盈杯,酒器。杯心直立一龙首,外底部有一漏孔。入注水,浅,则滴水不漏;满,则水流殆尽。故称“戒盈杯”。

我在处理信息时最大的感触也是如此,贪多嚼不烂,不如把最感兴趣的一小部分消化吸收掉

人的烦恼伴随欲望而来,战胜自己欲望的人,烦恼就会少~产生这样的内心能量之后,再来用分类阅读的方法,相得益彰~~

让我们一起成长吧!

[转载]说说模块组织—kissy1.2最佳实践探索 | ria之家--RIA三部曲:jquery、ext、flex

mikel阅读(1024)

[转载]说说模块组织—kissy1.2最佳实践探索 | ria之家–RIA三部曲:jquery、ext、flex.

在kissy1.2的工程内,一切皆是模块,无论js还是css,都可以采用kissy1,2的loader异步加载进来,当然本文只讨论js的情况。

模块遵循的关键原则

1.模块书写必须符合基本书写格式

kissy1.2的js模块文件,基础模板如下:

KISSY.add(function(S) {
    function model(){
    }
    return model;
}, {requires:[]});
  • 文件的js代码起始必须是KISSY.add()
  • 定义的模块必须有return值
  • 使用requires数组处理模块依赖
2.模块功能单一性原则

在kissy1.2的模块系统中,我们希望一个模块只做一件事情,就像乐高积木一样,形态虽然是单一的,但可以快速组合成一个有趣的玩具。模块功能越多,逻辑越复杂,而越限制了组合的自由度。

关于功能单一性原则,后面明河讲解业务模块的时候会详细讲到。

3.一致性原则

每个公司、每个团队在代码规范上肯定会有差异,但明河建议在一个应用系统内代码风格和模块组织方式应该保持一致,就像一个工厂生产出来一样。

当有第三方维护者参与系统的维护时,应该review下他的代码是否风格一致。明河见过淘宝的老系统维护的人很多,造成一个js文件,糅杂着各种风格的代码。

最典型的情况是DOM模块(类YUI取DOM)和Node模块(类JQuery取DOM)的混用。

//Node方式获取元素
var itemEl = item.itemEl;
//DOM方式设置元素样式
DOM.css(favLay, 'height', itemEl.height());

(上面代码截自淘宝购物车)

这种混合使用方式容易给第三方维护者造成不必要的障碍,维护者需要思考用DOM还是用Node。

所以明河建议一个系统内只用一种方式处理DOM操作,相对而言明河更喜欢Node方式(被JQuery毒害的…)。

4.解耦和适当耦合原则

理想的模块,应该是低耦合、内聚、不存在强依赖。

而在一个模块系统中基本上不存在没有耦合的情况,模块的互相调用,公用模块的数据依赖等。

明河又要提到乐高玩具,每个积木都有接头,通过看似简单的拼接,组成富有变化的玩具,就像我们的模块暴露出来的接口,接口的调用,模块的通信,组成了我们应用的模块系统。

关于如何解耦,内容太多,明河有机会再详细整理想法出来。

需要特别留意的是别为了解耦,而过度设计,如果为了解耦而使用复杂的继承、设计复杂的接口,只会本末倒置,我们还是应该以模块代码的简单清晰为首要诉求。

过度设计的典型案例:v1.0的KF/Uploader(异步文件上传组件),设计非常复杂,有兴趣的同学可以看下我之前写的PPT。

点此下载

如何解耦,是门学问,没有标准答案,需要开发者在实践和重构中斟酌。

5.合理按需加载原则

kissy1.2后按需加载这个词明河听到很多,但在实践过程中,明河会思考这样一个问题:真的需要按需加载吗?

按需加载在缩短页面等待时间上有所帮助,但帮助并没有那么大。这里有应用场景的问题,有些系统特别适合按需加载,比如以前的我的淘宝或者QQ空间这种用户可定制功能模块的页面,但更多的系统其实按需加载在性能优化上收效甚微,比如淘宝退款这种纯表单和展示的页面。

(ps:按需加载适用于附加功能模块或后置操作功能模块的加载场景,比如用户点击按钮后加载overlay,然后实例化出现弹出层。)

所以使用按需加载即动态use()模块,应该先思考适用场景。

过多的动态use()会额外增加请求数。

kissy1.2模块类型

根据模块功能的差异,我们一般将kissy1.2的模块划分为:

1.入口模块职能单一性原则

所谓入口模块,指的是页面use()的模块,用于处理页面逻辑模块的依赖管理的模块和初始化模块脚本,比如在demo页面中:

KISSY.use('core/init',function(S,Apply){
})

core/init.js即页面的入口模块:

KISSY.add(function(S, Header) {
    return function(){
        Header();
    }
}, {requires:['../mods/header']});

假设有第三方维护者,首先找的会是入口模块,然后通过入口模块去了解依赖的业务模块,入口模块作为起点,应该尽量纯粹简单,有利于维护者快速定位。

建议:入口模块只用于入口目的,不包含任何业务逻辑。

糟糕设计案例:refund系统的退款申请页面

KISSY.add(function(S, DOM, Event, Header, RefundMoney) {
    S.mix(re, {
        _init : function() {
            new Header();
            new RefundMoney();
        },
        _showReasonSelect : function() {
        },
        _submitHanlder : function(ev) {
        },
        _changeMaxMoney : function() {
        }
    });
    return re;
}, {requires:['dom','event','rf/mods/header/header','rf/mods/refundMoney/ui']});

入口模板中包含了大量的页面逻辑,整个入口模块看过去臃肿,不好理解,没有起到很好的门面作用。

ps:明河也不推荐入口模块只用于处理requires模块依赖,比如下面的入口模块文件:

KISSY.add(function(S,Header, RefundMoney) {
}, {requires:['rf/mods/header/header','rf/mods/refundMoney/ui','rf/mods/refundInfo/ui']});

这种方式,当加载模块后立即执行模块逻辑,维护者没办法控制模块逻辑执行时机,这会给日后的维护造成一定的困难。

2.通用模块

多个页面公用的模块,都可以理解为通用模块,包含组件模块等。

如果是组件模块,明河推荐参考kissy组件的写法。

  • 模块返回类,使用new 方式调用;
  • 类继承kissy的Base类,使用getter/setter方式控制属性;
  • 暴露出自定义事件供外部监听;
  • 可配置性,思考扩展性;
  • 更为仔细的注释。

典型的组件(通用)模块类代码如下:

/**
 * @fileoverview 异步文件上传组件
 * @author 剑平(明河)<minghe36@126.com>
 **/
KISSY.add(function (S, Base, Node) {
     /**
     * @name Uploader
     * @class 异步文件上传组件,支持ajax、flash、iframe三种方案
     * @constructor
     * @extends Base
     * @param {Object} config 组件配置(下面的参数为配置项,配置会写入属性,详细的配置说明请看属性部分)
     */
    function Uploader(config) {
        var self = this;
        //调用父类构造函数
        Uploader.superclass.constructor.call(self, config);
    }
    S.extend(Uploader, Base, /** @lends Uploader.prototype*/{
         /**
         * 运行组件,实例化类后必须调用render()才真正运行组件逻辑
         * @return {Uploader}
         */
        render:function(){
        }
     },{ATTRS:/** @lends Uploader.prototype*/{
        /**
         * Button按钮的实例
         * @type Button
         * @default {}
         */
        button:{value:{} }
    })
     return Uploader;
}, {requires:['base', 'node']});
</minghe36@126.com>
3.业务模块

从改动的频率和大小来说,业务模块大于通用模块和入口模块,所以业务模块的划分和编写,必须从可维护性的角度出发。下面明河整理出一些建议供大家参考,欢迎反馈不同看法。

1) 划分业务模块时功能单一性原则

功能单一性原则要求一个业务模块只干一件事情,明河曾人review代码时,经常发现一个业务模块会包含大量的功能逻辑,整个模块看上去臃肿复杂,已经违背了模块化思想的本意,更像是传统js代码的异步加载版。

举个案例:淘宝评价页的remark-seller.js

remark-seller.js基本包含了评价页整张页面的大部分逻辑,包括晒照片,评价分值处理,字数统计、表单处理等等,代码也高达737行,过于臃肿,可维护性差。

很多同学之所以在一个模块内混合大量的逻辑,主要是在模块通信上有所顾虑(也有偷懒的嫌疑),业务模块多后,就要考虑模块之间如何调用通信的问题,处理不好,模块之间的耦合加深,也会对可维护性造成影响。

(PS:关于模块通信问题,后面明河会讲到)

2) 模块命名建议

先来看个业务模块众多的系统:淘宝下单页(坑爹般的复杂)


上图只是一小部分模块,下单的业务模块命名是文件夹名作为模块名,将模块分成ui层和逻辑层,可以理解为简单版的mvc,想法本身没什么问题,但大量的ui.js(大部分模块都有这个ui.js)在编辑代码和调试时造成了一定的麻烦。

比如编辑代码时,文件选项卡上都是ui.js,有时你都分不清楚是哪个模块的ui.js

上图截自明河的idea编辑器,第一眼根本找不到到底是哪个模块的ui.js,非常蛋疼的设计。

大量重复命名的模块文件,也给调试带来困难,老版本的chrome调试脚本时只显示文件名的,面对一坨ui.js,彻底囧了,所以都用firefox调试….

(PS:设计者的本意是好的,希望借助分层思想,来剥离复杂的下单逻辑,达到简化和平台化的目的,但现实是残酷的,看上去很酷…)

明河还见到有的应用的业务模块群出现大量index.js或base.js的,都面临同样的问题。

明河推荐YUI3的模块命名方式:

不要吝啬给模块js加前缀,我们追求的应该是清晰明确的模块文件名。

3) 模块返回值尽量一致

明河的建议是所有的模块js都返回一个类,外部使用模块都采用new实例的方式,比如下面的代码:

KISSY.add(function(S,Header, RefundMoney) {
     function Apply(){
    }
    return Apply;
}, {requires:['rf/mods/header/header','rf/mods/refundMoney/ui']});

再举个淘宝下单页的风格:

KISSY.add(function(S,Header, RefundMoney) {
    return {
        _init:function(){
        }
    };
}, {requires:['rf/mods/header/header','rf/mods/refundMoney/ui']});

当然每个人都有不一样的习惯,明河的建议是务必保持业务模块的组织风格和返回值一致,这样更有利于第三方理解代码。

[转载]iOS开发官方文档汇总 - Ider - 博客园

mikel阅读(1785)

[转载]iOS开发官方文档汇总 – Ider – 博客园.

程序员的学习过程是无止境的,程序员学习的途径是多样的。可以从视频教程中领悟,也可以从他人的代码中 理解。但当我们专注于某一个平台在开发的时候,对于某个API使用或者功能实现有疑问,通常简单的测试可以让我们知道可能的效果,异或网上搜索一下别人的 经验,不过最好的途径应该还是阅读官方的文档解释。常常可以更全面,具体的了解。当然也不排除有时候官方文档说了跟没说似地,或者根本说得文不对题。

半年多里一直从事Mobile游戏的开发,Android和iOS平台都有接触,不过还是以iOS为主,为了解决问题也查阅了不少的文档资料。就像以前学.NET一定会去MSDN,iOS上的问题就会先去查阅Apple Developer。总得感觉Library的文档还是MSDN做得比较好,说明详细例子也比较多。而Apple Developer上对于API的注释都不够详细,而且没有例子告之该如何使用。

apple-pdf不过Apple的文档也有它独有的好处,比如绝大部分的文档都有相应的PDF格式文件提供下载,对于离线阅读十分的方便,而且还可以在PDF做一些相应的笔记,针对某几页进行打印等等。

另外,Apple还会给出该API所对应的框架结构使用的文档,其中会有一些代码例子。但是其实那些例子依然不够,还是需要到其它地方寻找。但是这些讲述结构的文档却是体现出API设计的来源,所以十分值得阅读,只是需要花时间了理解和消化。Companion-guides

在此,我简单文类罗列了一些我在学习和搜索的过程中看到的那些Apple官方的iOS开发相关的文档资料。其中粗体显示的文档,是我个人觉得比较有用的部分。你可以点击链接进行在线阅读。异或在打开的页面点击右上角的PDF,下载下来阅读。因为之前一直都是下载来看的,都全部累积了下来,所以在文章的最后,我也放了链接提供全部文档的打包下载。

Computer Graphics

Objective-C

Programming Guide

User Interface

 

下载全部文档 Download all Documents

欢迎到我的新博客地址阅读本文

[转载]一个人的命运决定于每天晚上8点到10点之间 - jihate - 博客园

mikel阅读(1012)

[转载]一个人的命运决定于每天晚上8点到10点之间 – jihate – 博客园.

经典语录1:

哈佛有一个著名的理论:人的差别在于业余时间,而一个人的命运决定于晚上8点到10点之间。每晚抽出2个小时的时间用来阅读、进修、思考或参加有意的演讲、讨论,你会发现,你的人生正在发生改变,坚持数年之后,成功会向你招手。不要每天抱着QQ/MSN/游戏/电影/肥皂剧……奋斗到12点都舍不得休息,看就看一些励志的影视或者文章,不要当作消遣;学会思考人生,学会感悟人生,慢慢的,你的人生将会发生变化……

经典语录2:

无论你的收入是多少,记得分成五份进行规划投资:增加对身体的投资,让身体始终好用;增加对社交的投资,扩大你的人脉;增加对学习的投资,加强你的自信;增加对旅游的投资,扩大你的见闻;增加对未来的投资,增加你的收益。好好规划落实,你会发现你的人生逐步会有大量盈余。

 经典语录3:

过去的一页,能不翻就不要翻,翻落了灰尘会迷了双眼。有些人说不出哪里好,但就是谁都替代不了! 那些以前说着永不分离的人,早已经散落在天涯了。收拾起心情,继续走吧,错过花,你将收获雨,错过这一个,你才会遇到下一个。

  经典语录4:

被人误解的时候能微微的一笑,这是一种素养;受委屈的时候能坦然的一笑,这是一种大度;吃亏的时候能开心的一笑,这是一种豁达;无奈的 时候能达观的一笑,这是一种境界;危难的时候能泰然一笑,这是一种大气;被轻蔑的时候能平静的一笑,这是一种自信;失恋的时候能轻轻的一笑,这是一种洒 脱。

经典语录5:

人生途中,有些是无法逃避的,比如命运;有些是无法更改的,比如情缘;有些是难以磨灭的,比如记忆;有些是难以搁置的,比如爱恋……与 其被动地承受,不如勇敢地面对;与其鸟宿檐下,不如击翅风雨;与其在沉默中孤寂,不如在抗争中爆发……路越艰,阻越大,险越多,只要走过去了,人生就会更 精彩。

 经典语录6:

你改变不了环境,但你可以改变自己;你改变不了事实,但你可以改变态度;你改变不了过去,但你可以改变现在;你不能控制他人,但你可以掌握自己;你不能预知明天,但你可以把握今天;你不可以样样顺利,但你可以事事尽心;你不能延伸生命的长度,但你可以决定生命的宽度。

 经典语录7:

魅力女人:1、善于发现生活里的美。2、养成看书的习惯。3、拥有品位。4、跟有思想的人交朋友。5、远离泡沫偶像剧。6、学会忍耐与 宽容。7、培养健康的心态,重视自己的身体。8、离开任何一个男人,都会活得很好。9、有着理财的动机,学习投资经营。10、尊重感情,珍惜缘分。

 经典语录8:

愚人向远方寻找快乐,智者则在自己身旁培养快乐。生活里的每一个细节都蕴藏着快乐,只是在于你是否感受到了而已。快乐着的人,每一件事,每一个人身上,他都能发现能令自己欢悦的因素来,并让快乐扩张,鼓舞和影响了周围的人。

 经典语录9:

【给自己安慰的10句温馨话】1、最重要的是今天的心;2、别总是自己跟自己过不去;3、用心做自己该做的事;4、不要过于计较别人评 价;5、每个人都有自己的活法;6、喜欢自己才会拥抱生活;7、不必一味讨好别人;8、木已成舟便要顺其自然;9、不妨暂时丢开烦心事;10、自己感觉幸 福就是幸福。

 经典语录10:

没有永远的缘份,没有永远的生命,我们所能拥有的,可能只是平凡的一生。然而因为有你,生命便全然不同,不用誓言,不必承诺,我们只需依了爱缘,以目光为媒,印证三生石上的约定,便牵了手,不必紧握,却永不放松,以自己设计的爱的程式,去演绎一种精典的永恒。

 经典语录11:

我们之所以会心累,就是常常徘徊在坚持和放弃之间,举棋不定。我们之所以会烦恼,就是记性太好,该记的,不该记的都会留在记忆里。我们之所以会痛苦,就是追求的太多。我们之所以不快乐,就是计较的太多,不是我们拥有的太少,而是我们计较的太多。

 经典语录12:

男人吸引女人的10个特质:1.真实 2.深刻 3.胸怀 4.敢为 5.风度 6.机灵 7.幽默 8.进取 9.浪漫 10.冒 险.女人吸引男人的10个特点:1.温柔 2.知性 3.直性 4.涵养 5.朦胧 6.小动作 7.勤于家事 8.肤白 9.性感着装 10.香氛

经典语录13:

真正的爱,是接受,不是忍受;是支持,不是支配;是慰问,不是质问;真正的爱,要道谢也要道歉。要体贴,也要体谅。要认错,也好改错;真正的爱,不是彼此凝视,而是共同沿着同一方向望去。其实,爱不是寻找一个完美的人。而是,要学会用完美的眼光,欣赏一个并不完美的人。

 经典语录14:

身边总有些人,你看见他整天都开心,率真得像个小孩,人人都羡慕他;其实,你哪里知道:前一秒人后还伤心地流着泪的他,后一秒人前即刻 洋溢灿烂笑容。他们其实没有能力独处,夜深人静时,总坐在窗前对着夜空冥想失意的苦楚。他们就像向日葵,向着太阳的正面永远明媚鲜亮,在照不到的背面却将 悲伤深藏。

经典语录15:

生命中,有些人来了又去,有些人去而复返,有些人近在咫尺,有些人远在天涯,有些人擦身而过,有些人一路同行。或许在某两条路的尽头相 遇,结伴同行了一段路程,又在下一个分岔路口道别。无论如何,终免不了曲终人散的伤感。远在天涯的朋友:或许已是遥远得无法问候,但还是谢谢您曾经的结伴 同行。

 经典语录16:

爱情很简单,因为每个人都会说:“我爱你,会为你付出一切!”,爱情很难,因为没有多少人做到了他的承诺。如果真心爱一个人,不承诺也会去爱;如果不爱一个人,曾经承诺也会背叛。

 经典语录17:

【你最后悔什么】某杂志对全国60岁以上的老人抽样调查:第一名:75%的人后悔年轻时努力不够,导致一事无成。第二名:70%的人后 悔在年轻的时候选错了职业。第三名:62%的人后悔对子女教育不当。第四名:57%的人后悔没有好好珍惜自己的伴侣。第五名:49%的人后悔没有善待自己 的身体。

 经典语录18:

【做人十心机】⒈做人不能太单纯 适度伪装自己 ⒉凡事留余地 要留退路 ⒊话不说绝口无遮拦难成大事 ⒋成熟而不世故 ⒌心态好 想得开活得不累 ⒍懂方圆之道:没事不惹事,来事不怕事 ⒎不可少二礼:礼仪与礼物 ⒏人在江湖飘 防挨朋友刀 ⒐偶尔”势利眼” 寻可靠伙伴 ⒑放下面子来做人。

 经典语录19:

人生旅途中,总有人不断地走来,有人不断地离去。当新的名字变成老的名字,当老的名字渐渐模糊,又是一个故事的结束和另一个故事的开 始。在不断的相遇和错开中,终于明白:身边的人只能陪着自己走过或近或远的一程,而不能伴自己一生;陪伴一生的是自己的名字和那些或清晰或模糊的名字所带 来的感动。

 经典语录20:

从现在开始,聪明一点,不要问别人想不想你,爱不爱你?若是要想你或者爱你自然会对你说,但是从你的嘴里说出来,别人会很骄傲和不在乎你。再也不要太在意一些人,太在乎一些事,顺其自然以最佳心态面对,因为这个世界就是这样:往往在最在乎的事物面前,我们最没有价值。

 经典语录21:

一个人的成就,不是以金钱衡量,而是一生中,你善待过多少人,有多少人怀念你。生意人的账簿,记录收入与支出,两数相减,便是盈利。人生的账簿,记录爱与被爱,两数相加,就是成就。

[转载]自己动手写一个推荐系统 - 懒惰啊我 - 博客园

mikel阅读(833)

转载自己动手写一个推荐系统 – 懒惰啊我 – 博客园.

废话:

最近朋友在学习推荐系统相关,说是实现完整的推荐系统,于是我们三不之一会有一些讨论和推导,想想索性整理出来。

在文中主要以工程中做推荐系统的流程着手,穿插一些经验之谈,并对于推荐系统的算法的学术界最新的研究进展和流派作一些介绍。当然由于我做推荐系统之时还年幼,可能有很多偏颇甚至错误的见解,就当抛砖引玉,还请各位大大指点。

 

 

Reading lists

虽然很多人觉得作为AI的分支之一,推荐跟自然语言处理等问题的难度不可同日而语。但所谓磨刀不误砍柴工,我觉得,至少在开工前先应该阅读这样基本书,起码要看看目录,以对于推荐系统有个初步的了解。

 

中文书籍:

1.《推荐系统实践》项亮http://book.douban.com/subject/10769749/

这本书你说他好吧,不如recommend handbook;你说他不好吧,确实又把很多基础而简单的问题讲的很详细,而且还是中文的。薄薄的一本书,分分钟可以翻完,总而言之,是一本入门的好书。

 

外文书籍

1.《Recommender Systems Handbook》Paul B. Kantor  http://book.douban.com/subject/3695850/

其实所有敢自称handbook的书都是神书。这本书写的 非常细,而且非常全,如果你去看一些推荐系统的一些比较冷门的topic的paper,比如fighting spam的,甚至能发现很多paper就是 直接引用这本书里相关章节的内容。可以说这本书是做推荐同学必备的枕边书,没看过这本书出去吹牛逼时你都不好意思说自己是做推荐的,不过说实在,真正看完 的没几个,一般是用到哪里查哪里,我刚才就去豆瓣验证了,几个做推荐的都是在读,一群文艺青年都是想读。此书唯一的缺点是没有出新版,有些地方已经成为时 代的眼泪了。

 

 

2.《Recommender Systems – An Introduction》 Dietmar Jannach 

http://book.douban.com/subject/5410320/

跟上面一本差不多,学院派的综述型的书,厚厚一本,不看的时候当枕头用。

 

3.《mahout in action》Sean Owen http://book.douban.com/subject/4893547/

上面的一中一外的书都是理论基础的书,这本书就和工程有些沾边。如果你要用mahout框架来做推荐系统,那么是必须要扫一遍的;如果你不用mahout,看看其中的流程和代码组织也是有一定好处的。

 

Paper

由于《Recommender Systems Handbook》很多地方已经成为了时代的眼泪,这里推荐一些综述,以便开阔眼界。

一篇是physics report上面的recommend system这篇综述,可以说是最新最全的综述了,看完后学术界都在折腾啥分分钟就清楚了。http://arxiv.org/pdf/1202.1112v1.pdf

比较经典的是recommend system的state of art这篇综述,很多老一辈的同志喜欢推荐的,说实在这篇因为年代久远,也已经成为时代的眼泪了。

 

开工:

上面给了一些reading lists,并不是说要读完 所有的才能开工,只是说,先看完个目录,哪里不懂查哪里。在下面介绍的做推荐系统的流程中,我只是想给大家介绍个普通的推荐系统该怎么做,所以很多地方都 有偷懒,还请大家见谅。而且由于我不是做的在线的推荐系统,而是属于隔天推荐的那种离线的,所以叙述工程的时候就是只叙述离线的推荐。

 

数据准备:

一般来说,做推荐系统的数据一般分两种,一种从在线的读取,比如用户产生一个行为,推荐系统就反应下(传说豆瓣fm就是这么做的?),还有一种就是从数据库里读。

我个人一般是这样做的:跟做反作弊的人打个招呼,让他们在记用户行为数据的时候顺便存到各个线上服务器上,再写个php脚本,从各个服务器上把我需要的日志抓过来,然后当日要的最新的数据就来了。

但是这种地方其实存储的数据是加了一些判断的,也就是说是分类记录的(因为很多记录是别人刷的,比如丢一个链接到qq群里让别人帮忙投票什么的),这里不细说,到后面fighting spam的地方再讨论。

 

数据过滤:

当我们得到了每天产生的数据后,说实在这些数据实在是太多了,我们当然用不到这么多,就要写个过滤模块,把一些我们用不到的数据过滤掉。

我一般是这样做的:写个python的脚本,把过滤器放到 一个单独的模块,要用的过滤器就到责任链里面注册下。这样别人和自己维护起来也方便点,顺便一说,过滤的东西一般来说有这样几种:一种是一个item只有 一个user打过分的,而且以前没有人打分的,这样的数据放到推荐的模型里去跑虽然mahout会自动无视它,但其实按照power law来说是有不少 的,内存能省就省吧;还有一种是有些黑名单的,有item和user各自的黑名单,这些也是事先要去掉的。

 

数据存储:

这个就是大家仁者见仁,智者见智的时候了,因为一般来说,是你选用的算法和算法具体的实现方式以及基础架构决定了你的存储方式,就不多说了。

我一般是这样做的:一般做成增量处理的方式,然后每天一计算,一周一回滚。由于算法实现的特殊性,每40个item user对存储在一起,有点类似于bitmap吧。

 

推荐系统算法部分:

这部分以前写过类似的小记录和心得笔记之类的东西,就直接贴了_(:з」∠)_

这里的推荐系统的核心算法主要用mahout实现。

 

各种算法对于推荐都有着自己的特定的假设,对于什么时候什么样的算法会有比较好的performance应该对于假设反复验证。说白了就是做实验。

 

然后我们一般用什么算法呢,看看mahout给的算法吧,花样繁多,什么item based,user based,slop-one,SVD等等,常用的都有了,那我们要用什么算法呢。

先简单说下user based的算法在mahout中的一些实现:

第一步应该先算出所有人的相似度矩阵W,再去对于item进行遍历,事实上mahout也是这样做的。

相似度矩阵也许可以保留下来,这样可以用来做谱聚类来验证。

UserSimilarity 封装了用户之间的相似性

UserSimilarity similarity = new PearsonCorrelationSimilarity (model);

UserNeighborhood封装了最相似的用户组

UserNeighborhood neighborhood = new NearestNUserNeighborhood (2, similarity, model);

总而言之,用DataModel来生成data model,用UserSimilarity生成User-user之间的相似度矩阵,用户的邻居的定义用UserNeighborhood定义,推荐引擎使用Recommender实现。

 

对于推荐的评判是使用evaluator来进行评判 

double score = evaluator.evaluate(recommenderBuilder, null, model, 0.95, 0.05);

用95%的数据构建模型,用5%的数据进行test

 

Fixed-size neighborhoods

对于到底用多少人作为用户周围的一圈,我们事实上没有一个确定的值,就像做生物实验一样需要不断在特定的数据集里跑出来。

 

Threshold-based neighborhood

当然,我们也可以定义个threshold去找出最相似的用户群。

Threshold定义为-1到1(相似度矩阵返回的相似度就是在这个范围)

new ThresholdUserNeighborhood(0.7, similarity, model)

 

我们对于各个算法做个简单的比(吐)较(槽):

(假设我们是要像亚马逊一样对商品做推荐,然后我们最终是要做top k的推荐)

 

Item based

一般来说item-based要跑得快一些,因为item比user少

 

Slop one

说实在话我觉得在对于一些个人品味比较看重的东西上这个算法不是很靠谱

但是它在GroupLens 10 million数据的结果是0.65

当然,对于股票系统这种还是可取的

这个算法的假设是对于不同item的preference有种线性关系

不过slope-one有个好处是它的online算的很快,并且它的性能不由用户的数量决定

在mahout中的调用方法是new SlopeOneRecommender(model)

这个方法提供了两种weight:weighting based on count and on standard deviation

count 是越多的user的给越多的权重,对出的是一个加权的平均数

standard deviation则是越低的deviation给予越高的权重

这两个weight是默认采用的,当然disable它们只会让结果变得稍微坏一点0.67

不过这个算法有个很明显的缺点是比较占内存

但是幸运的是我们可以把它存在数据库里:MySQLJDBCDataModel

 

Singular value decomposition–based recommenders

事实上,尽管SVD失去了一些信息,但是有时候它可以改善推荐的结果。

这个过程在有用的方式平滑了输入

new SVDRecommender(model, new ALSWRFactorizer(model, 10, 0.05, 10))

第一个参数10是我们的目标属性个数

第二个属性是lambda->regularization

最后一个参数是training step跑的次数

KnnItemBasedRecommender

囧,事实上这个是用的knn的方式来做的算法,和前面的选择一个threshold然后圈画用户的算法是比较像的

但是用knn代价是非常高的,因为它要去比较所有的items的对

ItemSimilarity similarity = new LogLikelihoodSimilarity(model);

Optimizer optimizer = new NonNegativeQuadraticOptimizer();

return new KnnItemBasedRecommender(model, similarity, optimizer, 10);

结果也还不算差,是0.76

 

Cluster-based recommendation

基于聚类的推荐可以说是基于用户的推荐的算法的变体中最好的一种思路

对于每一个聚类里面的用户进行推荐

这个算法在推荐里面是非常快的,因为什么都事先算好了。

这个算法对于冷启动还是挺不错的

感觉mahout里面用的聚类算法应该类似于Kmeans?

TreeClusteringRecommender

UserSimilarity similarity = new LogLikelihoodSimilarity(model);

ClusterSimilarity clusterSimilarity =

new FarthestNeighborClusterSimilarity(similarity);

return new TreeClusteringRecommender(model, clusterSimilarity, 10);

注意的是两个cluster之间的相似度是用ClusterSimilarity来定义的

其中cluster之间的相似度还有NearestNeighborClusterSimilarity可选

 

吐槽:

对于算法的选择,其实我们是要和我们要推荐的目标挂钩的。 为什么最近学术界搞SVD那一系的算法这么火,什么LDA,plsa各种算法层出不穷,事实上因为netflix的要求是要优化RMSE,在机器学习的角 度来看,类似于回归问题,而工业界的角度来说,我们一般的需求是做top k的推荐,更加类似于分类问题。所以为什么相比用SVD系列的算法,用 item based这种更加ad hoc的算法要表现的更好一些。当然2012年的KDD cup第一名的组用的是item based+SVD的算 法,这是后话。

 

那么我就假设解我们这个top k的商品推荐的问题就用item based好了(速度快,结果又不算差),接下来就是确定相似度了。

 

相似度确定:

 

我们对于各个相似度做一些比(吐)较(槽):

PearsonCorrelationSimilarity

Pearson correlation:

coeff = corr(X , Y);   

function coeff = myPearson(X , Y)

% 本函数实现了皮尔逊相关系数的计算操作

%

% 输入:

%   X:输入的数值序列

%   Y:输入的数值序列

%

% 输出:

%   coeff:两个输入数值序列X,Y的相关系数

%

 

 

if length(X) ~= length(Y)

    error(‘两个数值数列的维数不相等’);

    return;

end

 

fenzi = sum(X .* Y) – (sum(X) * sum(Y)) / length(X);

fenmu = sqrt((sum(X .^2) – sum(X)^2 / length(X)) * (sum(Y .^2) – sum(Y)^2 / length(X)));

coeff = fenzi / fenmu;

 

end %函数myPearson结束

 

当两个变量的标准差都不为零时,相关系数才有定义,皮尔逊相关系数适用于:

(1)、两个变量之间是线性关系,都是连续数据。

(2)、两个变量的总体是正态分布,或接近正态的单峰分布。

(3)、两个变量的观测值是成对的,每对观测值之间相互独立。

1.没有考虑到用户偏好重合的items的数量 

2,只有一个item是相交错的话是不能得到correlation的,对于比较稀疏或者小的数据集这是个要注意的问题。不过一般来说两个用户之间只有一个item重叠从直觉上来说也不那么相似

Pearson correlation一般出现在早期的推荐的论文和推荐的书里,不过并不总是好的。

在mahout中使用了一个增加的参数Weighting.WEIGHTED,适当使用可以改善推荐结果

 

EuclideanDistanceSimilarity

Return 1 / (1 + d)

CosineMeasureSimilarity

当two series of input values each have a mean of 0(centered)时和PearsonCorrelation是有相同结果的

所以在mahout中我们只要简单的使用PearsonCorrelationSimilarity就好

 

Spearman correlation

这个方法用rank的方式,虽然失去了具体的打分信息,不过却保留了item的order

Return的结果是-1和1两个值,不过和pearson一样,对于只有一个item重叠的也算不出。

而且这个算法比较慢,因为它要算和存储rank的信息。所以paper多而实际用的少,对于小数据集才值得考虑

 

CachingUserSimilarity

UserSimilarity similarity = new CachingUserSimilarity(

new SpearmanCorrelationSimilarity(model), model);

 

Ignoring preference values in similarity with the Tanimoto coefficient

TanimotoCoefficientSimilarity

如果一开始就有preference value,当数据中signal比noise多的时候可以用这个方法。

不过一般来说有preference信息的结果要好。

 

log-likelihood

Log-likelihood try to access how unlikely 这些重叠的部分是巧合

结果的值可以解释为他们的重合部分并不是巧合的概念

算法的结果可能比tanimoto要好,是一个更加聪明的metric

 

Inferring preferences

对于数据量比较小的数据,pearson很难handle,比如一个user只express一个preference

于是要估计相似度么………

AveragingPreferenceInferrer 

setPreferenceInferrer().

不过这种办法在实际中并不是有用的,只是在很早的paper中mention到

通过现在的信息来估计并不能增加什么东西,并且很大的降低了计算速度

 

最终我们要通过实验来比较上面的相似度,一般来说是用准确率,召回率,覆盖率评测。

这里有一篇Building Industrial-scale Real-world Recommender Systems

http://vdisk.weibo.com/s/rMSj-

写netflex的,非常好,我就不献丑多说了,所以上面只是吐槽下常见的算法和相似度。

 

其实算法按流派分是分为下面这些类别的,大家有兴趣也可以了解下,我这里就不多做介绍:

Similarity-based methods

Dimensionality Reduction Techniques

Dimensionality-based methods

Diffusion-based methods

Social fltering

Meta approaches

我上面所说的相似度和推荐算法,只能算是Similarity-based methods和Dimensionality Reduction Techniques里的非常小的一小部分,只当抛砖引玉了。

Ps:刚在豆瓣上问了下,他们说就用前两种,事实上我也是觉得协同过滤+SVD 偶尔做做topic model就够了 如果没事干再上点social trusted的东西

 

增加规则:

记得hulu在做presentation的时候说过“不能做规则定制的推荐系统不是一个好的推荐系统”(好像是这样吧……)事实上我们做出来的推荐的结果是需要做很多处理再展现给用户的,这时候就是需要增加规则的时候了。

1.改善推荐效果:有些协同过滤的算法,做出来的结果不可 避免的产生一些让人啼笑皆非的结果,比如用户什么都买,导致你有可能跟姑娘们推荐的时候在佛祖下面推荐泳装什么的(真实的故事)。这时候就有两种办法,一 种就是调整模型,一种就是增加规则做一定的限制。再举个常见的例子就是有时候会在推荐冬装的时候出现夏装什么的,这时候一般我的处理方式是给这种季节性商 品做一个随时间的衰退。

2.增加广告和导向:插入广告,我们的最爱,这个就不多说了,靠规则。而所谓用户喜欢的,其实不一定就是最好的,比如用户一般来说就喜欢便宜的,还有什么韩流爆款之流,如果你推出来的东西都是这样的,整个系统就变成洗剪吹大集合了,非常影响定位,这时候也要上规则。

3.做一些数据挖掘和fighting spam的工作:这个在fighting spam的地方细说

 

可视化参数调整:

做完上面的工作,一般来说推荐系统的基础架构就差不多了, 但是往往各个算法以及你自己上的规则都有多如牛毛的参数要调整,这时候一般要自己写个测试脚本,将调整的结果可视化下一下,我个人推荐的是 highchart,看参数以及比较各个指标非常清爽, 还可以自己做一些比如是取log之类的定制,很是方便。 http://www.highcharts.com/

 

 

调整参数以及上线:

上线前有两个事要做,一般来说就是离线测试和AB test。

离线测试就是把数据抽样一部分,分为train data 和test data,然后评估一些准确率,召回率以及coverage之类的指标,用上面的可视化工具观测比较下,感觉差不多了把pm叫过来让她给小编 们看看,看看审美和效果之类的。这是比较粗糙的,有的地方做的比较过细,还有用户调研和请一些人来实际使用,这是后话。

AB test也是大家最喜欢的地方了。因为说实在,评估 推荐系统学术界是看准确率那一些东西,但是工业界还是看pv uv转化率这种实打实带来效益的东西,而AB test就是评估这些的。我个人是比较推荐这 种方法,说不上好,只是抛砖引玉,就是按一般的做法先空跑一个星期,然后再把系统上线实际做算法pk,然后选取的实验用户一半的几率进入原来的算法的推 荐,一半的几率进入你的算法的推荐,每天看看转化率之间的比较,顺便还可以调下参数做做实验。如果算法稳定表现较好,就差不多了。

 

Fighting spam:

俗话说,有人的地方就有江湖,有推荐的地方就有人刷。刷子 一般分三种类型的:average random和nuke。一般来说,average和random比较好对付,只要你的系统鲁棒性好点,基本影响不 大。但是nuke的就非常烦,一般来说有两种思路解决,一种是提高系统鲁棒性,另外就是上规则。我们从这张图看看两种思路分布对应的解决效果:

 

其实鲁棒性的系统做的就是把efficient attack的曲线降低,其实效果不算太好。

规则就是做到提前检测,将危险扼杀在摇篮之中,就是做的蓝色的那块detectable的部分。

Fighting spam是个博大精深的问题,俗话说,与人斗,其乐无穷,就是说的这个意思。

我们从规则说来,一般来说规则可以放到最前面的数据收集和 过滤的阶段,比如在收集数据的时候看看这个人是否是多个账号但是是一个ip,或者有些人用户名或者注册邮箱有群体相似性质,或者有没有出现pv等不正常的 情况。有时候我们也可以从推荐的结果里上规则来查,比如有些人刷的过火了,导致推荐结果出现一些问题,我们就可以用迭代的方式按照先验的刷子的比例来排出 刷子排行榜之类的东西。这些都是经验之谈,上不了台面,大家也可以自己摸索。

 

结束:

上面啰嗦了半天,大体上做一个完整的简单推荐系统就是涉及 到上面这些步骤。一些地方说的不够详细,有些是因为我懒,有些是不方便说。大家觉得我说的不对或者不妥的地方,可以直接在下面跟帖喷,或者有大大指导我也 是很欢迎的,大家多交流经验。我的邮箱是flclain@gmail.com 豆瓣是http://www.douban.com/people /45119625/ 有什么问题也可以豆瓣或者邮件交流。

其实我是觉得,要是有个大大说“你个傻缺,写的狗屁不通,让我来教你我是怎么做推荐的”就更好了。

[转载]复杂表单应用解耦,淘宝机票订单实践 - 前端技术 | TaoBaoUED

mikel阅读(1291)

[转载]复杂表单应用解耦,淘宝机票订单实践 – 前端技术 | TaoBaoUED.

背景

在web应用中,复杂表单这类web应用富交互元素多,业务逻辑复杂,犬牙交错,且需求变化频繁。及容易成为晦涩和幽暗之地,也经常是各种代码坏味道的来源。针对这种典型的复杂应用,本文以淘宝机票订单为例提出一种架构模式梳理和消化表单带来的复杂性。

模块和组件划分

解决复杂表单的的第一步,划分模块。

概念上,为了复用和解耦方便,应将模块按照功能的内聚程度进行划分。强相关,频繁沟通和交互的功能应该归为一个模块。模块间尽量不存在依赖关系。也就是常说的“高内聚,低耦合”。
如下图所示,淘宝机票订单页面主要有被分为7个主要模块。

模块划分完毕,下一步确认组成模块的组件。
关于模块和组件的区分。一般按照以下三个纬度考量。

  • 是否有业务逻辑参与。
  • 是否包含html。
  • 是否具备一定独立性。

模块”,定义为一个包含”html”、”css(图片被认为是css的一部分)“、”JavaScript”的代码集。模块的 应用方式多为通过web模板技术(如:Velocity、freemarker、php)。因为包含了html,使得模块必须通过服务端合并加载并且最终 推送到用户浏览器。此外,“模块”还是具备一定独立业务和交互的集合,最好可以被其他页面引用。良好的独立性也可以帮助协同开发,在实际开发中可多人可以 并行开发多个独立模块,提高效率。

组件”,定义为一个仅包含”css”和”JavaScript”的代码集。正因为不包含html,所以组件可通过 javascript异步加载。因为这种可异步加载的特性,组件在复用方面的容易性远超模块。组件没有业务逻辑或者仅有少部分公共业务逻辑。业务逻辑越 多,组件的可复用性就越低。

模块、组件间通讯

组件/模块划分的目的是将彼此间相对独立的功能分离,前面通过模块和组件的划分解决了分离问题。实际中,模块之间存在协作关系。模块间应以一种轻量的方式协作。一般的为了更好的分离和解耦,可以考虑用广播的方式在模块间沟通,考虑使用事件的方式在组件间通讯。

如下图所示,淘宝机票订单页面的数据流向。

不同模块在后期均有可能扩展小功能。例如不定期的活动优惠等。事件广播可以让不同模块/组件间新增功能影响面缩小。在淘宝机票订单中应用中,使用广播组件通讯主要用来完成以下意图。

1、知会。
知会的特性在于异步通讯。广播发起方只需要放出事件,无需等待其他关注者完成处理。称为异步广播。例如表单模块的内容变更需要知会到显示订单金额的模块,显示订单金额的模块接受事件后需要更改金额。
基于这种方式的通讯,各模块之需要做好自己的事情,外部关注的事件广播出去即可。异步广播还有一个好处是系统坚固性比较强,广播发送者不会因为事件监听者的使用不当而异常。

2、请求数据
例如,模块6(负责提交)需要在被点击后从模块2(乘机人表单),模块4(联系地址)、模块7(金额计算)。获取具体数据提交。请求数据的场景特性在于,广播发起者需要等待事件处理者完成处理后再继续下一步行为。称为同步广播。

基于此机制。提交模块只需要负责综合校验,浮层,网络请求及异常处理。而具体请求的内容由其他模块决定。对后续模块的扩充起到了很好的左右。

复杂组件拆分

模块和组件划分完毕后,可能会发现某些组件非常复杂,几乎占据了整个web应用一半以上的代码。这部分组件由纯js实现,并且使用javascript模块加载器加载。
同一个组件大量代码纠结在一起,最终还是会导致架构腐化。因此,复杂组件需要进一步拆分。在淘宝机票订单中,乘机人信息组件是一个复杂组件。如下图所示:

拆分这类输入型的复杂组件,一般来说有两种思路方式。

  1. 纵切,组件树型式。
    将组件进一步划分为更细力度的输入组件,将每个输入域作为一个单独组件。最终形成一个组件树。

这样的组织方式结构严谨层级清晰,最大的优点是很容易支持字段扩展。
但考虑如下场景,为了尽量友好的提示用户,需要在输入域外的某处增加提示帮助。

这种场景下组件树的组织方式每次在面对变化时就会略显手忙脚乱。难道把每个地方出现的tip都座位独立组件看待吗?
字段级的适普性降低了适应细节调整的能力,付出的代价在于界面体验。

  1. 横切,AOP式。
    将所有输入域抽象的看待为同一个组件。按照组件的富应用特性分层看待。在本例中,乘机人组件被按照从简单到复杂分为3个切面。

切面1-基础展现层只负责最基础的可完成输入的表单控件,及基础dom管理。
切面2-富展现层负责修饰base层的基础html控件,形成富输入控件。
切面3-校验层负责对base层的输入数据进行业务级校验。
未来,如果新增tip或者其他业务逻辑,增加一个新切面即可,完全或者很少需要修改老代码文件。

淘宝机票订单采用了AOP这种方式,从最终代码量上来看,可以看出复杂度被比较均衡的分布到不同文件中去。

同样,这种方式也有局限,如果需要扩展字段,那将是一个灾难,你有可能需要到每一个切面里面去做修改。

有句老话说的好,没有最优方案,只有最适合的解决方案,任何解决方案,都需要放到具体场景中去评判。事实上,对这个问题的进一步研究,可以发现以下规律。

对于一个组件、模块,同时追求简单设计、适普性(字段级扩充)、界面体验是不可能的。如果场景需要适应字段灵活扩展,那就采用纵切的模式。如果使用 场景需字段确定,需要更多细节控制力度,那就横切,AOP式。如果两者都要兼顾,就需要引入复杂设计,综合运用横切和纵切。但是这样形成的最终设计会很复 杂,开发和可维护性上会有代价付出。

对于淘宝机票这类互联网应用,使用了横切的方式来拆分组件,因为在这个场景中,字段的数量是相对固定的,而围绕固定数量字段的优化需求是层出不穷的。然而在企业内网应用或者网站后台web应用中,字段的变化会比较频繁。建议主要采用纵切的思路划分。

表单校验

有表单的地方就有校验。项目初期,校验的功能总是不起眼。等待项目后期时候经常会发现校验已经占据了巨大工作量并且成为海量bug的源头。因此校验是一种典型的容易被轻视单又蕴含巨大工作量的事情,需要特别对待,专门设计。

一般来说,这根据校验根据其复杂度可以分为以下两类:

  1. 格式校验
    格式校验一般是校验用户输入的格式是否满足要求,比如是否数字、电话号码、邮箱等等。此类校验的特点是校验域单一,一般只对一个input或者某个组件的 value进行检查。格式类校验应与与用户展现非常接近,一种非常好的做法是将此类校验信息直接描述在html标签属性中。html5中input的 pattern属性就是一种基于这种思想的解决方案。
  2. 逻辑校验
    逻辑校验是满足格式校验后,继续进行的与业务相关的校验,例如是否存在相同用户名,输入的生日是否和身份证号不符等等。此类校验的一般涉及多个输入域,要综合处用户的输入内容一起校验。此类校验逻辑复杂,不适合写在html中。

目前有很多流行的form校验框架解决校验问题,如何引入合适的校验框架,先从理解校验这件事的过程开始。
典型的一个校验过程如下,用户在某个input处完成输入,应用在某个时刻被触发校验,可以是失去焦点或者keyup或者其他。被触发的校验过程找到此处 input所需要的校验规则(有时候这个规则被直接写在html中)判单正确与否,如果正确,可能有提示,如果错误,可能也有提示。
从以上场景的描述中,可以找到校验的几个关键环节。这里局部采用一下管理学上经典的5w1h问题分析方法来分析问题

  • who: 哪个输入控件的内容需要校验。这是框架是解决不了的。要对哪个输入域做校验应该是应用传递进入的。
  • when: 何时被触发校验。比如说是“who”失去焦点时。变化太多,框架解决不了。只能被动触发。
  • what: 做什么校验。有时候这个”what”被写在html中。基本上,所有格式校验都是固定的,这个问题应框架解决。但框架应预留接口做更加复杂的业务校验。
  • how: 校验完毕后的动作。框架不能决定做什么,但是在校验结果出来后,框架应能知会到外部调用者。

在设计框架或者选择已有框架时,首先要区分框架的边界,简单来说,就是做什么和不做什么。框架应实现相对固定的业务流程。同时对可变部分预留足够的灵活性。

一个通用的校验框架一定是不含界面部分的。界面是多变和难以穷举的,是用tip显示错误,还是在输入域附近显示,是否需要动画,是否需要修改输入域 的视觉状态,这些可变化的部分应为框架外部内容,由更专业的tip组件或者popup来完成。框架只应该负责在校验完成时候知会相关组件完成显示错误提示 等若干事情。

基于以上的分析,校验框架应该具备以下规格
1. 解决what问题。内置了各种格式校验规则,如电话号码、e-mail等.并且能够灵活定义新的逻辑校验。
2. 解决who问题。说明如何根据输入的字符真正找到who对应的value。并且能够对于这个who使用哪些校验规则
3. 解决when问题。提供一个触发校验的方法。
4. 解决how问题。产生校验结果后能够知会外部的功能框架。

在淘宝机票订单应用中,依据上述原则自行设计了一个Validator框架,接口定义如下,Validator是校验框架对象。

  1. 在构造函数中提供表格化的校验逻辑定义型式。如下图所示,传递如下结构,定义每个字段对应的校验方式。在下图中,定义每行为一个field,每个field有若干rule,每个rule可以是框架内置的格式校验,也可以是自定义的逻辑校验,实际上是函数名。
  2. Validator框架提供validate()方法,validate方法有两个行为,如果不指定参数,将依次执行完所有field的校验,并且将最终结果返回。如果执行一个field name,框架将只校验field name对应的输入域。
  3. 一旦执行validate()方法,无论校验结果如何,框架均向其观察者发送事件’onValidate’。以便触发后续动作。
  4. 一些辅助参数,需要提供一个从field name找到输入域value的function。

总结

在处理复杂表单时,首先通过合理模块、组件划分,将复杂度分散。然后利用详细和广播机制解决分散的模块和组件间通问题。接着,过于复杂的组件要考虑进一步拆分,具体拆分的方式有纵切和横切两种,根据具体使用场景决定。最后,不要小看了校验,需要特别对待,专门设计。

在新浪微博关注我:@魏凡哲-陶清

[转载]如何实现Velocity模板语法解释器 - 前端技术 | TaoBaoUED

mikel阅读(1354)

[转载]如何实现Velocity模板语法解释器 – 前端技术 | TaoBaoUED.

前言

承玉曾经写过一篇文章构建前端DSL,本文讨论的话题和承玉的差不多,相信大家都知道coffeescript,handlerbars。承玉的DSL和handlerbars类似,我完成的模板语言velocity解析器,和coffeescript类似。在此,与大家分享一些经验,如果你也希望知道coffeescript语法解析如何完成的,那么,本文应该对你有所启示。

说明:本文中主要关注编译器语法分析过程,就语法分析过程而言,解释器和分析器没有区别,所以,请原谅后文中把这两个概念混淆着用了。

让我们回顾一下2010年D2的时候,Hedger介绍了Closure Compiler,老赵的jscex,他们有一个共同点,都是对js进行编译,让js运行更快或者提供一起额外的功能。编译这么一个似乎和 JavaScript没有关系的话题,却逐渐被越来越多的人提起。

本文主要介绍如何用js写一个编译器,这看起来似乎很高级,实际上,编译原理很复杂,写一个编译器却不怎么难,在做这个模板编译之前,我个人对于编 译原理完全不知道的,因为看到coffeescript语法是Jison生成的,然后尝试了一下。写一个编译器,其实就是把一些语法规则翻译成计算机能够 理解的结构,计算机所能理解语法规则有专门的描述语言,Yacc + Lex。IBM上有文章如此描述:

Lex 和 Yacc 是 UNIX 两个非常重要的、功能强大的工具。事实上,如果你熟练掌握Lex 和 Yacc 的话,它们的强大功能使创建 FORTRAN 和 C 的编译器如同儿戏。

Yacc + Lex的一个实现是Bison,09年Zach Carter为了研究编译原理课程,用js完成了Bison的实现Jison, 承玉的kison类似。故事就讲到这里,什么是Yacc,Lex,Bison,Jison,Kison,都不重要,重要的是,这些技术使得我们可以使用简单的方式完成复杂的字符串解析(比如编译)任务。现在我们要来实现一个解释器了,看完就知道这一切了。

Lex & Yacc

Lex和Yacc主要用于解决编译中的第一个问题,源文件从字符串变得有意义(结构化数据)。这个过程,又分为两个步骤:

  1. 源文件拆分成各种标志(tokens) Lex
  2. 构造数据结构 Yacc

学习英语的时候,我们都会遇到语法问题,对于陌生的语言,需要知道其语法规则,计算机语法规则与自然语言类似,只是自然语言是与上下文有关的语言,比起计算机语言复杂得多。与上下文无关,其实就是语言的符号意义是确定的。扯远了,举个例子,一个正常的英语句子:

 I have a dream. 

回到英文课堂,老师会说,句子是由主语+谓语+冠词+宾语构成,这个句子构成的元素是,主语I,谓语have,宾语dream,句号表示句子的结束。这样的一个语法规则,让计算机理解,需要依据上面的两个步骤:

  1. 识别单词,也就是英语中的主语、谓语和宾语,好吧这些背单词的时候记住就行,标点符号也是词法元素。
  2. 语法识别,上面的句子对应的语法是:谓语 + 主语 + 冠词 + 宾语 + 问号 => 句子

词法识别和英语学习中背单词一样,计算机通过正则在字符串中匹配词,构成语言的基本结构,这些结构按照一定组合规则构成语法。Yacc所做的,是把 扫描一串字符串,识别其中的词,把词和所描述的语法一一对照,然后能够得到一些结构化的数据,比如上面英语,计算机就能够知道,这是一个完整的句子,三个 主要成分是I、have、dream,至于这个句子什么意思,你应该如何处理,这是编译过程的第二步了。

Velocity syntax

上面简单描述了一下原理,现在开始写语法规则分析器吧。写编译器就是把一套语法规则描述清楚,就像翻译一篇说明书。当然,我们首先需要能明白说明书 的意义,本文以Velocity模板语言为例,Velocity是Java实现的一套模板,是阿里集体后端webx框架的模板语言,语法规则文档,可以大致看下语法,或者点击此处在线尝试vm解释过程。

vm(velocity简称)语法规则很简单,大概开5分钟就能学会,vm虽然简单,但是也是一套比较基本的计算机语言的实现了,对比下,英语我们学习了10年,还没能学好,vm只需要5分钟,自然语言的复杂度,比起计算机语言实在不是一个数量级。

 #set( $foo = "Velocity" ) Hello $foo World! 

vm语法分为两部分,第一部分是vm语法内容,另一部分是字符串,模板语言都是如此,字符串部分无需考虑,原样输出即可,vm语法主要是前者结构分析。上面的vm输出Hello Velocity World!。语法部分,主要分为两部分References和Directives。

References 和 Literal

References是vm中变量,解析时References输出为变量对应的值,模板语言最基本的功能也就是变量替换,vm同样如此,只是vm 还有一些其他复杂的功能。Literal和js里面的字面量一直,是vm里面的基本数据结构。vm是一个模板语言,变量的值可以来自外部,而且是主要数据 来源,References和Literal这两者构成了vm语法的基本数据。

References基本形式是$foo,或者加一些修饰符$!{foo}。复杂形式是,变量+属性,支持的属性方式有三种:

  • Properties 最普通的属性$foo.bar
  • Methods 方法$foo.bar(),因为方法是有参数的,参数由References和Literal构成
  • Index 索引$foo['bar'],index可以是字符串,也可以是变量References

上面三种方式和js的对象属性查找方式一样,因为存在Methods和Index,方法和Index本身又可以包含References,引用的组 成部分可以是引用。这样式描述形成了递归,语法一般都是通过递归的形式来相互包含。引用(References)里包含自身,这如果使用普通的字符串匹 配,逻辑上会有些晕。

Literal是基本的数据结构,分为字符串、map(js中的对象)、数字、数组。map的值由Literal 或者References构成,数组元素同样,字符串和数组相对简单,可以直接从源文件中匹配得到。到此,应该大致明白编译的复杂了吧,就这些基本的数据 结构相互包含,要理清其中结构,还是很麻烦的吧,虽然我们可以一眼就知道这些结构,如何让计算机明白,就不那么容易了。不过,通过yacc,我们只需要描 述清楚这些结构就行,怎么理清其中关系,Jison会自动处理的。

Directives

前面引用和字面量部分,是vm中关系最复杂的结构了,Directives是一些指令,包括逻辑结构和循环,模块之间引用加载等运算。这些结构比较好搞定,一般都是各自不相干,不像上面,相互引用,纠缠不清。vm解析,最复杂的还是在于引用的确定。

Directives分为单行指令和多行指令,单行指令作用范围是一句,比如#set#parse,多行指令主要是#macro,#foreach,if|else|elseif,这些都是通过#end来结束,这样的区分可以在语法分析阶段完成,也可以在后期处理。

语法分析

本文有些长,已经开始靠近目标了。上面描述语法的过程,是非常重要的,使用yacc描述语法规则,就是对输入源分类的过程。经过上面的分析,yacc的已经差不多构思好了,接下来把规则用yacc语法写下来就好。

在写yacc描述之前,需要做一件是,lex词法分析。词法分析就是要找到上面说的References、Literal、Directives的基本元素。新建一个文件velocity.l,开始写lex描述。

References

从References开始,vm里面引用的最主要的特征是符号$,首先假设有一个vm字符串:

 hello $foo world 

其中,$foo是References,很明显References是美元符号开头,$后面跟字母,这里需要引入状态码的概念,因为$后面的字母的意义和$前面的字母意义是不一样的,那么当扫描到$以后,可说我们处于不同的状态,区分好状态,就可以专心处理之和vm语法,否则同样的一个字符,意义就不一样。这个状态,我们用mu表示,状态吗可以随意命名,使用mu,是有渊源的,handlerbars的lex文件因为继承了Mustache语法,mu表示Mustache语法开始,我参考了handlerbars,所以用mu

velocity.l写下:

%x mu %% [^#]*?/"$" { this.begin("mu"); if(yytext) return 'CONTENT'; } <mu>"$!" { return 'DOLLAR'; } <mu>"$" { return 'DOLLAR'; } <INITIAL><<EOF>> { return 'EOF'; } 

%x声明有的状态码,状态码和字符串或者正则表达式组合成一个特征,比如<mu>"$",双引号表示字符串,这个特征描述表示,mu状态下,遇到$,返回DOLLAR。我们用DOLLAR描述$,至于为什么我们要给$一 个名字,再次回到英语中,我们会把单词分为名词、动词,各种分类,语法规则不会直接处理某个特定的词如何组合,而是规定某一类词的组合规则,比如,最普通 的句子,主语+谓语+宾语,主语一般是名词,谓语是动词,宾语也是名词,这样描述要简单得多,lex词法分析是给字符做最小粒度的分类,最终,一个vm输 入源码,可以归纳到一个分类里,符合英语语法的字符串,我们统称为英语。

特征都使用全大写字母,这是一种约定,因为在yacc描述中,语法规则名都用小写。%%后面第一行,[^#]*?/"$",这是一个正则表达式,正则分为两个部分,第一部分 [^#]*?匹配所有不是符号#的字符,后面一部分"$",中间反斜杠分割,是一个向后断言,匹配美元符号前面所有不是符号#的字符,也就是遇到没有符号的时候,后面通过 this.begin开始状态mu。这里使用到yytext,就是前面正则所匹配到的内容,有个细节,这个匹配去除了#,因为#是另一种状态Directives的开始,这里暂时只讨论引用识别。最后一行,表示结束返回,这个无需理解。

引用的最基本形式,$ + 字母,美元符号识别了,接下来识别后面的字母,使用正则表达式

<mu>[a-zA-Z][a-zA-Z0-9_]* { return 'ID'; } 

如此,我们可以用这两条规则,开始写第一条yacc语法规则了:

reference : DOLLAR ID { $$ = {type: "references", id: $2} } ; 

上面描述的是reference,由lex中返回的DOLLAR和ID组合成为一个reference,大括号里面写的是js代码,用于构造结构化数据,需要什么样的数据可以自己随便搞,$$表示返回结果, $1是DOLLAR词对应的字符串,也就是$$2表示第二个词,也就是ID。复杂的reference可以继续写:

reference : DOLLAR ID | DOLLAR ID attributes ; attributes : attribute | attributes attribute ; attribute : method | index | property ; property : DOT ID ; index : BRACKET literal CLOSE_BRACKET | BRACKET reference CLOSE_BRACKET ; 

reference在原来的基础下,增加了attributes,attributes是由一个或者多个属性组成,在yacc中,使用attributes attribute来描述多个属性的情况,规则直接包含自身的情况还是非常常见的。attribute由 method,index,property 组成,继续拆分,index是两个中括号加一个literal或者 reference 组成,我们可以继续对literal进行分类,同样的描述。我们回到了上面对vm 语法描述的那个分类过程只不过,现在我们使用yacc的语法描述,前面使用的是自然语言。

解析过程

上面讲了那么多,现在来总结一下Jison解析一个字符串的过程。用一张图表示吧:

lext

词汇分析过程就是上面所描述的了,一个lex文件,构成一个词汇表,通过从左到右的扫描输入源,依次匹配词汇表里面定义的模式,然后构成一个个词 汇。得到词汇之后,那什么是语法呢,还记得英语语法吗?在计算机里面,语法就是上面所描述的,词汇的组合,规定了词汇的组合形式,比如DOLLAR ID组成一个reference,写yacc语法规则就是不断的对语法进行分类,直到所有的分类最底层都是lex中的词汇,然后语法规则也就ok了。程序会自动根据yacc文件所有定义的规则,分析得到输入源对应的数据结构。

velocity最终的语法描述在这里

状态码

上面简要描述了yacc和lex工作原理过程,实际中,还是会遇到一些有意思的问题。在写vm解析器的时候,最麻烦的事情是,如何保证括号和中括号的匹配,首先看一段vm字符串:

$foo.bar($foo.name("foo")[1]) $foo.bar([)] 

经过分析,我发现括号匹配的一个特点是,括号的闭合状态下,它的前一个状态肯定是括号开始,中括号同样如此。因此,我在velocity.l中再引入两种状态,i, c,分别表示括号开始和中括号开始,在匹配到括号或者中括号结束的时候,判断前面的一个状态是否是符号的开始,这样,就能保证括号和中括号的配对。

在lex词汇分析中,状态码是一个堆栈,这个堆栈通过this.begin开始一个状态,this.popStat退出一个状态,词汇可以是多种状态和正则表达式进行组合,状态的开始和结束,需要自己控制,否则可能会有问题。

解析最终得到一个对象,这个对象的构造是根据velocity.yy而生成的。如何选择合适的数据结构,这个是很很重要的,后面的语法树解释过程, 完全取决于解析器所返回的语法树。在velocity的语法树,最终得到的是一个一维数组,数组的元素分为字符串和对象两种,如果是对象,那么是vm语法 需要进行分析解释的。

语法树解释

得到输入源语法结构之后的工作,相对而言就容易了,这其中会涉及到两个点,我个人觉得比较有意思的。第一个是局部变量,在vm语法中,有一个指令#macro,这个是vm的函数定义,由函数,自然有形参和实参,在函数执行过程中,形参是局部变量,只在函数解析过程中有效,#foreach也会形成一个局部变量,在foreach中有一个内部变量$foreach.index, $foreach.count, $foreach.hasNext这样的局部变量。

局部变量的实现,可以参考lex语法分析过程,在语法树解释过程中,增加一个状态码,当进入一个foreach或者macro的时候,生成一个全局 唯一的条件id,并且在状态中压入当前的条件id,当foreach和macro运行结束后,推出一个状态。foreach和macro控制状态,同时构 造一个数据空间,贮存临时变量,在vm解析过程中,所有的变量查找和设置,都是通过同样的一个函数。当一个变量查询时,检测到存在状态时,首先依次根据状 态码,找到对应状态下的局部变量,如果需要查询的变量在局部环境中找到,那么返回局部对象对应的值,如果是这是值,同样如此。这样的实现和js所中的函数 执行上下文有点类似,可以继续想象一下如何实现避包,实现避包其实只需要在一个函数中返回一个函数,这样的语法在vm中没有,不过如果真的可以返回一个函 数,那么只需要在这个函数和当前函数执行所对应的状态放在一起,并且不释放状态对象的局部变量,那么避包也就有了。

结束

本文到此要结束了,不知道是否有说明白,具体实现细节可以参考velocity.js源码

[转载]淘宝js类库kissy的api下载(kissy.chm)

mikel阅读(1018)

[转载]辉仔’s Blog » 淘宝js类库kissy的api下载(kissy.chm).

KISSY 是由淘宝前端攻城师们发起创建的一个开源 JS 类库。

她遵循的原则是 小巧灵活、简洁实用、愉悦编码、快乐开发。

下面是kissy的一些帮助文档和教程,希望对你们有所帮助。

在线文档 (点击查看)

离线API文档 (下载点我)

15天学会kissy (想学习赶快点)

淘宝店铺开发文档-Widget规范-关于kissy (对你有帮助吧!)

[转载]ASP.NET MVC 4 利用动态注入HTML的方式来设计复杂页面 - Artech - 博客园

mikel阅读(1063)

[转载][ASP.NET MVC] 利用动态注入HTML的方式来设计复杂页面 – Artech – 博客园.

随着最终用户对用户体验需求的不断提高,实际上我们很多情况下已经在按照桌面应用的标准 来设计Web应用,甚至很多Web页面本身就体现为一个单一的页面。对于这种复杂的页面,我们在设计的时候不可以真的将所有涉及的元素通通至于某个单独的 View中,将复杂页面相对独立的内容“分而治之”才是设计之道。我们可以借鉴Smart Clent应用的设计方式:将一个Windows Form作为应用的容器(Smart Client Shell),在操作过程中动态地激活相应的用户控件(Smart Part)并加载到容器中。对于一个复杂页面来说,我们也只需要将其设计成一个容器,至于运行过程中动态显示的内容则可以通过Ajax调用获取相应的 HTML来填充。[源代码从这里下载]

目录
一、实例演示
二、作为容器的View
三、显示联系人列表
四、弹出“修改联系人”对话框
五、联系人信息的最终修改

一、实例演示

我 们先来演示一个简单的例子,假设我们要设计一个“联系人”管理的页面。该页面初始状态如左图所示,它仅仅具有一个用于输入查询条件(First Name和Last Name)进行联系人查询的表单。当用户输入相应的查询条件之后点击“Retrieve”按钮,相应的联系人列表显示以表格的形式显示出来(中图)。当我 们点击作为ID的链接后,会以“模态对话框”的形式显示当前联系人的编辑“窗口”(右图)。

这个“单页面应用”是通过ASP.NET MVC开发的,接下来我们来逐步介绍如果将同一页面中的这三块不同的内容提取出来进行“分而治之”。

二、作为容器的View

如下所示的是表示联系人的Contact类型的定义,没有什么特别之处:

 1: public class Contact
 2: {
 3:     [Required]
 4:     public string Id { get; set; }
 5:     [Required]
 6:     public string FirstName { get; set; }
 7:     [Required]
 8:     public string LastName { get; set; }
 9:     [Required]
 10:     [DataType(DataType.EmailAddress)]
 11:     public string EmailAddress { get; set; }
 12:     [Required]
 13:     [DataType(DataType.PhoneNumber)]
 14:     public string PhoneNo { get; set; }
 15: }

联系人管理对应的HomeController定义如下。简单起见,我们通过一个静态字段来表示维护的联系人列表。我们仅仅列出了默认的Action方法Index,它会直接将作为“容器页面”的View呈现出来。

 1: public class HomeController : Controller
 2: {
 3:     private static List<Contact> contacts = new List<Contact>
 4:     {
 5:         new Contact{Id = "001", FirstName = "San", LastName = "Zhang", EmailAddress = "zhangsan@gmail.com", PhoneNo="123"},
 6:         new Contact{Id = "002", FirstName = "Si", LastName = "Li", EmailAddress = "zhangsan@gmail.com", PhoneNo="456"}
 7:     };
 8:
 9:     public ActionResult Index()
 10:     {
 11:         return View();
 12:     }
 13:      //其他Action方法
 14: }

如下所示的是Index.cshtml的定义,在这里使用了Twitter的Bootstrap,所示我们引用了相应的CSS和JS。这个主体部分包含三个<div>,分别对应着上述的三个部分。

 1: <html>
 2:     <head>
 3:         <title>Contacts</title>
 4:         <link href="@Url.Content("~/Assets/css/bootstrap.css")" rel="stylesheet" type="text/css" />
 5:         <link href="@Url.Content("~/Assets/css/bootstrap-responsive.css")" rel="stylesheet" type="text/css" />
 6:     </head>
 7:     <body>
 8:         <div class="form-search">
 9:            @Html.Partial("QueryFormPartial")
 10:          </div>
 11:         <div id="contactList"></div>
 12:         <div class="modal fade" id="contactDialog"></div>
 13:
 14:         <script type="text/javascript" src="@Url.Content("~/Assets/js/jquery-1.7.1.min.js")"></script>
 15:         <script type="text/javascript" src="@Url.Content("~/Assets/js/bootstrap.min.js")"></script>
 16:         <script type="text/javascript" src="@Url.Content("~/Assets/js/jquery.unobtrusive-ajax.min.js")"></script>
 17:         <script type="text/javascript" src="@Url.Content("~/Assets/js/jquery.validate.min.js")"></script>
 18:         <script type="text/javascript" src="@Url.Content("~/Assets/js/jquery.validate.unobtrusive.min.js")"></script>
 19:     </body>
 20: </html>

表示“查询表单”的部分定义在如下所示的Partial View(QueryFormPartial.cshtml),直接通过调用HtmlHelper的Partial方法呈现在当前View中。

 1: @{
 2:     Layout = null;
 3: }
 4: using (Ajax.BeginForm("Find", new AjaxOptions { OnSuccess = "renderCustomerList" }))
 5: {
 6:    <fieldset>
 7:        <legend>Maintain Contacts</legend>
 8:        <label class="control-label" for="firstName">First Name. :</label>
 9:        <input type="text" name="firstName" class="input-medium search-query" />
 10:        <label class="control-label" for="lastName">Last Name :</label>
 11:        <input type="text" name="lastName" class="input-medium search-query" />
 12:        <input type="submit" value="Retrieve" class="btn btn-primary" />
 13:    </fieldset>
 14: }

三、显示联系人列表

QueryFormPartial.cshtml定义了一个以Ajax方式提交的表单,目标Action为具有如下定义的Find,它根据指定的 First Name和Last Name筛选匹配的联系人列表,并将其呈现在一个名为ContactListPartial的View中。

 1: public class HomeController : Controller
 2: {
 3:     //其他成员
 4:     public ActionResult Find(string firstName = "", string lastName = "")
 5:     {
 6:         var result = from contact in contacts
 7:                         where (string.IsNullOrEmpty(firstName) || contact.FirstName.ToLower().Contains(firstName.ToLower()))
 8:                             &&(string.IsNullOrEmpty(lastName) || contact.LastName.ToLower().Contains(lastName.ToLower()))
 9:                         orderby contact.Id
 10:                         select contact;
 11:         return View("ContactListPartial",result.ToArray());
 12:     }
 13: }

如下所示的ContactListPartial.cshtml的定义,这是一个Model类型为IEnumerable<Contact>的强类型View,它以表格的形式将联系人列表呈现出来。

 1: @model IEnumerable<Contact>
 2: @{
 3:     Layout = null;
 4: }
 5: <table class="table table-striped table-bordered">
 6:     <thead>
 7:         <tr>
 8:             <th>ID</th>
 9:             <th>First Name</th>
 10:             <th>Last Name</th>
 11:             <th>Email Address</th>
 12:             <th>Phone No.</th>
 13:         </tr>
 14:     </thead>
 15:     <tbody>
 16:         @foreach (var contact in Model)
 17:         {
 18:             <tr>
 19:                 <td>@Ajax.ActionLink(contact.Id, "Update", new { contactId = contact.Id }, new AjaxOptions { OnSuccess = "showDialog" , HttpMethod="GET"})</td>
 20:                 <td>@contact.FirstName</td>
 21:                 <td>@contact.LastName</td>
 22:                 <td>@contact.EmailAddress</td>
 23:                 <td>@contact.PhoneNo</td>
 24:             </tr>
 25:         }
 26:     </tbody>
 27: </table>

从QueryFormPartial.cshtml的定义可以看到,表单成功提交之后会调用一个名为renderCustomerList的 JavaScript函数(@using (Ajax.BeginForm(“Find”, new AjaxOptions { OnSuccess = “renderCustomerList” }))),它以如下的方式定义在Index.cshtml中。从定义了看出,它将获取的数据(实际上ContactListPartial这个View最 终的HTML)作为contactList这个<div>的HTML。

 1: <html>
 2:     <head>
 3:         <script type="text/javascript">
 1:
 2:             function renderCustomerList(data) {
 3:                 $("#contactList").html(data);
 4:             }
 5:
</script>
 4:     </head>
 5: </html>

四、弹出“修改联系人”对话框

从ContactListPartial.cshtml的定义可以看到联系人ID以一个链接的方式呈现出来,点击该链接会以Ajax的方式访问 Action方法Update,当前联系人ID会作为请求的参数(<td>@Ajax.ActionLink(contact.Id, “Update“, new { contactId = contact.Id }, new AjaxOptions { OnSuccess = “showDialog” , HttpMethod=”GET”})</td>)。如下所示的是Action方法Update的定义,它根据指定的ID获取对应的联系人, 并将其呈现在一个名为ContactPartial 的View中。

 1: public class HomeController : Controller
 2: {
 3:      //其他成员
 4:     [HttpGet]
 5:     public ActionResult Update(string contactId)
 6:     {
 7:         Contact contact = contacts.First(c => c.Id == contactId);
 8:         return View("ContactPartial", contact);
 9:     }
 10: }

如下所示的ContactPartial.cshtml的定义,这是一个Model类型为Contact的强类型View,它将联系人信息呈现在一个表单中。

 1: @model Contact
 2: @{
 3:     Layout = null;
 4: }
 5: @using(Ajax.BeginForm("Update", null, new AjaxOptions { HttpMethod = "Post", OnSuccess = "reLoad" }, new { @class = "form-horizontal" }))
 6: {
 7:     <div class="modal-header">
 8:     <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
 9:     <h3>Contact Information</h3>
 10:     </div>
 11:     <div class="modal-body">
 12:         <div class="control-group">
 13:             @Html.HiddenFor(model=>model.Id)
 14:             @Html.LabelFor(model=>model.FirstName,new{@class="control-label"})
 15:             <div class="controls">
 16:                 @Html.EditorFor(model => model.FirstName)
 17:                 @Html.ValidationMessageFor(model => model.FirstName)
 18:             </div>
 19:         </div>
 20:         <div class="control-group">
 21:             @Html.LabelFor(model=>model.LastName,new{@class="control-label"})
 22:             <div class="controls">
 23:                 @Html.EditorFor(model => model.LastName)
 24:                 @Html.ValidationMessageFor(model => model.LastName)
 25:             </div>
 26:         </div>
 27:         <div class="control-group">
 28:             @Html.LabelFor(model => model.EmailAddress, new { @class = "control-label" })
 29:             <div class="controls">
 30:                 @Html.EditorFor(model => model.EmailAddress)
 31:                 @Html.ValidationMessageFor(model => model.EmailAddress)
 32:             </div>
 33:         </div>
 34:         <div class="control-group">
 35:             @Html.LabelFor(model => model.PhoneNo, new { @class = "control-label" })
 36:             <div class="controls">
 37:                 @Html.EditorFor(model => model.PhoneNo)
 38:                 @Html.ValidationMessageFor(model => model.PhoneNo)
 39:             </div>
 40:         </div>
 41:     </div>
 42:     <div class="modal-footer">
 43:     <a href="#" class="btn" data-dismiss="modal">Close</a>
 44:     <input type="submit" class="btn btn-primary" value="Save" />
 45:     </div>
 46: }

联系人编译窗口的弹出最终通过调用JavaScript函数showDialog实现(<td>@Ajax.ActionLink(contact.Id, “Update“, new { contactId = contact.Id }, new AjaxOptions { OnSuccess = “showDialog” , HttpMethod=”GET”})</td>),具体定义如下所示。它将获取到的数据(实际上是ContactPartial这个 View最终的HTML)作为第三个<div>的HTML,并按照Bootstrap的方式以模态对话框的形式将其呈现出来。至于中间的两行 代码,在于解决动态添加表单无法实施验证的问题。

 1: <html>
 2:     <head>
 3:         <script type="text/javascript">
 1:
 2:             function showDialog(data) {
 3:                 $("#contactDialog").html(data);
 4:                 $("#contactDialog form")
 5:                     .removeData("validator")
 6:                     .removeData("unobtrusiveValidation");
 7:                 $.validator.unobtrusive.parse($("#contactDialog form"));
 8:                 $("#contactDialog").modal();
 9:             }
 10:
</script>
 4:     </head>
 5: </html>

五、联系人信息的最终修改

通过ContactPartial.cshtml的定义可以看出编辑联系人表单最终以POST的方式提交到HomeController的Action方法Update(@using(Ajax.BeginForm(“Update“, null, new AjaxOptions { HttpMethod = “Post“, OnSuccess = “reLoad” }, new { @class = “form-horizontal” }))),该Action方法具有如下的定义。它在完成修改之后,返回字符串“OK”表明联系人修改成功。

 1: public class HomeController : Controller
 2: {
 3:     //其他成员
 4:     [HttpPost]
 5:     public string  Update(Contact contact)
 6:     {
 7:         contacts.Remove(contacts.First(c=>c.Id == contact.Id));
 8:         contacts.Add(contact);
 9:         return "OK";
 10:     }
 11: }

联系人修改表单提交后关闭当前窗口并加载新的数据通过具有如下定义JavaScript函数Reload实现(@using(Ajax.BeginForm(“Update“, null, new AjaxOptions { HttpMethod = “Post“, OnSuccess = “reLoad” }, new { @class = “form-horizontal” }))),该函数依然定义在Index.cshtml中。

 1: <html>
 2:     <head>
 3:         <script type="text/javascript">
 1:
 2:             function reLoad(data) {
 3:                 if (data == "OK") {
 4:                     $("#contactDialog").modal("hide");
 5:                     $(".form-search form").submit();
 6:                 }
 7:             }
 8:
</script>
 4:     </head>
 5: </html>

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

[转载]Portal技术综述,关于Portal技术综述的文章-Portal-尚观知识库

mikel阅读(897)

[转载]Portal技术综述,关于Portal技术综述的文章-Portal-尚观知识库.

本文地址: http://www.uplook.cn/blog/6/64977/

[ 本文作者:lkj107 ]

Portal技术综述,

转自:http://neujingxiao.blog.163.com/blog/static/51394720087732536269/

从上世纪八十年代起,随着个人计算机(PC) 的发展,企事业单位逐步建立起计算机应用系统来处理传统事务。从简单的单机应用逐步发展到局域网系统,进而建立跨地区垮国家的广域网系统。从单纯的以科学 计算和数据管理为主的系统逐步发展成综合的信息处理系统和办公自动化系统。从九十年代起随着互联网的普及,企事业单位的计算机应用系统也进入了互联网时 代,以Web网站为核心的面向全球的信息系统正在广泛建立。

在 企事业单位内部的各种信息系统逐步发展完备的情形下,新的问题新的需求逐步浮现出来:各种信息系统自成一体,使用和维护成本增大;信息资源不能共享,数据 一致性维护的成本太高;用户需要多次重复登录进入不同系统,操作繁琐,工作效率受限;用户不能按需获取信息定制内容;现有系统缺少用户之间的协作支持等 等。因此,如何将各阶段建立起来的各式各样的信息系统资源重新整合,即把一个个的信息孤岛有效地联结起来,通过一个“港口”简便进出,正在成为信息系统深 化发展的首要问题。

Protal (门户)技术的出现,带来了解决这些问题的良方。Portal以用户为中心,提供统一的用户登录,实现信息的集中访问,集成了办公商务一体的工作流环境。利用Portal技术,可以方便地将员工所需要的,来源于各种渠道的信息资料集成在一个统一的桌面视窗之内。根据Portal提供的定制功能,部门主管可以为本部门人员量身定制一套特有的信息门户,将部门共同所需信息有效地组织在统一的Web浏览器之中,并可根据人员级别和职能来设定相应的访问操作权限。

早在九十年代中期就有人提出了Portal的概念。最初出现的是像Yahoo这类的互联网服务商的Portal网站,后来出现了一些行业性Portal网站。早期由于没有相关的标准规范,Portal网站技术五花八门,产品功能和品质参差不齐,因而难以获得人们的认可,Portal技术的应用,Portal网站的发展出现过短暂的停滞。

随着Web技术的广泛应用和Web相关标准的逐步完善,特别是在2003年获得业界通过的两个标准——JSR-168WSRPPortal的发展确立了方向,更是随着SOA理念的实际化使得Portal愈加火热起来。

一、Portal架构

(一)PortalPortlet概念

Portal”一词在英语中解释为“入口,大门”,中文翻译为“门户”。在SunPortlet技术规范JSR-168Java Specification Request 168)中定义为:Portal是基于Web的应用,通常提供个性化,单点登录,整合不同资源的综合信息展示平台。

Portal展现在最终用户面前的是类似于Web网页的Portal页面,也许有些Portal主页制作的更像是一个桌面系统的界面,更能获得用户的认可。

构成Portal页面的是能够建立和展现不同内容的一系列PortletPortal使用Portlet作为可插拔用户接口组件,提供信息系统的表示层。

Portlet是部署在特定容器内用来生成动态内容的可重用Web组件。Portlet处理从Portal传递来的用户请求,动态生成输出内容的一个片段,展现在Portal页面的某个位置上。

Portlet的主要特点:

Portlet 是基于Java技术的Web 组件。

由专门的容器管理 Portlet

Portlet用于生成动态内容。

Portlet只生成标记片段,并不产生完整的Web页面。由Portal负责将Portlet片段整合成完整的Portal页面。

Portlet不直接捆绑到URL

Web用户通过PortalPortlet进行交互。

Portlet具有预定义的模版和视窗状态。

Portlet容器是Portlet的运行环境,它负责管理Portlet的生命周期,保存Portlet的定制信息。通俗的讲,Portlet容器就是Portlet组件工作的管理器,这一点类似于Servlet容器和Servlet之间的关系。

(二)Portal结构

Portal是一个Web页面,它由一个或若干个Portal页面组织而成。而每个Portal页面又是由一个或若干个Portlet窗口构成的——Portlet是以窗口的形式出现在Portal页面里。如图1所示是一个Portal页面结构的示例,有四个Portlet窗口。

 

Portlet窗口由两部分构成:其一是外观,包含有标题条、窗口控制和边框样式;其二是内容,即Portlet片段。

2显示了Portal服务器、Portlet容器以及Portlet之间的关系。

(三)Portal组成架构

Portal系统一般由Portal服务器、支持ServletJava应用服务器或Web服务器、数据库服务器、LDAP服务器组成,此外还可以加上身份认证和访问控制服务器(Identity ServerAccess Manager)等。前面所说的Portlet容器通常是打包在Portal服务器中的。

有的Portal系统可以工作在具有Servlet容器的Web服务器上,但多数Portal要求在Java应用服务器上运行。如Liferay Portal既能在TomcatServlet容器+Web服务器)上使用,也可以在JBossASJava应用服务器+Web服务器)上运行,而JBoss Portal只支持JBossASJava应用服务器。

通常,Portal的定制信息是保存在数据库中的,几乎所有的Portal系统产品需要数据库管理系统的支持。

LDAP服务器主要用在用户管理、身份认证和权限管理上。有些Portal系统也支持使用数据库来管理用户身份和权限。

为了解决用户单点登录和访问控制问题,也许还要增加这方面的专业服务软件,如Idengtity ManagerAccess Manager等。

此外,Portal服务器也支持其它应用服务系统的协同工作,比如同电子邮件(Email),网络交谈(Chat),网络会议(Web Conference),以及基于Lotus DominoOA系统等直接对接联动。

(四)Portal应用架构

4Portal应用的一个示例架构图。

(五)Portal主要功能

1、单点登录(SSOSingle Sign-On):Portal提供对各种应用系统和数据的安全集成,用户只需从Portal服 务器登录一次就可以访问其它应用系统和数据库。对于安全性要求较高的业务系统,如电子银行、电子交易系统等,通过传递用户身份信息,如数字证书信息、数字 签名信息等进行二次身份认证,保证单点登录的安全性。单点登录既减少了用户在多个应用系统反复登录多次认证的麻烦,更是简化了各种应用系统对用户及其权限 的一致性维护管理。

2、资源整合:能够把各种不同应用的内容聚合到一个统一的页面呈现给用户,实现同应用系统实时交换信息。能够从各种数据源如数据库、多种格式的文件档案、Web页面、电子邮件等集成用户所需的动态内容。

3、定制与个性化:能够为不同角色的用户制定不同功能权限的Portal页面。同时,用户自己也能够按照喜好在规定的权限下定制自己风格的页面和内容,如可以定制Portal页面,取舍不同功能和内容的Portlet窗口,自行布置Portlet窗口的摆放位置,可以对Portlet窗口外观,如标题,图标,颜色等进行个性化设置。

4、协作功能:为用户提供即时讨论,聊天,论坛,电子邮件以及语音或视频会议等功能。

5、工作流:支持根据业务处理规则建立起来的工作流任务处理,比如审批流程等待办事宜。

6、信息检索:从多种数据源检索动态信息资料。

7、客户端:除了Web浏览器外,可以为PDA和手机提供接口,实现移动接入服务。

二、Portal标准

建立一个以标准为依托的Portal才能很好地保护自己的投资,既便于同现有应用系统连接,也使得同第三方的相关产品更容易接口。在2003年先后发布的JSR-168WSRP两大标准为Portal的发展奠定了基础,结束了战国纷争的局面,Portal的发展和应用将会更加广阔长远。

下面介绍几个与Portal紧密相关的技术标准:

1WSDL——Web Service Description Language

Web服务描述语言。WSDL是用来描述Web服务和说明如何与Web服务通信的XML语言。WSDL语言使用XML格式来描述信息的接口、访问格式和处理形式。

WSDL描述信息内容。

2SOAP——Simple Object Access Protocol

简单对象访问协议。SOAP是一种在无中心的分布式环境下,应用系统之间交换结构化信息和特定类型的信息所使用的基于XML的轻量级协议。SOAP允许任何信息对象在任何语言、任何平台上使用多种传输协议实现传输处理。

SOAP定义信息的传输处理。

Web应用环境中,通常把SOAPWSDL结合起来,利用HTTP协议实现应用系统之间交换各种类型的信息对象。

3JSR -168——Java Specification Request – Portlet Specification

Java Portlet规范。JSR-168为业界明确了Portal的定义,制定了Portlet规范标准,从而解决了基于JavaPortal之间,以及同其他Web应用系统之间的互操作性。遵循JSR-168Portlet将具有适用于所有Portal服务器和Web应用系统,支持多种类型的客户端,支持本地化和国际化,具备确定的安全性,允许Portal应用程序热部署和重新部署。

4WSRP——Web Services for Remote Portlets

远程Portlet Web服务协议。WSRP定义了PortalPortlet容器服务之间标准化接口的一个Web服务标准。WSRP允许在Portal之间或其他Web应用上即插即用,具有互操作性,提供可视化的、面向用户的远程Web服务。

远程Portlet在远程服务器上作为Web服务运行,其服务可以发布到公共的或单位自己的UDDI服务器上。Portal或其他支持WSRP的应用系统通过UDDI服务来查找并使用远程系统提供的WSRP服务内容。

WSRP的典型应用是把天气预报、即时新闻、股市行情等嵌入到自己的Portal中(在国外有专门的WSRP内容提供商提供这种服务)。

WSRP使用了WSDL定义应用程序的接口,并以SOAP作为通讯标准。

5、其他规范标准

此外还有一些与Portal有一定关联的技术标准,在开发建立Portal应用中将会使用到:

UDDIUniversal DescriptionDiscovery and Integration

JSR-170/283Java Specification Request – Content Repository for Java Technology API

JAASJava Authentication and Authorization Service

LDAPLightweight Directory Access Protocol

SAMLSecurity Assertion Markup Language

BPELBusiness Process Execution Language for Web Services

三、Portal应用实现

Portal可以从本地或远端获得数据资源:数据资源可以来自于本地或异地的数据库,应用系统,公共信息内容供应商(RSS,提供新闻、财经信息、天气预报等),Web站点或其他Portal。此外,Portal还可以提供日历、工作流、电子邮件、论坛、博客、Wiki、即时交谈、电子会议等等协同工作的应用功能。可见,Portal是一个有别于传统桌面应用和Web网站的全新应用系统,是各种信息处理的集中展现平台,是用户日常工作的综合台面。

显然,建立一个良好的Portal应用需要充分考虑各种应用系统和数据资源的整合问题:

1、现有应用系统和数据资源的利用 对能够改造利用的,要开发相应的Portlet组件来重新实现;对不能改造的可以通过链接的方式跳转到这些系统,其中的数据库资源可以采取单纯读取的方式获得;还有些封闭的专业应用系统可能完全无法接入Portal,可以采取定期卸载的方式获得它的数据库资源。

2、新建应用系统的考虑 需要以Portal理念进行设计,按照相关标准来开发实现应用功能的Portlet组件,然后集成到Portal系统使用。

3、单点登录与权限管理  对于新建应用系统或能够改造的现有应用系统,通过Portlet组 件比较容易实现单点登录,进行统一用户认证和用户权限的控制。当然,对那些安全性要求较高的应用系统还可以在这些系统内部进行二次认证和授权处理。对那些 不能改造的应用系统显然也无法实现单点登录,用户需要重新登录到这类系统,用户管理和权限控制还依赖于这些系统自己处理。

(一)单点登录和权限控制

单点登录是为了方便用户进入多个应用系统,减少用户多次登录,免除用户记忆多套用户名和密码的麻烦。

单点登录涉及到两个问题,一是身份认证,二是权限控制。

身份认证是Portal系统提供访问控制的第一步,即确认用户是谁,能否进入系统。通常要求用户提供用户名和口令,必要时要求提供用户的数字证书,也可以配合使用IC卡、指纹等验证手段。

权限控制或授权确定一个用户的角色和级别,从而控制用户的访问许可,即决定用户能查阅哪些资料,能进行哪些操作等等。Java EE架构采用了基于角色的访问控制策略(RBAC)。RBAC的基本思想是把对用户的授权划分成两个分配关系,即“用户—角色”和“角色—权限”。RBAC的好处是便于应用系统的开发,使得程序设计相对独立和透明化,只是在应用系统部署使用时才通过“角色”把“用户”和“权限”关联起来,而且对用户和权限的调整配置容易实施。

用户与角色之间是多对多的关系,即一个用户可以被分配给多个角色,多个用户也可以分配给同一个角色。

角色与权限之间也是多对多的关系,即一个权限可以与多个角色相关,一个角色也可以包含多重权限。

Sun公司建立了具有可堆叠和可插接功能的JAAS框架,为Java应用系统提供安全而灵活的身份认证和授权机制。如在JBossAS应用服务器环境下,利用JAAS技术,JBossAS提供了几种身份认证模块(使用者也可以自己编写新的身份认证模块),并且可以实现多级认证(堆叠)。对Web应用和EJB等则通过配置文件定义访问角色和访问方式以实现安全控制。对没有提供JAAS支持或支持不足的Web服务器或Java应用服务器,使用者需要在自己的应用程序中编写相关代码或模块来支持JAAS实现身份认证和权限管理。

此外还需要考虑不同领域内不同应用之间的信任关系,解决跨越应用系统的身份认证和访问控制问题。这一点需要用到SAMLFederationLiberty等技术规范,通过传递用户认证资料取得应用系统之间的认证互信。

专业的Web服务软件公司能够提供比较全面的解决方案,其产品为Web环境下的各种应用提供可靠的身份认证和访问权限管理。比如Sun公司的Access Manager 7.1Oracle公司的Oracle Access Manager 10gIBM公司有Tivoli Access Manager(提供用户单点登录)和Tivoli Identity Manager(解决访问控制问题),还有BEA公司的BEA AquaLogic Enterprise Security等。

相比之下,开源软件在这方面比较瘦弱,基本上没有专门的软件产品,只能提供一定的支持方案。例如通过对JBossASTomcat做一些配置,可以支持使用耶鲁大学的CAS实现单点登录功能,也能够实现JAAS来获得用户身份认证和对应用资源的访问控制。这样,建立在JBossASTomcat之上的应用如JBoss PortalLiferay PortalApache JetSpeed当然也就具备了这些功能了。

在用户管理、身份认证和权限控制方面,无论是商业的或开源的Portal产品多数喜欢采用LDAP,当然也有的支持使用数据库。LDAP的好处一是它可以方便的按类别存储任何类型的数据信息;其二,它的树形存储结构类似于一个企事业单位的组织架构,容易对应;三是它同应用系统接口容易,各个LDAP产品的接口都一致无需特别配置;四是它对数据信息的访问安全控制方便;五是它偏向于相对固定数据信息的查询使用,效率较高,维护也方便。

(二)Portlet开发

建立Portal应用系统的主要任务之一就是设计各式各样的Portlet组件,实现应用系统的各种功能。虽然多数Portal系统会附送一些常用的Portlet组件,可以满足一些公共服务需要,但跟本单位工作事务和业务处理相关的大量Portlet组件必须有专门人员进行细致的设计和开发。

Portlet的设计开发有必要遵循JSR-168规范和WSRP标准,以适合各种类型的Portal服务器。在具体的实现上,也将会用到WSDLSOAPUDDI相关技术规范,以便同Web服务应用系统进行信息交互处理。

开发Portlet主要有两种方法,一是借助于Portal产品商提供的可视化的预制开发工具,二是应用Java语言直接编程。预制开发工具为Portlet开发者提供了许多有益的帮助,如自动产生必要的配置文件,预制了程序代码框架,提供所见即所得编辑和调试环境等等。但无论如何,Portlet开发的重点是Portlet片段内容的产生和处理,主要以JSP为主配合HTMLJavaScript等网页开发技术,再借用JSFStrutsHibernate等框架来简化开发。

Portlet处理信息资料主要有以下三种情况:

1Portlet在本地直接处理信息资料

Portlet在本地直接同数据库、LDAP或其他数据存储系统进行查询、更新等操作,也可以使用其他Java资源如JavaBeanEJB组件进行交互处理。

2Portlet访问其他应用系统的Web服务

Portlet需要访问其他应用系统资源时,一般是采用SOAP技术实现对Web服务的请求操作,如图5所示。

3Portlet访问其他Portal服务

Portal可以把公共的Portlet信息发布到UDDI资源中心,以供其他Portal选择使用。需要其他Portal提供特定服务的Portal可以通过在UDDI中心查找定位来获取需要的Portlet提供服务(图6)。此种情况使用了WSRP技术来实现。

(三)Portal定制

1、业务处理

有别于传统的应用系统的开发,开发Portal应用的重点是Portlet,并每个Portlet并不是一个完整的系统而是一个实现特定信息处理功能的Web组件。因此,如果要把一个传统的应用系统全部转换到Portal来,就需要把原系统进行分解,把每个操作功能重新用Portlet来实现(不一定是一对一的),然后根据系统处理的要求组装到Portal页面上。

用户在使用传统的应用系统时,是从一层层的菜单逐级进入实际操作程序的界面的,操作繁琐,使用不便,容易遗漏。转入Portal系统后需要简化这种使用方式,把应用系统操作程序制作成的Portlet组件,直接嵌入某个Portal页面上或者按操作类型,时间先后或缓急程度顺序放置在“待办事宜”列表中,提示用户快速处理。

Portal页面的组装即页面布置跟用户的级别和角色有关。Portal架构员需要根据每项业务的处理流程和每个Portlet的功能,按照每类用户的级别和角色来配置用户的Portal页面。与用户无关的或无操作权限的Portlet不应该出现在用户的Portal页面上,用户也不能把它添加进来。对于用户必须使用的Portlet也要限制使用者不能把它从Portal页面中移除出去。

2、协作功能

提供同事之间的协同工作支持是Portal的功能之一。协同工作主要包括电子邮件、实时交谈(Chat,类似于QQ)、网络会议(语音或视频会议)、维客(Wiki,允许参与者修改讨论的文章)、博客(Blog)、留言等等。在工作处理中遇到疑问需要向同事了解情况,可以即时开启Chat展开讨论,也可以召开网络会议。

另外可以设计一个手机短信Portlet,把需要尽快处理的事项及时发送短信通知相关人员。

有的Portal产品附带了一些协同工作的Portlet和服务软件,有的需要专门购买,也有开源软件可以免费使用。

3、其他功能

提供诸如信息检索、公告、新闻、日历、通信录等辅助功能以方便工作。如果连接互联网,还可以获取天气预报,提供证券行情等公共服务。

来自CSDN网站lkj107博客