之前一直使用一个叫做新浪连接的插件来同步我的博客内容到新浪微博上。最近今天发现,总是同步失败,发布内容时页面报500错误。仔细查了下,是新浪强制使用了Oauth2的认证方式,插件使用的老认证方式已经不被支持了。到插件官网去看,作者貌似没有更新插件的意思,只能自己再想他法鸟。

找了一下,发现微博通提供api可以调用,同时提供了一个现成的wordpress插件,只需要注册微博通,然后在里面绑定微博账号,当然,可以绑定新浪、人人、腾讯等各种微博,一发全发,这个是之前那个插件没有的优势。不过也有令人不爽的地方,首先是这个插件功能比较弱,只能发文章标题,不能发摘要,不能发图片。参考新浪连接,我就把代码改了下,现在可以发标题摘要也能发图片了。代码如下:

<?php
/*
Plugin Name: 微博通同步发布
Plugin URI: http://www.wbto.cn
Description: 自动把你的博客文章同步到微博通,微博通将同步至你所绑定的各个平台。
Version: 1.0
Author: yige <abcwuwuwu@qq.com>
Author URI: http://t.qq.com/abcwuwuwu

Date: 2011年3月17日 23:07:30
Modified by :zhujianfeng <http://weibo.com/fdjianfeng> 2012-07-27
*/

function wbto_install() {
	global $wpdb;
	$table_name = $wpdb->prefix."wbto";
	if($wpdb->get_var("show tables like '$table_name'") != $table_name) {
		$sql = "CREATE TABLE " . $table_name . " (id mediumint(9) NOT NULL AUTO_INCREMENT, wbto_username VARCHAR(100) NOT NULL, wbto_password VARCHAR(100) NOT NULL, );";
	}
	require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
	dbDelta($sql);
}

function send_to_wbto($post_ID) {
	$username = get_option('wbto_username');
	$password = get_option('wbto_password');
	$posted = get_post($post_ID);

	$post_title = $posted->post_title;
	$post_content = get_post_excerpt($posted);

	$title_len = mb_strlen($post_title,'UTF-8');
	$content_len = mb_strlen($post_content,'UTF-8');
	$rest_len = 110;

	if($title_len + $content_len> $rest_len) {
		$post_content = mb_substr($post_content,0,$rest_len-$title_len).'... ';
	}
	$status = '【'.$post_title.'】 '.$post_content;

	$pic = get_post_first_image($posted->post_content);

	$fields = array();
	$fields['source'] = 'wordpress';
	$fields['content'] = urlencode($status.' '.$posted->guid);
	$wbto_url = "http://wbto.cn/api/update.json";
	if ($pic){
		$wbto_url = "http://wbto.cn/api/upload.json";
		$fields["imgurl"] = $pic;
	}

	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $wbto_url);
	curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
	curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER,TRUE);
	curl_setopt($ch, CURLOPT_TIMEOUT, 10);
	curl_setopt($ch, CURLOPT_POST, TRUE);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
	$result = curl_exec($ch);
	curl_close($ch);

}

