安居客的网站架构-转

做个备份。

原文地址:http://erning.net/blog/2012/06/18/the-architect-of-anjuke-inc/

Java RSA加密

	private String encrypt(String encodeString) {
		//空字符串不加密
		if (StringUtil.isEmpty(encodeString)) {
			return "";
		}
		try {
			byte[] modulusBytes = new BASE64Decoder().decodeBuffer(PropertiesHelp.getProperty(
				configPath, "encrypt.modulus"));
			byte[] exponentBytes = new BASE64Decoder().decodeBuffer(PropertiesHelp.getProperty(
				configPath, "encrypt.exponent"));
			BigInteger modulus = new BigInteger(1, modulusBytes);
			BigInteger exponent = new BigInteger(1, exponentBytes);

			RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
			KeyFactory fact = KeyFactory.getInstance("RSA");
			PublicKey pubKey = fact.generatePublic(rsaPubKey);

			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, pubKey);

			byte[] bytes = encodeString.getBytes();
			byte[] encodedByteArray = new byte[] {};
			for (int i = 0; i < bytes.length; i += 102) {
				byte[] subarray = ArrayUtils.subarray(bytes, i, i + 102);
				byte[] doFinal = cipher.doFinal(subarray);
				encodedByteArray = ArrayUtils.addAll(encodedByteArray, doFinal);
			}

			return new BASE64Encoder().encode(encodedByteArray);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return null;
	}

 

项目小结:权限管理平台

今天终于把AC权限管理系统“上线”了

内部项目,取名AC,即access control 一个权限授权及管理服务。

一些好玩的地方:
  1. RBAC模型的实践
  2. 跨域单点登录的实践
  3. 持久化session
一些不足之处:
  1. 使用了不熟练的jeasyui, 累计有1天的工作量在查阅相关文档和处理其问题. 虽然是边开发边学习,但这种方式一定程度上影响整体开发效率
  2. 过多的考虑为了兼容原有平台的权限模型,而没有考虑将来新平台的模型。这点被总监指出。有时候做的太范的确不太好。
  3. 大量的低脑力高体力的代码。估计占项目20%的代码是在写基础的mybatis配置及其接口。如果表设计合理加上一定规则。完全可以反向把表结构转化为基础的代码。更多的注意力集中在业务和高层抽象。抽时间考虑根据公司情况,开发code-to-code工具。
  4. 后期因为修改,造成设计上的出现冗余。处理多对多关联问题上,没能找到太好的解决方案。造成至少3段代码的冗余。12-15处DAO冗余。严重影响以后代码阅读者。
  5. 原权限迁移到新平台时,没有深思熟虑。导致本次权限迁移。部分员工的权限丢失。需要人工补回。
不满意的地方颇多。究其原因:
  1. 管不住自己的好奇心,为了代码而去设计。
  2. 时间不充裕。因为不是核心项目。全部编码和测试只有2周时间。存在较多bug未被发掘。

分享几个百度网盘邀请码

百度最近蛋疼的搞出了“百度网盘” 分享分享。大家一起用用。

http://pan.baidu.com/netdisk/beinvited?invite_code=4a71de09a323e92f473702bd3e908a9f
http://pan.baidu.com/netdisk/beinvited?invite_code=91d92483f8016a39c0ac3681881ce6a3
http://pan.baidu.com/netdisk/beinvited?invite_code=eece5a656f35811e329acd4b92012195
http://pan.baidu.com/netdisk/beinvited?invite_code=0d1b7588022472d479fb185cd3926cd1
http://pan.baidu.com/netdisk/beinvited?invite_code=562e1bbcffe5374290682a9204d2fc66
http://pan.baidu.com/netdisk/beinvited?invite_code=f5e51e22680ba945431d462b3d9bd46d
http://pan.baidu.com/netdisk/beinvited?invite_code=0a8387a03876e7215d49d62a7548eb3b
http://pan.baidu.com/netdisk/beinvited?invite_code=39cd3b2cc1e003b602e5edd22cd1db99
http://pan.baidu.com/netdisk/beinvited?invite_code=a2a6ad4aaa5631b14a6f0075d9707e46
http://pan.baidu.com/netdisk/beinvited?invite_code=429b7611de758873f39d4fc278e7b310

Java WebSocket 开发 Webbit

