我们的开源Twitter客户端:TinyTui2
TinyTui是由印客网前技术总监老庄(表伟)发起的一个开源项目,后来由于共同的兴趣爱好,我和另外一个朋友felixding也加入到了这个团队当中。项目虽然不大,但却凝聚了我们不少心血,当然,做为后来者加入的我,贡献有限,主要还是前面两位牛人在开发。目前产品正在完善当中,不过已经基本上满足需求。
一、关于TinyTui2:
TinyTui2是一个基于Twitter API构建的Twitter客户端程序,目标是建立一个高可用性、高智能的代_理平台,为墙内的互联网用户搭建起通往Twitter的桥梁。为了防止信息过载,我们在这个平台上加入了贝叶斯算法(bayesian,wiki:点这里),试图应用这个算法的自我学习机制,来过滤掉一些无意义的噪音,让用户能更有效Touch到目标信息。
TinyTui2为开源程序,基于GPL协议,可二次开发,当然,我们也要求二次开发人员须具备能忍受我们在代码间叽叽歪歪的废话而不疯掉的心理承受能力,除此没其它的要求。祝您好运,感谢祖国,感谢父母,感谢国民党,感谢功夫网,感谢你丫挺的。
项目主页:http://code.google.com/p/tinytui2/
二、我们的信念:
以佛主如来的名义,Fuck G_F_W 一万年。阿门。
三、系统安装:
1、将程序上传到你的WEB目录下,请保证域名指向的根目录是:apps
2、保证:cache目录及其子目录可写。
3、保证:libs/b8-0.4.4/etc/config_storage.php文件可写。
4、运行:http://youdomain.com/install
5、输入你的数据库账号及密码。点击安装。
6、安装成功之后,请删除apps目录下的install文件夹。
四、目录结构:
/----apps - WEB应用程序所在目录
| |
| |----install -- 系统安装文件
| |
| |----src -- 静态资源文件
|
|--conf - 网站配置文件所在目录
|
|--libs - 库文件,包括开源的库,b8和smarty
|
|--view - 模板所在目录
|
|--lang - 语言包所在目录
|
|--cache - 缓存目录(此目录以及子目录必须设置为可写)
| |
| | ------ smarty_cache -- smarty缓存目录,可在配置文件里设置是否开启。
| |
| | ------ templates_c -- smarty模板编译默认目录。
|
|--Doc - 文档目录,包括原始的参考资源,以及原始的PSD文件等。
五、系统配置:
要运行本套系统,需要满足如下条件:
1、墙外的虚拟主机或VPS一台。
2、PHP+MySQL环境,虚拟主机要有可读写权限。
3、不怕被墙的域名一个[可选]。
六、开发人员Twitter及BLOG:
@zhuangbiaowei - http://www.zhuangbiaowei.cn
@felixding - http://dingyu.me/blog/
@handaoliang - http://www.handaoliang.com
七、其它:
这是PHP版本,后续的Python+Tornado+GAE版本正在开发当中。
线上版本:http://www.ijiyi.com,欢迎关注并使用。
基于JSON格式数据的Ajax分页实现
很早就想写一篇关于Ajax分页的文章,只是一直偷懒,拖到现在。有这个念头,是因为我看到现在大部分的WEB分页实现,都是在服务器端做,即便是有一些号称用Ajax实现的,也无非是从服务器端传一组HTML到前端来展示。从纯数据传输的角度来讲,这是相当不靠谱的事情,明明可以纯JSON传输的数据,为什么要附带上这么多冗余的HTML呢?因为既然是和页面相关的事情嘛,自然应该都交给前端去处理。
实现起来其实很简单,先实现分页逻辑:
- //创建全局对象。
- var handaoliang = {};
- //设定命名空间。
- handaoliang.func = {};
- (function(){
- /**
- * 这里传入的数据里必须包含三个Key键:
- * allRecords - 所有记录数。
- * allPageNum - 所有页面数。
- * currentPage - 当前页。
- **/
- handaoliang.func.AjaxPage = function(my_page_data){
- this.record_num = parseInt(my_page_data.allRecords);
- this.all_page_num = parseInt(my_page_data.allPageNum);
- this.cur_page = parseInt(my_page_data.currentPage);
- //在多页的时候,一次显示多少页。
- this.show_page_num = 8;
- /**
- * 画分页内容的时候,要求传入两个参数:
- * container - 显示分页内容的容器。
- * call_back_fun_name - 回调函数。
- **/
- this.draw_page = function(container,call_back_fun_name){
- var loop_num = this.all_page_num > this.show_page_num ? this.show_page_num : this.all_page_num;
- var page_info = "<p style='clear:both; height:20px; margin-bottom: 8px; font-weight: bold;'>一共有"
- + " " + this.record_num + " 条记录,"
- + "分成 " + this.all_page_num + " 页显示,"
- + "当前第 " + this.cur_page + " 页。</p>";
- //只有当全部分页大于1的时候,才显示“第一页”。
- if(this.all_page_num > 1){
- page_info += "<a href='javascript:" + call_back_fun_name + "(1);' class='page_link'>[第一页]</a> ";
- }
- //当当前页处于第一页的时候,即以第一页为参照页,往后画页面。
- if(this.all_page_num > 1 && this.cur_page == 1){
- for(var i = 1;i <= loop_num;i++){
- if(this.cur_page == i){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + i + ");' class='page_link_num' style='font-weight:bold;'>" + i + "</a> ";
- }else{
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + i + ");' class='page_link_num'>" + i + "</a> ";
- }
- }
- page_info += "<a href='javascript:" + call_back_fun_name + "(2);' class='page_link'>[下一页]</a> ";
- }
- //当当前页处于第一页到最后一页的距离中间的时候,此时以当前页为参照,输出前多少页后多少页。
- if(this.cur_page > 1 && this.cur_page < this.all_page_num){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)-1) + ");' class='page_link'>[上一页]</a> ";
- //要先判断是否大于展示的页数。以避免出现负数的页码。
- if(this.cur_page > (parseInt(this.show_page_num)-1)/2){
- for(var i = (parseInt(this.show_page_num)-1)/2; i >= 1 ;i--){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)-i) + ");' class='page_link_num'>"
- + (parseInt(this.cur_page)-i) + "</a> ";
- }
- }else{//当当前页小于展示的页数时,不能多减,否则会出现负数页。
- for(var i = this.cur_page-1; i >= 1 ;i--){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)-i) + ");' class='page_link_num'>"
- + (parseInt(this.cur_page)-i) + "</a> ";
- }
- }
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + this.cur_page + ");' class='page_link_num' style='font-weight:bold;'>"
- + this.cur_page + "</a> ";
- if((parseInt(this.all_page_num)-parseInt(this.cur_page)) > (parseInt(this.show_page_num)-1)/2){
- //此时如果当前页小于展示页的一半,则需要多显示几页。以达到平衡。
- if(this.cur_page < (parseInt(this.show_page_num)-1)/2){
- var added_page = ((parseInt(this.show_page_num)-1)/2)-parseInt(this.cur_page)+1;
- for(var i = 1;i <= ((parseInt(this.show_page_num)-1)/2+added_page);i++){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)+i) + ");' class='page_link_num'>"
- + (parseInt(this.cur_page)+i) + "</a> ";
- }
- }else{
- for(var i = 1;i <= (parseInt(this.show_page_num)-1)/2;i++){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)+i) + ");' class='page_link_num'>"
- + (parseInt(this.cur_page)+i) + "</a> ";
- }
- }
- }else{
- for(var i = 1;i <= parseInt(this.all_page_num)-parseInt(this.cur_page); i++){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)+i) + ");' class='page_link_num'>"
- + (parseInt(this.cur_page)+i) + "</a> ";
- }
- }
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)+1) + ");' class='page_link'>[下一页]</a> ";
- }
- //当当前页处于最后一页,并且包含的页面大于1页时,则由后往前画分页。
- if(this.all_page_num > 1 && this.cur_page == this.all_page_num){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)-1) + ");' class='page_link'>[上一页]</a> ";
- if(this.all_page_num > this.show_page_num){
- for(var i = this.show_page_num; i >= 1;i--){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)-i) + ");' class='page_link_num'>"
- + (parseInt(this.cur_page)-i) + "</a> ";
- }
- }else{
- for(var i = (this.all_page_num-1); i >= 1;i--){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + (parseInt(this.cur_page)-i) + ");' class='page_link_num'>"
- + (parseInt(this.cur_page)-i) + "</a> ";
- }
- }
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + parseInt(this.cur_page) + ");' class='page_link_num' style='font-weight:bold;'>"
- + this.cur_page + "</a> ";
- }
- //只有当全部分页大于1的时候,才显示“最尾页”。
- if(this.all_page_num > 1){
- page_info += "<a href='javascript:" + call_back_fun_name + "(" + this.all_page_num + ");' class='page_link'>[最尾页]</a>";
- }
- container.innerHTML = page_info;
- };
- }
- })();
假设后端输出的分页信息为:
- {
- "page_info": {
- "record_num": 70,
- "current_page": "1",
- "page_num": 3
- }
- }
此时调用分页方法即可以获得分页效果(假设分页容器是:page_box):
- var doSetPageInfo = function(json_data){
- document.getElementById("page_box").innerHTML = '';
- if(typeof json_data.page_info != "undefined"
- && json_data.page_info != null
- && json_data.page_info.page_num != 0
- ){
- var AjaxPageObj = new handaoliang.func.AjaxPage({
- allRecords:json_data.page_info.record_num,
- allPageNum:json_data.page_info.page_num,
- currentPage:json_data.page_info.current_page
- });
- AjaxPageObj.draw_page(document.getElementById("page_box"),call_back_function);
- }
- }
至于后台,则只需根据传入的参数来实现分段取数据就可以了。当用户点击一个页面的时候,传数的页数乃是通过变量page来传递,这样就最大限度上的做到了运算和展示分离,同时也做到了客户端和服务器端数据传输量的最小化。
为Repoze.who开发的SSO插件
在架设ClueMapper的过程当中,由于ClueMapper是基于repoze.who做为登录认证模块的,所以为了适应需求,对repoze.who做了不少的改造,比如之前说过的给repoze.who加上LDAP认证即是改进之一。当然,在后来使用的过当中,并没有完全依赖于LDAP的谁机制,而是使用了SSO认证机制,这是目前很多公司内部普遍使用的认证机制之一。
其实这是第一次尝试写Repoze.who的中间件,在写之前仔细看了一遍repoze.who的代码,发现了不少有意思的东西,比如中间件的机制,等回头有时间,可以专门写一篇repoze.who解读。当然这是题外话,现在要说的,是这个SSO插件。
代码很简单,由于现在我们使用的SSO认证仅仅基于HTTP协议,所以只用urllib2就够了。下面是代码:
- # -*- coding: utf-8 -*-
- """
- SSO Plugins for Repoze.who
- Author: handaoliang <handaoliang@gmail.com>
- Basic usage:
- >>> from repoze.who.plugins import sso
- >>> sso_auth = sso.SSOAuthenticatorPlugin(SSO_URI,SSO_ORG_ID,SSO_SUB_ID,REMOTE_ADDR)
- >>> authenticators = [('sso_auth', sso_auth)]
- >>> pam = middleware.PluggableAuthenticationMiddleware(
- authfilter,
- identifiers,
- authenticators,
- challengers,
- mdproviders,
- classifier,
- challenge_decider,
- )
- """
- from zope.interface import implements
- import hashlib
- import urllib2
- from repoze.who.interfaces import IAuthenticator, IMetadataProvider
- class SSOAuthenticatorPlugin(object):
- implements(IAuthenticator)
- def __init__(self, ssoURI, orgID, subID,ipADDR):
- if ssoURI is None:
- raise ValueError('The SSO URL must be specified')
- self.sso_uri = ssoURI
- self.org_id = orgID
- self.sub_id = subID
- self.ip_address = ipADDR
- def authenticate(self, environ, identity):
- try:
- user_name = identity['login']
- password = identity['password']
- except (KeyError, TypeError, ValueError):
- return None
- auth_password = hashlib.md5(password).hexdigest().upper()
- auth_url = "%s?orgname=%s&sub=%s&user=%s&pwd=%s&ip=%s" \
- % (self.sso_uri,self.org_id,self.sub_id,user_name,auth_password,self.ip_address)
- #connecting....
- try:
- request_handle = urllib2.Request(auth_url)
- contents_handle = urllib2.urlopen(request_handle)
- return_data = contents_handle.read()
- _status = return_data.split("|")
- if int(_status[0]) == 1:
- return user_name
- else:
- return None
- except Exception,e:
- #catched this error:
- return None
- def __repr__(self):
- return '<%s %s>' % (self.__class__.__name__, id(self))
使用方法:
- from repoze.who.plugins import sso
- SSO_URI = "http://192.168.0.1:8000/SsoCertify";
- SSO_ORG_ID = 2
- SSO_SUB_ID = 992
- REMOTE_ADDR = '127.0.0.1'
- sso_auth = sso.SSOAuthenticatorPlugin(SSO_URI,SSO_ORG_ID,SSO_SUB_ID,REMOTE_ADDR)
- authenticators = [('sso_auth', sso_auth)]
- pam = middleware.PluggableAuthenticationMiddleware(
- authfilter,
- identifiers,
- authenticators,
- challengers,
- mdproviders,
- classifier,
- challenge_decider,
- )
本人较懒,没有做安装包,需要安装包的可以联系我。如果有多人需要,将考虑将它更完善。