function wbto_menu() {
add_options_page('微博通同步设置', '微博通同步', 8, __FILE__, 'wbto_options');
}
if(!function_exists('get_post_excerpt')){
	function get_post_excerpt($post){
		$post_excerpt = strip_tags($post->post_excerpt); 
		if(!$post_excerpt){
			###第一种情况,以<p>开始,</p>结束来取第一段 Windows live writer
			if(preg_match('/<p>(.*)<\/p>/iU',trim(strip_tags($post->post_content,"<p>")),$result)){ 
				$post_content = $result['1'];
			} else {
			###第二种情况,以换行符(\n)来取第一段   
				$post_content_r = explode("\n",trim(strip_tags($post->post_content))); 
				$post_content = $post_content_r['0'];
			}
			$post_excerpt = explode("\n",trim(strip_tags($post->post_content))); 
   			$post_excerpt = $post_excerpt['0'];	
		}
		$post_excerpt = trim(strip_tags($post_excerpt));
		$post_excerpt = str_replace('"', '', $post_excerpt);	
		// replace newlines on mac / windows?
		$post_excerpt = str_replace("\r\n", ' ', $post_excerpt);
		// maybe linux uses this alone
		$post_excerpt = str_replace("\n", ' ', $post_excerpt);
		$post_excerpt = mb_substr($post_excerpt,0,120);

		return $post_excerpt;
	}
}
if(!function_exists('get_post_first_image')){

	function get_post_first_image($post_content){
		preg_match_all('|<img.*?src=[\'"](.*?)[\'"].*?>|i', $post_content, $matches);
		if($matches){		
			return $matches[1][0];
		}else{
			return false;
		}
	}
}
function wbto_options() {
	echo '<div class="wrap">';
	echo '<h2>微博通同步</h2>';

	echo '<form method="post" action="options.php">';
	echo wp_nonce_field('update-options');

	echo '<table class="form-table">';

	echo '<tr valign="top">';
	echo '<th scope="row">用户名 <a href="http://www.wbto.cn/?app=wp">注册</a></th>';
	echo '<td><input type="text" name="wbto_username" value="'.get_option('wbto_username').'" /></td>';
	echo '</tr>';

	echo '<tr valign="top">';
	echo '<th scope="row">密码</th>';
	echo '<td><input type="password" name="wbto_password" value="'.get_option('wbto_password').'" /></td>';
	echo '</tr>';

	echo '</table>';

	echo '<input type="hidden" name="action" value="update" />';
	echo '<input type="hidden" name="page_options" value="wbto_username,wbto_password" />';

	echo '<p class="submit">';
	echo '<input type="submit" name="submit" id="submit" class="button-primary" value="保存更改" />';
	echo '</p>';

	echo '</form>';
	echo '</div>';

}

add_action('admin_menu', 'wbto_menu');
add_action('publish_post', 'send_to_wbto');
?>

还有另外一点比较不爽的就是,发送频率有限制,具体限制多久不知道,但是根据我的测试,5分钟内发两次是不行的。
参考的文章如下:
http://fairyfish.net/project/sina-connect/
http://www.jsxubar.info/wordpress-use-wp_wbto-plugin-sync-post-to-weibo.html

Posted in PHP.

工作中有个功能想用js的eval来实现,上网搜了一把,一堆说eval不好的,总结一下,记在这里。

Eval函数是js中用来执行一段字符串的,当然前提是这段字符串的内容的是js代码。但是为什么是evil的呢,原因如下:

1、使用eval的代码不好调试。一般使用eval的地方,代码都是动态生成的,虽然动态生成具有很大的灵活性,但是一旦出错,很难找出bug在哪里,毕竟你要调试的代码没有写在静态文件里。
2、使用eval的代码不好维护。想一下,一个程序员写的代码,几个月之后回去再看,可能需要看一阵子才能明白写的是什么,如果你用eval,那么就意味着你写了一段生成代码的代码,维护起来代价就更高了。
3、性能差。eval在执行的时候意味着浏览器又要开一个解释器(实际也许不是这样,但是总需要做点什么才能让这段文本执行起来),开解释器的这个过程代价是很高的,导致性能会急剧下降。例如下面的两段代码,片段1中使用了eval,在我的chrome上执行,平均耗时在2300ms左右,片段2没有使用eval,平均耗时只有150ms左右。可见,性能差距巨大。

//片段1
var count = 1;
var t1 = new Date();
for (var i = 0; i < 10000000; i++){
	eval("count = count + 1;");
}
var t2 = new Date();
alert(t2 - t1);

//片段2
var count = 1;
var t1 = new Date();
for (var i = 0; i < 10000000; i++){
	count = count + 1;
}
var t2 = new Date();
alert(t2 - t1);

4、可能会有注入危险。设想这么一个情况,你从用户那里接收一段js代码字符串,然后在服务端使用类似于Node之类的技术来使用eval执行用户输入的代码,那么用户就完全可以写一段代码来获得你的机器控制权。

