要实现类似tumblr的自定义风格系统,首先需要一个模板引擎。 该模板引擎需要满足以下需求:
语法足够简单,最好只支持variable、block元素,不要支持循环。由于要对所有用户开放,支持循环的模板存在安全问题,可能遭到攻击,因为用户可以构造一个死循环的模板。
数据不通过${对象.属性}的方式来访问,这可能会造成普通用户理解困难,数据是单层的。比如日志标题就是${Title},而不是${post.title}
这样的话,freemarker、velocity虽然功能强大,却不符合上面两点,就不适合在这里使用了。
不过还是有很多轻量级的模板引擎可供选择的,比如MiniTemplator、Mustache,这两个引擎都有多种语言的实现。
其中MiniTemplator的语法更接近tumblr,稍加修改就能改成和tumblr一样的语法,正好符合我们的要求。MiniTemplatorde的解析部分基本可以直接使用,无需做太大修改。
由于tumblr模板的数据是单层的,因此同一个变量在不同的块级元素下可能需要填充不同的数据,比如${Title}在post的block中就是日志的标题,而在顶级的block中就是博客的标题。
MiniTemplator只提供了最基本的数据填充方法,因此需要自己实现一套处理上下文相关的数据填充框架。对这种数据的填充逻辑必然是和具体的数据定义耦合在一起的:
/** * 数据填充接口 * */ public interface Data { /** * 该block是否需要处理 * @param blockName * @return */ public boolean needProcessBlock(String blockName); /** * 填充该block数据 * @param xpath * @param blockName * @param blockNo * @param miniTemplator */ public void process(String xpath, String blockName, int blockNo, MiniTemplator miniTemplator); /** * 获取该变量的值 * @param key * @return */ public Object getValue(String key); }
process是个递归的过程,调用process("","",0,templator)就完成对模板的数据填充:
public void process(String xpath, String curBlockName, int curBlockNo, MiniTemplator miniTemplator) { ListblockNos = miniTemplator.getSubBlocks(curBlockNo); for (int blockNo : blockNos) { String blockName = miniTemplator.getBlockName(blockNo); if (needProcessBlock(blockName)) { process(xpath, blockName, blockNo, miniTemplator); } } List variables = miniTemplator.getSubVariables(curBlockNo); for (String variable : variables) { miniTemplator.setVariable(variable, String.valueOf(getValue(variable))); } miniTemplator.addBlockByNo(curBlockNo); }
getValue是个类似回溯的过程,根据数据填充逻辑的复杂度,可以实现多个Data接口,比如GlobalData表示全局数据,PostData表示日志数据,PostData会保存对GlobalData的引用,调用PostData的getValue时,如果postData无法处理该值,就调用GlobalData的getValue。
Appearance Options是tumblr模板的一个特色,可以给模板的使用者提供一些选项,模板根据用户的设置会显示成不同的效果,比如就是一个条件值。
虽然minitemplator本身提供条件判断,但是如果把这些条件值在初始化minitemplator的时候传给TemplateSpecification,就会导致模板的结构和用户的数据交织在一起,这样每个用户都会有一个minitemplator的实例,给缓存模板带来麻烦。
因此还是使用和tumblt一样的语法,把这些参数都作为数据,和templator的解析区分开:
如果if:Show Likes的选项会真,就设置数据IfShowLikes为true,IfNotShowLikes为false,{block:IfShowLikes}{/block:IfShowLikes}中内容会被渲染,{block:IfNotShowLikes}{/block:IfNotShowLikes}中内容不会被渲染。
Appearance Options的定义也单独解析:
(?i)<meta\s+name=\"(color|font|if|image|text|customcss):([^"]+)"\s+content="([^"]*)"
只要再对MiniTemplator做一些扩展(主要是添加一些接口),这样就基本上实现tumblr的自定义风格模板系统,只要针对自己的需求添加一些处理各种块数据的逻辑就可以了。
使用这样的自定义风格系统,所有的数据都是在服务器端填充的,因此数据的加载速度是很重要的。
需要用一些方法来加速页面的加载:
服务器端对于相互间没有依赖的数据的获取,应该使用并行的方式来查询数据,这样可以成倍的减少加载时间。
对某些特殊的数据块,服务器不直接填充数据,而是填充js代码,当页面加载完成后,再异步加载这些数据并显示。
对某些用户数据,可是适当的使用本地缓存。
由于tumblr允许用户上传自己的html模板,因此防范恶意代码也是相当重要的。tumblr的cookie是放在www.tumblr.com下的,因此可以防止个人主页窃取cookie。
但是在个人主页加入xss代码似乎还是比较容易的。安全方面不熟悉,就不多说了。