webbit是基于netty扩展的websocket工具。可以大大简化websocket开发。

项目地址:https://github.com/webbit/webbit

使用说明:https://github.com/webbit/webbit/blob/master/README.md

本文权当翻译,高手直接进上面链接

一些题外话:

  1. websocket和我一开始想象的TCP应用不同。websocket和传统意义上的socket通信不一样。本质上还是HTTP的扩展。
  2. websocket协议目前还没有定稿。目前主要有3个版本的协议在使用。且都是草案。webbit都实现了3个草案。具体参阅维基http://en.wikipedia.org/wiki/WebSocket

快速开始

Maven配置

<dependency>
	<groupId>org.webbitserver</groupId>
	<artifactId>webbit</artifactId>
	<version>0.4.7</version>
</dependency>

 配置端口8080.并配置websocket路径/socket的handler

public class WebSocketServer{

	public static void main(String[] args) {
		WebServer webServer = WebServers.createWebServer(8080)
	    .add(new StaticFileHandler("/socket"));
		webServer.start();
	}

}

 编写/socket handler

public class WebSocketHandler  extends BaseWebSocketHandler{

    private int connectionCount;

    public void onOpen(WebSocketConnection connection) {
        connection.send("Hello! There are " + connectionCount + " other connections active");
        connectionCount++;
    }

    public void onClose(WebSocketConnection connection) {
        connectionCount--;
    }

    public void onMessage(WebSocketConnection connection, String message) {
        connection.send(message.toUpperCase()); // echo back message in upper case
    }

}

到此为止。基本代码已经都好了。感觉更写个servlet一样方便。

下面是客户端代码:

<html>
  <body>

    <!-- Send text to websocket -->
    <input id="userInput" type="text">
    <button onclick="ws.send(document.getElementById('userInput').value)">Send</button>

    <!-- Results -->
    <div id="message"></div>

    <script>
      function showMessage(text) {
        document.getElementById('message').innerHTML = text;
      }

      var ws = new WebSocket('ws://' + document.location.host + '/hellowebsocket');
      showMessage('Connecting...');
      ws.onopen = function() { showMessage('Connected!'); };
      ws.onclose = function() { showMessage('Lost connection'); };
      ws.onmessage = function(msg) { showMessage(msg.data); };
    </script>
  </body>
</html>

到此为止。websocket基本功能都已经实现。

 

 

netty+flash xmlsocket 在线聊天室

这几天公司做了个简单的web im聊天室。麦包包晒包频道 右下角的“包打听”。

采用netty 做socket server + flash client. 通讯采用自定义的JSON文本。

在线IM初步的探索,现阶段做的比较简单。

目前就部署1台服务器。没有考虑多机通信。没有涉及通信队列。

总共代码不到1000行。netty的确强大。

关键代码分享:

无话可说的代码,netty通用设置。

public class ChatRoomNioServer {

	private static final Logger		logger		= Logger.getLogger(ChatRoomNioServer.class);

	private static final Integer	SERVER_PORT	= Integer.parseInt(PropertiesHelp
													.getProperty("chat.server.port"));
	public void start() {
		logger.info("chatroom init...");
		ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
			Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));
		bootstrap.setPipelineFactory(new ChatRoomServerPipelineFactory());

		bootstrap.setOption("child.tcpNoDelay", true);
		bootstrap.setOption("child.keepAlive", true);
		bootstrap.setOption("reuseAddress", true);
		bootstrap.bind(new InetSocketAddress(SERVER_PORT));
		logger.info("chatroom running...");
	}

}

由于采用JSON协议。

上行通道:

  1. DelimiterBasedFrameDecoder 读取缓存时,已\0\r\n 为中止符
  2. StringDecoder 二进制转字符串进行UTF-8解码
  3. StringEncoder 字符串UTF-8编码

下行通道:

  1. MessageHandler 业务逻辑处理
  2. MessageEncoder JSON转字节流打入下行通道
public class ChatRoomServerPipelineFactory implements ChannelPipelineFactory {

	@Override
	public ChannelPipeline getPipeline() throws Exception {
		ChannelPipeline pipeline = Channels.pipeline();
		pipeline
			.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.zeroDelimiter()));
		pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
		pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
		pipeline.addLast("messageHandler", new MessageHandler());
		pipeline.addLast("encoder", new MessageEncoder());
		return pipeline;
	}

}