5、影响优化。现代的js解释器一般都会对js代码进行优化,但是当你的代码里有eval的时候,就意味着在执行之前这段代码是不固定的,解释器就不知道该怎么优化,所以当解释器碰到eval的时候基本就放弃优化了。

但是一直没弄明白的是,既然这玩意这么eval,那么为什么当时把他设计出来了呢?

围脖上看到有人说AppJS,去官网围观了下,果然NB,这东西还真就能用JS写图形化本地程序。

传统的图形化本地程序(或者说客户端程序)一般是一个可执行文件,在windows上一般是个exe,执行之后有个图形界面供用户交互。能写图形化本地程序的语言比较多,C/C++、java、C#、python等等,可能有些程序需要图形库支持,比如QT、GTK之类。JS写图形化本地程序之前我还没有听说过,今天这个算是看了眼界了。

简单来说,AppJS把Chromium和Node.js结合在一起,使用了Node.js的本地化能力来进行本地逻辑处理,同时使用Chromium的图形化能力来展现图形化界面,把这两者串起来的就是js了。Node果然无比强大。

使用AppJS来写本地程序有以下几个好处:

1、像写网页一样写本地程序。因为AppJS的图形化用的是Chromium,本质上还是使用浏览器来展现,所以写这样的本地程序跟写网页区别不大。
2、你可以使用Chromium提供的很多牛X哄哄的特性:HTML5, CSS3, SVG, WebGL等
3、你可以使用Node提供的与系统有关的特性:文件系统访问、网络访问、进程管理等,你甚至可是让你的程序是个http服务器。
4、跨平台,目前AppJS支持Windows、Linux和Mac。

来看一下AppJS自带的一个例子吧,界面如图:

这样的界面是怎么做出来的呢,还是看看代码好了

<!doctype html>
<html>
<head>
<title>Hello World!</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>

仅仅是一个页面而已,当然还用了点CSS3

html{ 
width: 100%; 
height: 100%; 
overflow: hidden; 
background-image: url(rainbowsky.jpg); 
background-size: cover;
-webkit-box-sizing: border-box; 
}
body{ 
margin: 0; 
height: 100%; 
font-family: 
FixedWidth; 
color: #fff 
}
h1{ 
text-align: center; 
font-weight: 900; 
font-size: 120px; 
line-height: 1; 
text-shadow: 6px 3px 3px rgba(0,0,0,.7), 0 0 30px #aaccff;
position: absolute; 
margin: -1em 0 0 0; 
top: 50%; 
width: 100%; 
-webkit-text-stroke-width: 2px; 
-webkit-text-stroke-color: #000; 
}

逻辑部分,代码就是Node了,不过例子里也没什么复杂逻辑,就是把界面显示出来而已

var app = module.exports = require('appjs');

app.serveFilesFrom(__dirname + '/content');

var window = app.createWindow({
  width  : 640,
  height : 460,
  icons  : __dirname + '/content/icons',
/***************************** defaults ********************************
* url            : 'http://appjs', // serve static file root and routers
* autoResize     : false,          // resizes in response to html content
* showChrome     : true,           // show border and title bar
* resizable      : false,          // control if users can resize window
* disableSecurity: true,           // allow cross origin requests
* opacity        : 1,              // flat percent opacity for window
* alpha          : false,          // per-pixel alpha blended (Win & Mac)
* fullscreen     : false,          // client area covers whole screen
* left           : -1,             // centered by default
* top            : -1,             // centered by default
*************************************************************************/
});

window.on('create', function(){
  console.log("Window Created");
  this.frame.show();
  this.frame.center();
});

window.on('ready', function(){
  console.log("Window Ready");
  this.require = require;
  this.process = process;
  this.module = module;
  this.console.log('process', process);
});

window.on('close', function(){
  console.log("Window Closed");
});

AppJS托管在github上,有兴趣的去围观吧 https://github.com/appjs/appjs

