Archive for 二月, 2011

终于完成了产品的基本功能,但是由于最后几天在赶进度,而且心里有点急,导致一些烂代码的引入。为了避免破窗户效应,决定利用一到两天的时间对代码进行重构。

1.分离原有代码的静态文件

图片类 配置独立host,如 image.diff.com
BASE_DIR(wwwroot/happy_try*)/images 不分项目全部文件都放置在这里
前端文件类 配置独立host,如 xfiles.diff.com
BASE_DIR(wwwroot/happy_try*)/xfiles/taobao/css/
BASE_DIR(wwwroot/happy_try*)/xfiles/taobao/js/
BASE_DIR(wwwroot/happy_try*)/xfiles/taobao/flash/
BASE_DIR(wwwroot/happy_try*)/xfiles/taobao/image/
BASE_DIR(wwwroot/happy_try*)/xfiles/admin/css/
BASE_DIR(wwwroot/happy_try*)/xfiles/admin/js/
BASE_DIR(wwwroot/happy_try*)/xfiles/admin/flash/
BASE_DIR(wwwroot/happy_try*)/xfiles/admin/image/

图片和前端文件的一级域名是与web一级域名不同的,这样可以设置不发送cookie。

数据库中如果需要存储图片路径的话,存储相对路径。这样可以保证图片迁移到便捷。

2. 分离第三方插件及lib库

由于我们有基于sina等开放平台的应用,需要使用他们提供的SDK或者lib库,这些都统一放置于BASE_DIR/plugins下面

BASE_DIR/plugins/sina/

BASE_DIR/plugins/taobao/

3. 严格分离core层代码

刚开始设计的时候,由于不是很了解整个业务的流程,只是进行的宽泛的框架设计,目前证明整体而言是没有问题的,但是细节上需要修改。core层应该是各个application层的基础,我需要将最核心的功能抽离出来,进行独立的封装。比如lottery、provider、product、user、invite等。core层的代码严格禁止调用application层的代码(前两天居然写出了这么恶心的代码,汗颜)。

需要保证core层的代码是可增加,但不修改的。

core层的目的是,利用这里的组件(class)可以快速的搭建起一个application,并且不影响到其他applications。

如果某application有特殊的功能需求,可以在继承并重载core层的class。

  • core层的类,为动作(method)的集合体;数组作为数据的载体,不专门定义元数据类(由于json_encode无法encode class的private数据,即使定义了__set和__get method)
  • 重新设计control与view层的交互方式,原先大量使用global变量,很麻烦。借鉴了cake框架的方式,所有application的control继承自control父类,需要暴露给view的变量,$this->set(‘argname’, ‘argval’),在control中include view前,extract control类保存变量的数组,在view中直接使用。
4. 使用strategy模式重写lottery和provider类

我们的项目中,lottery(抽奖流程)和provider(领取流程)是两个扩展性和可变性极强的组件,所以特别对其进行的设计,使用了继承和strategy模式。

以lottery为例,lottery及其子类在其go函数中,组装一至多个strategy子类,并分别执行其go函数。

贴一个strategy类的go函数:

class Taobao_LSExpress extends Core_LtyStrategy{
        function go($args){
                if (empty($args['award']) || empty($args['product'])){
                        throw new Core_PrdProvideException("Arguments fail");
                }

                if ($args['award']['status'] != Core_Record::PREPARE){
                        throw new Core_PrdProvideException("Status errror");
                }

                $ret = $this->add($args['product']);

                $viewArgs = array(
                        'product' => $args['product'],
                );

                return $this->render("_taobao_express.html", $viewArgs);
        }

        protected function add($product){
                // Call taobao api, to add this product in "购物车"
        }
}
class Taobao_Express extends Core_PrdProvider{
        // Maybe we'll need taobao-info for this product, we'all add in future.