netty没有提供\0截取。不过重写部分代码即可。

public class Delimiters {

	public static ChannelBuffer[] zeroDelimiter() {
		return new ChannelBuffer[] { ChannelBuffers.wrappedBuffer(new byte[] { '\0' }),
				ChannelBuffers.wrappedBuffer(new byte[] { '\r', '\n' }) };
	}

	private Delimiters() {

	}
}

逻辑处理:

  1. 处理flash 的policy file request
  2. 处理业务逻辑
public class MessageHandler extends SimpleChannelUpstreamHandler {

	private static final Logger	logger			= Logger.getLogger(MessageHandler.class);

	private static final String	POLICY_REQUEST	= "<policy-file-request/>";

	private static final String	POLICY_XML		= "<?xml version=\"1.0\"?><cross-domain-policy><site-control permitted-cross-domain-policies=\"master-only\"/><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>";

	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		super.channelConnected(ctx, e);
		ChatService.initConnection(e.getChannel());
		logger.info("one user connection server, left " + ChatUserService.getOnlineUserCount()
					+ " users");
	}

	@Override
	public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		super.channelConnected(ctx, e);
		ChatService.closedConnection(e.getChannel());
		logger
			.info("one user left server, left " + ChatUserService.getOnlineUserCount() + " users");
	}

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		String msg = (String) e.getMessage();
		if (msg.equalsIgnoreCase(POLICY_REQUEST)) {
			e.getChannel().write(POLICY_XML + "\0");
			e.getChannel().close();
		} else {
			try {
				MessageDispatchHandler messageDispatchHandler = new MessageDispatchHandler(e);
				messageDispatchHandler.setListener(new ChatRoomListenerImpl());
				messageDispatchHandler.dispatch();
			} catch (Exception ex) {
				logger.error("message handler error", ex);
			}
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		logger.warn("Unexpected exception from downstream.", e.getCause());
		e.getChannel().close();
	}
}

具体业务在    MessageDispatchHandler 处理一些业务逻辑即可。

public class MessageDispatchHandler {

	private static final Logger	logger	= Logger.getLogger(MessageDispatchHandler.class);

	private ChatData			chatData;

	private ChatConnection		connection;

	//事件监听器
	private ChatRoomListener	listener;

	public MessageDispatchHandler(ChatConnection connection, Object message) {
		this.chatData = ChatPackageUtil.unpackage(message);
		this.connection = connection;
	}

	public void dispatch() {
		logger.debug("current chatdata :" + chatData);
		if (listener == null) {
			throw new RuntimeException("监听器不能为空!");
		}
		try {
			switch (chatData.getMethod()) {
				case C_CONN:
					listener.connChatRoom(connection, chatData);
					break;
				case C_JOIN:
					listener.loginChatRoom(connection, chatData);
					break;
				case C_PUBLISH_MSG:
					listener.broadcastMsg(connection, chatData);
					break;
				case C_SEND_ADMIN:
					listener.sendAdminMsg(connection, chatData);
					break;
				case C_SEND_NORMAL:
					listener.sendNormalMsg(connection, chatData);
					break;
				case C_GET_USERS:
					listener.getOnlineUserList(connection, chatData);
					break;
				default:
					throw new BusinessException("错误指令:" + chatData.getMethod());
			}
		} catch (BusinessException e) {
			logger.info("error msg:" + e.getMessage());
			ChatService.pushErrorMsg(e.getCode(), e.getMessage(), connection);
		}
	}

	public void setListener(ChatRoomListener listener) {
		this.listener = listener;
	}

}

 

总结下遇到的问题:

  1. flash socket通讯格式。每个协议请求都会\0作为消息结尾。因此netty接收消息也必须以\0为截止符读取消息。
  2. flash 安全策略。flash夸域访问服务器时,会自动发<policy-file-request/> ,服务端收到消息后,必须响应对应策略文件。
    <cross-domain-policy>
      <allow-access-from domain="*" to-ports="80-9000" />
    </cross-domain-policy>
  3. SQL注入。Socket和Http相比。只是通讯的层次发生了改变。应用本身的漏洞仍旧没变。处理SQL的时候需要特别注意。不过用一些成熟的ORM框架可以很好的避免这类问题。
    注:这里使用的是比较低级的JSON字符串作为消息载体,较为普遍和靠谱的做法是采用head+body的方式,但前提需要对格式有严格的约定。JSON灵活性较大,因此采用。