已经把这事搁置很久了,前一段时间主要是毕业的事情比较多,现在终于能有一点时间继续写了。好了,进入正题,本节的重点在于WebSocket。
由于各大浏览器对WebSocket的支持各不相同,所以直接使用浏览器提供的API会比较复杂。Socket.IO对这些API做了封装,并且还使用其他方式对不支持WebSocket的浏览器做了支持,使用Socket.IO会减轻很多工作量。
使用Socket.IO只需要在express里安装Socket.IO模块,同时在页面中加入/socket.io/socket.io.js的引用,服务端包含socket.io包即可。服务端代码如下:

var express = require('express')
  , routes = require('./routes');

var app = module.exports = express.createServer();
var io = require('socket.io').listen(app);
// Configuration

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.set('view options',{layout:false});
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(express.errorHandler());
});

// Routes

app.get('/', routes.index);
var conns = {};
var userNames = {};
var sids = {};
var onLineList = [];
io.sockets.on('connection',function(socket){
	var sid=socket.id;
	conns[sid]=socket;
    socket.on('submitUserName',function(userName, fn) {
        if (userNames[userName]) {
            fn("have");
        } else {
            for (var i = onLineList.length - 1; i >= 0; i--) {
                var tmpSid = userNames[onLineList[i]]["sid"];
                conns[tmpSid].emit('addNewUserToList', userName);
            }
            var userInfo = {};
            userInfo["sid"] = sid;
            userNames[userName] = userInfo;
            sids[sid] = userName;
            onLineList.push(userName);
            fn("not_have");

        }
    });
    socket.on('getOnlineList', function(userName, fn){
        fn(onLineList);
    });
    socket.on('setCompetitor', function(competitor, fn){
        var myName = sids[socket.id];
        for (var i = onLineList.length -1; i >= 0; i--) {
            if (onLineList[i] == myName) {
                onLineList.splice(i, 1);
            }
        }
        for (var i = onLineList.length -1; i >= 0; i--) {
            if (onLineList[i] == competitor) {
                onLineList.splice(i, 1);
            }
        }
        var deletedUser = [myName, competitor];
        for (var i = onLineList.length -1; i >= 0; i--) {
            var tmpSid = userNames[onLineList[i]]["sid"];
            conns[tmpSid].emit('deleteUserFromList', deletedUser);
        }
        if (!userNames[myName]["competitor"] && !userNames[competitor]["competitor"]) {
            userNames[myName]["competitor"] = competitor;
            userNames[competitor]["competitor"] = myName;
            var comSid = userNames[competitor]["sid"];
            conns[comSid].emit('beginChess', 'begin');
            fn("success");
            socket.on('goChess', function(data){
				conns[comSid].emit('comGoChess', data);
            });
            conns[comSid].on('goChess', function(data){
                socket.emit('comGoChess', data);
            });
        } else {
            fn("fail");
        }
    });
});
app.listen(3000, function(){
  console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
});

代码前半部分都是Express框架的设置部分,然后io.sockets.on();部分开始监听来自浏览器端的WebSocket连接。连接建立之后,会将该连接存起来,并在该连接上设置一些可能的监听事件。

socket.on('submitUserName',function(userName, fn){});
//用户提交自己的名字时触发的事件

socket.on('getOnlineList', function(userName, fn){});
//用户获取在线等待配对用户列表时触发的事件

socket.on('setCompetitor', function(competitor, fn){});
//用户选中一个对手之后设置对手的事件

浏览器端在触发事件时会发送一些数据给服务端,比如socket.on(‘submitUserName’,function(userName, fn){})中,userName是浏览器端发送过来的用户名,而fn则是浏览器端定义的一个函数回调函数,当服务端完成某些操作后,会调用fn函数,相当于浏览器端得到了服务端的响应。当然,这里也可以不设置fn,只发送数据。关于socket.io的具体情况可以查看官方文档,地址:http://socket.io/#how-to-use
浏览器端会根据用户的一些操作触发一些服务端事件,部分代码如下:

$(function(){
    $("#chessCanvas").hide();
    $("#PiecesCanvas").hide();
    $("#onlinelist").hide();
    socket = io.connect();
    window.submitUserName = function(){
        var userName = $("#username").val();
        if (userName) {
            socket.emit('submitUserName', userName, function(data){
                if (data == "have") {
                    alert("用户名已经被使用,请重新取一个更酷的名字吧!");
                    $("#username").val("");
                    $("#username").focus();
                } else {
                    socket.emit('getOnlineList', userName, function(list){
                        $.each(list, function(i, item){
                            if (item == userName){
                                return true;
                            }
                            var str = '<div id="' + item + '"><input type="radio" name="onlineuser" value="'
                                + item + '" /><span>' + item + '</span></div>';
                            $("#namelist").append(str);
                        });
                        $("#login").hide();
                        $("#onlinelist").show();
                    });
                }
            });
            socket.on('addNewUserToList', function(data){
                var str = '<div id="' + data + '"><input type="radio" name="onlineuser" value="'
                    + data + '" /><span>' + data + '</span></div>';
                $("#namelist").append(str);
            });
            socket.on('deleteUserFromList', function(data) {
                $.each(data, function(i, item) {
                    $("#namelist > #" + item).remove();
                });
            });
            socket.on('beginChess', function(data) {
                showChess('red', 'wait', socket);
            });
        } else {
            alert("请输入用户名!");
        }

    };
    window.beginChess = function(){
        var competitor = $("input[name='onlineuser']:checked").val();
        if (competitor) {
            socket.emit('setCompetitor', competitor, function(data){
                if (data == "success") {
                    showChess('black', 'idle', socket);
                } else {
                    //未配对成功的情况
                }
            });
        } else {
            alert("请选择一个对手!");
        }
    };
    function showChess(ourColor, status, sock) {
        $("#onlinelist").hide();
        $("#chessCanvas").show();
        $("#PiecesCanvas").show();
        window.chss = chess("chessCanvas", "PiecesCanvas", sock);
        window.chss.ourColor = ourColor;
        window.chss.status = status;
        window.chss.initPieces();
    }

});

当用户输入完用户名之后,点击登录,就会使用socket.emit(‘submitUserName’, userName, function(data){})触发服务端响应。同时,浏览器端也会监听一些服务端事件,例如socket.on(‘beginChess’, function(data) {})就监听了服务端发起的棋局开始事件。这就是WebSocket的强大之处,浏览器可以被动接收服务端发送的消息,换个角度说,这一刻的浏览器就是C/S结构中的服务端,而服务器则是C/S结构中的客户端。当棋局开始后,浏览器使用如下代码监听服务器发送过来的对方走子情况,并在本地执行。

//监听服务端发送过来的对方棋子移动的事件
me.socket.on('comGoChess', function(movement) {
    var lineX = 10 - movement.toLineX;
    var lineY = 11 - movement.toLineY;
    var comPiece = movement.piece;
    me.pieceMoveTo('opposite', comPiece, lineX, lineY);
    me.status = 'idle';
});

当然,在本地走子之后,也会将走子信息发送给服务器,服务器再发送到对方。本子走子信息通过如下代码向服务端发送。

var movement = {};
movement["piece"] = me.activePiece;
movement["toLineX"] = lineX;
movement["toLineY"] = lineY;
me.socket.emit('goChess', movement);

到这里,仅仅实现了配对,同步走子的功能。判断输赢和结束棋局重来棋局等功能还未实现。最后,再放截图一张。