        function __construct(array $provider_db_array){
                if (empty($provider_db_array['product_id'])){
                        throw new Core_PrdProvideException("Need product id");
                }
                $this->product_id = $provider_db_array['product_id'];
        }

        function go($step, $product){
                $award = parent::check(User::get_instance(), $product);

                switch($step){
                        case 1:
                                $args = array(
                                        'award' => $award,
                                        'product' => $product,
                                );
                                $strategy = new Taobao_LSExpress();
                                $child_html = $strategy->go($args);
                                break;
                        default:
                                throw new Core_PrdProvideException("Unknown step");
                }

                return $child_html;
        }

}

需要从一个utf-8编码的页面,通过form表单提交数据给gbk编码的页面(bt啊bt啊)。

这时只需要利用form的accept-charset属性即可:

<form accept-charset=”gbk” >

但是ie不支持。。。所以还需要继续:

<form accept-charset=”gbk”  onsubmit=”document.charset=’gbk';”>

这样就ok了!不过当然最好一个站点内的页面是统一编码的,数据库也是统一编码,否则会带来太多莫名的问题了。

刚搬来两天,有点胡乱,之后要按这个时间表行事。

7点起床

7点20 出门买菜

8点太极

8点20 吃饭

8点40 开工

12点吃饭/洗衣服

13点开工(18点煮粥)

19点收工

19点30 吃饭、洗澡、洗衣服

20点 念经、看书、整理当天和明天的工作

22点30 睡觉

不准备加班太厉害,但是要保证工作时间是100%集中注意力的!

http://www.itbadu.com/seo/gjcpmcxgj.html#comment 这个是介绍工具的

http://www.flashplayer.cn/keywords/  这是下载地址

我们基于sina微博开发了一个应用。最近接到一个产品需求,针对我们的某些用户,发送新浪微博站内信。

本来是一个很简单的小需求,调用微博的几个api,判断是否符合filter条件,是则发送站内信。但是遇到以下几个问题:

1、新浪微博仅允许发送站内信给关注了“我”的人