python模拟飞信(模拟webim)

之前写过一点飞信相关的小程序。很久没关心了。发现现在飞信协议都改掉了。强制了验证码。基本上原来的第三方库都不能使用了。

下面就是现在的飞信验证码。看上去破解难度不大。就一根干扰线。有哪位可以破解的网友贡献下代码to me<me[???]fengsage.com> 替换[???]你懂得。
飞信验证码

废话不多说,开始进入正文。

最近发现飞信官网出了webim版的飞信。而且因为年底工作不多。有点无聊。花了几周无聊时间写写代码。给自己发发短信神马的。没多大意义。

项目地址:http://code.google.com/p/fetionim/

值得注意的地方:

  1. 这个程序不是api库。如果验证码能破解则可以作为API库。
  2. 这个程序需要不关机环境。因为要保持和飞信server的心跳连接。一旦关机需要重新登录。
  3. 很多未知bug

目前实现的功能:

  1. 简单的cron表达式。目前只支持 month day hour minute 四个区间. 支持一个*通配符。一般用用够了。举例:[* * * *]表示每分钟执行到。[* * 20 00]表示每天20点执行。[* 20 * *] 表示每月20日的每一分钟执行到。[12 20 20 1]表示12月20日20点1分执行到。
  2. 只支持2种定时任务。即天气预报(只支持嘉兴.因为我住这)和任意短信。

未来要实现的功能:

  1. 天气预报各地支持。这块实现起来很简单。但打算重新设计下任务模块和cron模块。到时候一起改。
  2. 开放一些api。方便其他程序连接。比如宏机短信……
  3. 做个wordpress插件。方便博友直接通过短信联系作者。

 一些废话:

本来不打算发布这个。个人感觉这玩意不能算是什么好的作品。不过以我个性,如果不发布这玩意。估计后面也懒得去维护了。索性发布。代码写的不好。看到的朋友请口水少喷。多提点实在点的意见。

java和ruby都有基于webim的类似实现。

java参见:litefetion
ruby参见:fetion-robot

python的应该算是独一无二的。我没看过这些现有的代码是如何实现的。纯粹自己编写边想。思路和方式和他们应该都存在较多的不同。算是给python初学者提供一些例子吧。

 

总结2011

每年都给自己写个总结。写给将来的自己看。

不太会写文章,分几个方面总结下2011过去的一年。

生活

离开了生活了5年多的杭州。其实说是5年。但里面有3年大学生活,只有2年的工作。虽然是爸妈的意思,但我知道,其实是我自己的软弱。我害怕孤单的一直生活下去。每天上班下班。不知道前面的路在哪里。回嘉兴的这一年来感觉非常的放松,再也没有了那种漂泊孤单感觉,有着家人的关爱,重新滋润了我那干枯的心床。我不后悔!感谢小龚老师、小王老师、老蒋童鞋。曾经的照顾和关怀~

工作