每年这个时候,总会有各种各样的离别故事。回想三年前,我也是刚刚经历了一番离别,然后来到上海。看到微博上各种离别状态,人人上各种散伙饭照片,不免也会被这些伤感的气息感染。毕业了,难免会对学校有或多或少的留恋,毕竟在这里待了三年。其实,与其说是对学校的留恋,倒不如说是对学校里曾经朝夕相处的人的留恋。因为有了你们,我的这三年才那么的多姿多彩。除了想和你们道别,更多的还是想对你们说声谢谢。
404的哥们,谢谢你们。谢谢你们的夜夜相伴,谢谢你们的次次卧谈。虽然每次斗嘴我都斗不过天梅,但是以后连在一起斗嘴的机会也少了。很怀念每次站在天梅后面看电影的场景,你不愧是各种大片喂出来的,对电影的品味也那么独特,口味奇特无人能及。我们寝室的电费每次都是中杰去交,我们寝室的水喝完了也是中杰去换,饮水机的各种毛病你也都能搞定,果然不愧是家里蹲大学饮水系毕业。我觉得每次打电话回家的时刻就是最不公平的时刻,你们两个说啥我都听不懂,但是我打电话说的每一句话你们都能听的懂。以后天南海北,不知何时才能相聚,希望你们两个高富帅别忘记我这个还在魔都混的IT屌丝男。
软工一起入学的兄弟们,谢谢你们,依然记得刚来复旦的时候我们一起住宾馆的日子。我到上海第一个联系的同学是肥兔子,谢谢你安排我到宾馆,到东京之后记得扬我国威。当然还有小苗,谢谢你到公交站台去帮我拎行李,七年的同学,缘分啊。谢谢邱哥、谢谢flyshow、谢谢小明。阿杜,干的不错,争取以后要当个好的家庭煮夫。欢欢也加油,争取早日毕业哈。Tancy一直是高富帅的代表,除了羡慕还是羡慕。
软工的学长学姐学弟学妹,谢谢你们。陈宇,谢谢你送的O1,不过要令你失望了,我到现在为止都没有开发出一款Ophone应用来,你的投资要打水漂了。水哥,谢谢,和你一起出差的日子很快乐,尤其是当你迷路的时候,能给我带来更多快乐。赵哥,谢谢,跟着你做过一段时间的bpel,只是最后我去搞其他的了,感觉很遗憾没能一干到底。伟哥,谢谢,谢谢你每次请我们到你寝室吃东西,我真心觉得你做的饭很好吃,博士里的大厨,大厨中的博士。刚哥,谢谢,从你那我学到了很多东西,以后有空还要多想你请教。董哥,谢谢,能有你这样的博士学长,我感觉很幸运。惠姐,谢谢,感谢你给我们做大餐吃,你那爽朗的笑声我将永远记住。邱诚、潘森、陈宵、宋扬、徐天伦、王永峰,谢谢你们,你们都很牛,从你们那里我也学到了很多。林日昶,加油,网管不好当,但是我觉得你比我牛,你可以的。还有林博和钱博,两位博士要多为实验室发paper,软工实验室的繁荣就看你们的了。实验室所有的同学们,谢谢你们,你们才是我对实验室记忆的主体,谢谢你们陪伴我走过了这么长时间。
小团队的小朋友们,谢谢你们。因为有了你们,我的实验室生活才更加欢乐。丁贝贝,好基友一被子,谢谢去年夏天你收留我到你寝室去吹空调。作为你个认不全颜色的前端,你做的很成功,希望你以后继续加油。Jofanie,谢谢你,接了我那么多工作,你干的很出色,另外,作为一个自动提款机,你做的也很出色。漂亮阿姨,谢谢你,是你在我最缺钱的时候接济了我,作为一个美丽善良的白富美,你做的很好很好。诸姣,谢谢,谢谢你帮我拿过多次快递帮我打印过多次东西还当过我写的程序的小白鼠。旭旭,谢谢,智慧与美貌并存说的估计就是你了。挺妹,谢谢,听说你在毕业聚餐上吻了7个男人,那么为什么你没有吻我?岚姨,谢谢,到了米国要记得帮我要一张taylor的签名照。
还有08SS的小朋友们,谢谢你们,第一次当TA,希望你们能多多包涵。喜之郎和pandalove,你们一直是模范小夫妻,好好读书好好幸福下去。
最后,要谢谢实验室的老师们。谢谢你们这三年来的培养和教诲,毕业之后我也是软工实验室的学生。