2、微博api访问次数受限(http://open.t.sina.com.cn/wiki/index.php/Rate-limiting

针对第一个问题,我们从产品上做了一些调整,对于我们的活跃用户,请他们关注我们的官方帐号。并且在filter条件中增加了该relation的判断。

针对第二个问题,头就大了,我的app key是初级授权的:

默认REST API的访问限制是每小时150次,限制分用户和IP, 未授权的访问次数限制主要针对IP,登录后的请求访问限制主要针对用户。

普通授权限制情况:

  • 请求限制:单用户每小时150次
  • 发表微博:单用户每小时最大30次
  • 发表评论:单用户每小时最大60次
  • 发表私信:单用户每小时最大60次

最初,我就使用了同一个sina帐号,获取到oauth授权后,依次调用user/show, friends/exists,message/new接口,这样,在最坏情况下(就是filter都成功,并且发送mail),每个用户需要访问api 3次,假设有3w个用户,则共需调用api 9w次,按照以上限制,需要运行9w/150=23day!!!该效率显然不可接受。而且按照上面的说法,或许还加了ip限制,我们是小公司,服务器有限,如果一旦ip被封,后果不堪设想。所以想到利用新浪sae开放平台。

sae-sina-mail

sae-sina-mail

这里之所以做这种相对复杂的设计,也是因为SAE有每个http请求30sec的运行时间限制。所以,就需要把大的请求分割为小的,利用cron触发,并且利用task队列做一定的并发。

暨,这里,我们需要考虑到sina微博api 和 sae的两种限制!

为了突破微博api的限制,由于前面的filter并不一定要用官方帐号来做,所以注册了几个微博帐号,作为robot。而最终发送站内信必须用官方帐号,所以没办法,这里就要注意每小时60封信的限制。

index.php处理post数据,将userids文件分割为文件(每个文件1000行),拼装成sql句子,比如insert into table(a,b)values(1,2),(2,3),存储到sae的storage中,以特定的字符串为前缀命名。并且将发信的具体text、oauth token存储到mail_detail表中,获取其mail_id,作为sql句子中的数据。

cron_load每分钟运行一次,从storage中寻找特定的字符串为前缀命名的文件,读取其中的sql句子,执行。若成功,直接删除该文件。若失败,则删除该文件,将sql存储为其他字符串前缀命名的错误文件中。

cron_filter也是每分钟运行一次,获取所有可用的robots,调用http://api.t.sina.com.cn/account/rate_limit_status.json?source=接口可以获得该用户对应的访问状态。然后获取对应数目的待执行filter_target数据,与robot一一对应,生成task,存储到sae的taskqueue中。

filter.php作为task为调用,其传入参数包括filter_target_id和robot_name, robot_password,这里不使用sina微博的oauth sdk,因为oauth的获取太麻烦了,而是改用url加用户名、密码的方式:

function get_user_info_from_api($user_id, $no_login_user_name, $no_login_user_pwd){/*{{{*/
        $url = "http://api.t.sina.com.cn/users/show.json?source=".APP_KEY."&user_id=$user_id";
        $ret = request_without_login($no_login_user_name, $no_login_user_pwd, $url);
        return $ret;
}/*}}}*/

function request_without_login($user_name, $password, $url){/*{{{*/
        $s = curl_init(); 

        curl_setopt($s,CURLOPT_URL,$url);
        curl_setopt($s,CURLOPT_TIMEOUT, 30);
        curl_setopt($s,CURLOPT_RETURNTRANSFER,true);
        curl_setopt($s, CURLOPT_USERPWD, $user_name.':'.$password);
        curl_setopt($s,CURLOPT_HEADER,false); 

        $ret = curl_exec($s);
        if(curl_errno($s)){
                sae_debug("Can't run $url");
                return false;
        } 

        curl_close($s); 

        $ret_obj = json_decode($ret, true);
        if (isset($ret_obj['error_code'])){
                sae_debug("Run fail: $url, error:".$ret_obj['error']);
                return false;
        }

        return $ret_obj;
}/*}}}*/

如果filter条件成立,则生成mail_target存储到db中,等待之后的cron_mail进行处理。并更改filter_target的status为2(已处理不需要发信)或者3(已处理,需要发信).

cron_mail也是每分钟运行一次,从mail_target、mail_detail中获取发信的具体内容、token,并且以目标用户的名称拼接到发送文本前面,因为微博限制连续两封信不能同意内容,即使是发送给不同人的也不行。发送后,若失败,把失败原因存入mail_target。更新mail_target的status。

不过,sina的这些限制还确实是需要的,否则站骚就满天飞了。注大家站骚愉快,啦啦啦!

这是一篇受密码保护的文章,您需要提供访问密码:

js盲,迫于项目,无奈开始使用mootools,连蒙带猜。并且,貌似我对那些绚丽的效果没啥感觉,反而是对这些基本的、可以帮助人偷懒的功能比较感兴趣。

1、getElement和getChildren()[0]有什么本质区别?

$(‘trial-info’).getElement(‘div’);
$(‘trial-info’).getChildren(‘div’)[0];

这两条语句的返回值貌似是一样的,都是$(‘trial-info’)的第一个child element。

2、css selector有哪些? div, .classname,#id,还可以使用:

  • = : is equal to
  • //selects the input with the name 'phone_number'
    $('body_wrap').getElements('input[name=phone_number]');
  • ^= : starts-with
  • //selects the input with a name beginning with phone
    $('body_wrap').getElements('input[name^=phone]');
  • $= : ends-with
  • //selects the input with a name ending in number
    $('body_wrap').getElements('input[name$=number]');
  • != : is not equal to
  • //selects the input which is not named address
    $('body_wrap').getElements('input[name!=address]');

甚至还包括even和odd,可以在表格等应用中区分奇偶行等。

3、element的getProperty都可以get哪些值呢?就是property指哪些东西?比如一个input 是不是就是包含了name、value等?

再说event事件里都可以包含哪些属性?像按键事件event.key、event.shift

去哪里可以查到类似的这些对象的属性?http://mootools.net/docs/core/Element/Element#Element-Properties 这里可以看到element的properties。

4、document.getElementsByTagName(‘a’) 和 $$(document.getElementsByTagName(‘a’))又有什么区别?

5、尽量把function放在外部引用的js文件中,执行的js代码在不能避免的情况下,可以放在页面上的domready代码中。如果需要php控制传递某些参数,可以打印在页面上作为js变量,调用function的时候传入。减少页面上的js、css代码,有利于SEO。

6、inject函数,可以bottom/top/before/after四种方式调整或者插入element。

Bottom and top will place the injected element inside a selected element, in the top or bottom spot. Before and after on the other hand, will inject one element before or after another, but not inside.

7、clean这个函数挺牛的,它可以去除多余的空白,不论是字符串的头、尾、还是中间!

8、这个函数挺搞笑的:它替换中括号里的内容,如果rep中没有定义对应的替代,则留白。

var str = “one is {one}, and two is {two}”;

var rep = {

one: ‘the first ‘

};

str.substitute(rep)

9、字符串的test和contains函数的区别在于:contains寻找整词!

我们的项目需要往几个平台发微博,而由于各个平台接口稳定性和调用时间不同,而且有可能需要将用户的一条微博同步到多个平台去,所以不太适合做同步调用。考虑到有两种实现方式:1、利用php的socket接口,读取完POST、GET参数后即关闭socket,然后继续处理发送微博;2、队列方式。

正好当前手头上有个新浪SAE的账号,而它提供了TaskQueue队列,就试了下。

主要接口就两个:/recv.php 和 /worker.php。

recv.php接收用户请求,必须的参数有:app_key, app_key_secret,access_key,access_key_secret,platforms和微博内容。其中platforms用逗号分隔多个平台名称,如“sina,qq”。这里明文传递app_key, app_key_secret,access_key,access_key_secret其实不大安全,之后会对其进行加密。

        $task = array();
        $ret_uniq = array();
        foreach($platforms_arr as $p){
                if(($uniq_id = save_recv($app_key, $app_key_secret, $access_key, $access_key_secret, $p, $text))<=0){                         return_error("系统错误,请稍后再试");                 }                 // Make package                 $postdata = sprintf('uid=%d&%s=%s', $uniq_id, MY_KEY_NAME, MY_KEY_VAL);                 $task[] = array('url'=>DOMAIN."/worker.php", "postdata"=>$postdata);

                // prepare return values
                $ret_uniq[$p] = $uniq_id;
        }

        // save it to queue
        $queue = new SaeTaskQueue('packages');
        $queue->addTask($task);
        $ret = $queue->push();
        if ($ret === false){
                mylog("push fail: no.".$queue->errno().' ,'.$queue->errmsg());
                return_error("无法加入到队列中,请重试");
        }

        // return uniq id
        return_ok($ret_uniq);

由于SAE对于worker的执行时间有限制,所以将每个平台作为一个package存储,以减少每一次worker的运行时间。以上代码,将package的具体信息存储在数据库中,将唯一key作为worker的执行参数。

worker.php获取到唯一key,从数据库中读取到package的具体信息,通过package的status检查该package是否被处理过,然后调用send方法发送微博。

       function send(array $package){
                if (empty($package) || empty($package['secret']) || empty($package['platforms'])
                                || empty($package['content'])){
                        return false;
                }

                if(!($token = explode(',', $package['secret']))){
                        return false;
                }
                list($app_key, $app_key_secret, $access_key, $access_key_secret) = $token;

                if(!($platforms = explode(',', $package['platforms']))){
                        return false;
                }

                foreach($platforms as $p){
                        $wp = O_Weibo::create($p, $token);
                        $wp->send($package['content']);
                }

                return true;
        }

这里send方法仍然支持逗号分隔的多platforms。针对每一个platform名称,调用抽象工程类O_Weibo::create生成其对应的object。例如,新浪的微博类方法:
                protected function __construct(array $oauth){
                        if (empty($oauth)){
                                throw new O_WeiboException("We need app_key, app_key_secret, oauth_token and oauth_token_secret");
                        }
                        $this->app_key = trim($oauth[0]);
                        $this->app_key_secret = trim($oauth[1]);
                        $this->oauth_token = trim($oauth[2]);
                        $this->oauth_token_secret = trim($oauth[3]);

                        // Maybe we should check this oauth keys correct?

                        try{
                                $this->client = new WeiboClient( $this->app_key, $this->app_key_secret, $this->oauth_token , $this->oauth_token_secret);
                        } catch (Exception $e){
                                throw new O_WeiboException("Can't init WeiboClient: ".$e->get_message());
                        }
                }

                public function send($text){
                        return $this->client->update($text);
                }

另外,由于SAE禁止了curl系列函数,改为提供SaeFetchurl类,所以需要对新浪微博的SDK中的http函数进行修改:
                        $this->http_info = array(); 

                        $f = new SaeFetchurl();

                        $f->setHeader(CURLOPT_USERAGENT, $this->useragent);
                        $f->setHeader(CURLOPT_RETURNTRANSFER, TRUE);
                        $f->setHeader(CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer); 
                        $f->setHeader(CURLOPT_HEADERFUNCTION, array($this, 'getHeader')); 
                        $f->setHeader(CURLOPT_HEADER, FALSE); 

                        $f->setMethod($method);

                        switch ($method) { 
                        case 'POST': 
                                if (!empty($postfields)) { 
                                        $f->setPostData($postfields);
                                } 
                                break; 
                        case 'DELETE': 
                                if (!empty($postfields)) { 
                                        $url = "{$url}?{$postfields}"; 
                                } 
                        } 

                        $header_array = array(); 
                        
                        $header_array2=array(); 
                        if( $multi ) 
                                } 
                        } 

                        $header_array = array(); 
                        
                        $header_array2=array(); 
                        if( $multi ) 
                                $header_array2 = array("Content-Type: multipart/form-data; boundary=" . OAuthUtil::$boundary , "Expect: ");
                        foreach($header_array as $k => $v) 
                                array_push($header_array2,$k.': '.$v); 

                        $f->setHeader(CURLOPT_HTTPHEADER, $header_array2 ); 
                        $f->setHeader(CURLINFO_HEADER_OUT, TRUE ); 

                        //$f->debug(true);

                        $response = $f->fetch($url);
                        
                        // TODO: process error

                        $this->http_code = $f->httpCode();
                        //$this->http_info = array_merge($this->http_info, curl_getinfo($ci)); 
                        $this->url = $url; 

                        return $response; 

在线视频 http://www.tudou.com/programs/view/xC3T9s5vdKk/

豆瓣评论http://movie.douban.com/subject/3793023/

刚刚看完这部叫3 Idiots的印度电影,酣畅淋漓的感觉!做了这么多年学生,从来没有人能这么扬眉吐气的说出对教育制度的反抗,哦,不止是说,而且是用行为去做!太爽了!

喜欢什么,就去做什么,把兴趣与工作结合起来,就可以自然而然的做到卓越。再或者,其实卓越的仅仅是少数,我们能开心的生活和工作就足够了。活着,不是为了维持心脏的跳动,而是要活出质量!

哈,而且做工程师能做到这个地步,用吸尘器接生,用汽车蓄电池发电……

恩,不过我也不差啊,我喜欢用代码抓取小说、格式化存到我的IPAD上,喜欢用脚本自动化各种费力的机械化动作,偷懒是应该是程序员的美德。

强烈推荐3 Idiots!