告别了曾经共同奋斗过得同事们,告别了我最尊敬的王总。感谢你2年前给我一个走出大学的学生机会,让我不至于在茫茫的毕业潮中失去方向。在公司的这段日子是我毕生难忘的日子。你教会了我如何学习,如何沟通,如何写文档……感谢震宇,我的好兄弟,好朋友。感谢所有曾经给我过帮助的同事朋友们。
这1年找了2份工作,这在我以前看来是十分反常的。我很反感经常换工作的人,频繁的换工作即是对公司的不负责任也是对自己的懈怠。然而我犯了这个错误。
回嘉兴的第一份工作是在浙江衣派网络科技公司,一家不上进的公司。从刚开始给人一种勃勃生机的兴奋,到最后给人一种日落黄昏的伤感。不想过于主观的评价一家公司,这里感谢强哥,老陆,小朱,老吴,小彭。感谢技术部同事们给我的帮助和照顾。感谢你们包容我的缺点,祝愿新的一年,你们在各自的岗位上取得新的成就。
第二家公司就是现在的嘉兴麦包包网络科技公司。说实话我刚回嘉兴之前从来没听说过。虽然麦包包在电商领域已经较有名气。来麦包包的理由就简单,一是上一家公司的确经营不善,面临裁员的风险,其次就是这里有我非常感兴趣的项目,搭建自己的支付系统。前面这半年,基本快打磨掉我的积极性,整天准点下班,做些没有挑战的事情。人都懒散了。然而这个项目给了我兴奋的感觉。我太渴望有所提升。感谢我的胖师傅银鲨,二师弟银鲈.(呵呵,麦包包的企业文化的确做得不错,有意思的花名文化,虽然山寨但不错)。你们给了我太多太多的帮助。银鲨师傅的“苛刻”灭了偷懒的借口,重新点燃了我的斗志。银鲈童鞋的固执,坚持,避免了我很多bug的出现。然而好事多磨,年前的1个月,银鲨师傅的突然离职,让我感到希望的破灭,没有领路人的感觉真的不好受。随后项目的暂停和转移,让我有了离开麦包包的想法。12月被安排到日本项目组,我有种被打入冷宫的感觉。为了总监给我的希望,坚持了1个月。回头想来这1个月同样精彩。跟新的同事打了交道,虽然已经来了2个多月,但是除了跟银鲨和银鲈,没有和太多的同事沟通,想来有点可悲。感谢滕枣童鞋,很高兴与你的合作。这个月是轻松愉快的。感谢下糯米同学,你是我见过最有才华和实力的应届毕业生了。希望你在新一年里,新的公司取得更多的成就。年初之际总监重启了支付项目,今年这个将是我努力的目标和方向。
感谢下卷丹童鞋、木虱童鞋、戎麦童鞋、巴叶童鞋、灵猫童鞋、角蒿童鞋、银鲈童鞋、章木童鞋、腾枣童鞋、铁阕童鞋、青柠童鞋、戟天童鞋、白鱼童鞋、翠雀童鞋、戎草童鞋、海椒童鞋、蜂虎童鞋、蝎虎童鞋、龙猫童鞋、简草童鞋、洋姜童鞋、白鲩童鞋、蓝天童鞋、白苏童鞋、西柚童鞋、毛尖童鞋…..(排名按我座位的远近,方便我回想。并且在职的同事。保佑我都记全了)

感情

感情方面一直是我的弱项,25年来就没真正谈过一场恋爱。大学里我不想谈,更不敢谈。承担不起责任的我,没资格谈恋爱。所以我不后悔。这方面今年是可悲一年。一是曾经很喜欢的女生结婚了,我从头到尾都没有勇气去追求过,伤悔。二是好不容易相亲到一个比较中意的女生,准备踏踏实实一起走下去。但对方一句从来没有喜欢过我,把我打落了谷底。我不怪她,如果我能多关心关心她,也许结局会不一样。感情方面一直都是我很回避的方面,但2012年我会改正这一点。遇到喜欢的就尽力去追,”放开”的去追。不想以后来了有后悔。

财产

左边是全年的支出图,总体感觉每一笔钱都是有价值的。
理财方面非常的糟糕。今年基本亏损10%左右。还在坚持定存的习惯,投入财产的资金不多。总体亏损在1-2K之前。但和我2011年初制定的理财计划已经相差太多了。2011理财计划。感觉今年的亏损主要原因还是投机心太重,没有真正的肯去学习理财知识。以为读几本理财方面的书,就能理到财。其次今年全球的经济的确不景气。黄金大大起大落,给人当年中国股市崩盘的感觉。

 

2012的展望

工作上,把东西做好,做漂亮。努力工作,早日升值加薪。
生活上,多做点力所能及的事情,少让爸妈操心。周末多学点菜,为将来做准备。
感情上,希望今年能找到心仪的女朋友,早日踏上婚姻的”坟墓”。
理财上,有空,踏踏实实学点经济常识,少点投机心,多点主观意念。
学习上,今年要好好学好英语。踏踏实实。把阅读和听力提升一个台阶。
身体上,有时间好好锻炼下自己,瘦巴巴的,像柴火一样。我是女人也不会喜欢。

2012欢迎我吧~

  1.  2010年总结

wordpress图片搬家脚本

历经几次换域名,以及从GAE-wordpress造成不少历史遗留的图片分布在不同域名或gae目录下。

主要分布
http://oldres.fengsage.com
http://www.fengsage.com
http://fensageblog.appspot.com

原来使用代码实现图片代理访问。但自己越看越不爽。索性写个脚本一次性把原来的图片下载到wordpress本地。并且更新数据库。主要是圣诞节闲的蛋疼。

代码有不少问题:

  1. 不用php,因为懒得看wordpress插件制作。
  2. 因为是python脚本,所以必须是有执行python的权限
  3. 没有生成wordpress media记录。不能后台编辑多媒体

wordpress插件实现:

http://wordpress.org/extend/plugins/velvet-blues-update-urls/  等发现的时候脚本已经写好了。悲哀~~

脚本笔记简单。分享下。

#!/usr/bin/env python
# --*-- encoding:utf-8 --*--
'''
Created on 2011-12-25
@author: fred 
'''

import MySQLdb
import re
import urllib
import time
import os

WP_POST_CONTENT = u"wp_posts"
SIMPLE_IMG_URL = r'' 
NEW_IMG_URL = u''
DOWNLOAD_PATH = u'wp-content/uploads/%s'%(time.strftime('%m/%d', time.localtime(time.time())))

REPLACE_LIST = ['http://oldres.fengsage.com','../media','http://www.fengsage.com']

conn = MySQLdb.connect(host="localhost",
                         user="root",
                         passwd="zhufeng",
                         db="wordpress",
                         charset='utf8')


def get_post_list():
    cursor = conn.cursor()
    cursor.execute("SET NAMES utf8")
    cursor.execute("select * from %s"%WP_POST_CONTENT);
    return cursor.fetchall()

def find_img_urls(str):
    pattern = re.compile(SIMPLE_IMG_URL) 
    match = pattern.findall(str)
    if match:
        return match
    return None

def process_imgs(img_urls,replace):
    result = {}
    for img in img_urls:
        for rp in replace:
            if img.find(rp)>=0:
                print "\tdownload picture"
                new_img_ath = download_img(img)
                result.update({img:new_img_ath})
    return result
                
def download_img(img_url):
    IMG_NAME_REG = r'([^./]*).(png|jpg|gif|jpeg|bmp)'
    m = re.findall(IMG_NAME_REG, img_url)
    if m:
        name,suf = m[0]
        if os.path.isdir(DOWNLOAD_PATH) == False:
            os.makedirs(DOWNLOAD_PATH)
        file_path = u'%s/%s.%s'%(DOWNLOAD_PATH,name,suf)
        downloaded_image = file(file_path.encode('utf-8'), "wb")
        try:
            image_on_web = urllib.urlopen(img_url.encode('utf-8'))
            while True:
                buf = image_on_web.read(65536)
                if len(buf) == 0:
                    break
                downloaded_image.write(buf)
            downloaded_image.close()
            image_on_web.close()
            return file_path
        except:
            print '\tdownload img failture:%s'%img_url

def refresh_content(post_content,result):
    for k in result.keys():
        post_content = post_content.replace(k,'%s/%s'%(NEW_IMG_URL,result[k]))
    return post_content

def update_content(post_id,post_content):
    sql = "update "+WP_POST_CONTENT+" set post_content=%s where ID=%s"
    cursor = conn.cursor()
    cursor.execute("SET NAMES utf8")
    cursor.execute(sql,(post_content,post_id))
    
    
def go():
    post_list = get_post_list()
    for post in post_list:
        post_id = post[0]
        post_content = post[4]
        post_title = post[5]
        if post_id and post_content and post_title:
            print "process article ID:%s"%(post_id)
            img_urls = find_img_urls(post_content)
            if img_urls:
                result = process_imgs(img_urls,REPLACE_LIST)
                if result and len(result)>0:
                    post_content = refresh_content(post_content,result)
                    try:
                        update_content(post_id,post_content)
                    except Exception, e:
                        print 'update database failture:%s'%e
                        

if __name__ == '__main__': 
    print go()

javascript实用模板引擎

John Resig 开发的一个简易模板引擎,配合json非常的实用。

// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
  var cache = {};
 
  this.tmpl = function tmpl(str, data){
    // Figure out if we're getting a template, or if we need to
    // load the template - and be sure to cache the result.
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
     
      // Generate a reusable function that will serve as a template
      // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
       
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
       
        // Convert the template into pure JavaScript
        str
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');");
   
    // Provide some basic currying to the user
    return data ? fn( data ) : fn;
  };
})();

很少的一点代码。但功能非常实用。

准备json和一个模板即可

jquery simple template demo