TeamToy完全使用手册
这是一份姗姗来迟的使用手册,因为TeamToy之前一直处于heavy develop的状态,一方面是精力有限,另一方面是变动频繁,请大家谅解。到上周,TeamToy已经拥有了一个比较清晰的框架,于是有了这份文档。
在这里,我们将讲述TeamToy的理念、安装、基本功能的最佳实践、移动客户端、如何对接其他系统、以及插件的使用和开发。我们希望每个架设TeamToy的负责人都抽时间读一读这篇文章,它将帮你自上而下的去理解TeamToy,同时也提到了经常遇到的问题,让你在实际使用中会更加顺利。
理念
愿景
TeamToy是为创新团队量身定制的效率工具。和以人为核心的社交网络不同,它是以事为核心的交流环境。我们期望TeamToy能提升团队的开发效率,而不是相反。这也是为什么默认的页面是我的TODO。
TeamToy致力于让用户体验到新兴技术带来的美好,所以我们会使用一些还没有大规模普及的技术,并只兼容Chrome、Firefox和Safari等现代浏览器。
产品结构
在产品层面,TeamToy分为核心和插件体系(稍后会详细讲)。
核心只专注于TODO和沟通两方面。在TeamToy里边我们为大家准备了非常多的沟通层次,有完全私人的WebIM、一对多的广播、带有事件上下文的TODO评论。这将保证TeamToy的结构清晰,对小团队不会出现大量用不上的功能。
插件体系则为大家在个性化、垂直应用方向留出了足够的空间。不管是项目管理、Bug追踪、客户服务,还是和EverNote、新浪微博、新浪微盘的整合,都可以很方便的通过TeamToy强大的插件体系实现。在接下来的版本中,我们还会为大家提供内置的插件管理页面,方便大家可以在线安装插件。
技术架构
在架构上,TeamToy实现了完全的接口化。Web版的所有功能都是通过OpenAPI来实现的。这意味着你可以开发自己的全功能客户端,同时也意味着它可以很方便的和其他系统进行整合。上周我的同事通过TeamToy的team_members接口查询到了所有同事的最新邮件地址,并为他们发送了测试代金券。科技正是应该在这些烦人的小事上帮助我们。
秉承敏捷开发的理念,TeamToy建立了几乎实时的测试-发布-分发体系。我们一般2~3天就会有一个较大的版本更新,而小版本更可能几个小时一次迭代,这也是为什么TeamToy直接使用Reversion来标识版本。借助TeamToy的一键升级功能,一个新版本从测试、打包、发布到用户升级只需要5分钟。
开源授权
在授权上,TeamToy对非商业用户(只要你不直接用它卖钱,我们都认为是非商业用户)采用GPL2协议,这保证你的数据、你开发的插件、你贡献的代码随时都可以自由的使用,不会出现因为软件厂商消失,一个好不容易建立起来的良好工作氛围无法继续的问题。而这也是我们将TeamToy以开源方式运作的最大原因——只要客户能买到服务器、租到云空间,他就可以Hold住自己的一切。不同于其他的开源项目,为了保持产品的质量和简洁性,TeamToy的核心不接受新功能的push,只接收已有功能的bugfix和优化。我们希望贡献者将新功能以插件的形式贡献,我们将推送给感兴趣的用户进行试用,达到一定的成熟度后进行预装或者融入核心产品。
安装
虽然TeamToy的主要用户中技术类团队居多,但这并不意味着它只适合技术团队,实际上非技术团队更能体会到TeamToy的神奇。通过和新浪云商店的合作,我们为非技术客户提供了一键安装功能,它可以在30秒内架设起一个可以立即使用的TeamToy,并为你提供一个二级域名以便使用。云商店的托管由TeamToy提供代码、新浪提供云环境、用户完全控制数据,“三权分立”,比起由软件商提供的托管服务信息更加安全。云商店空间入门级空间的费用是6元每月,足够二十人左右的团队使用了,对于没有或者不想自己架设服务器的同学来说是个不错的选择。
如果你希望自己架设TeamToy,请准备一个具备下边条件的空间:
- PHP5.3及以上、支持GD、CURL、JSON模块,打开ini中的URL File-Access权限。
- MySQL4及以上,关闭Strict Mode,否则可能出现“字段缺少默认值”的错误。
- 为支持在线升级,请将放置TeamToy的目录设置为可写,从TeamToy目录可以访问到外部网络。
下载TeamToy的安装包,解压后修改数据库配置文件:进入config目录,复制db.config.sample.php,改名为db.config.php。
修改上图中选中的部分。TeamToy不会自动创建数据库,所以请预先创建好。由于在线升级会覆盖db.config.sample.php,请一定不要直接修改它。
我们推荐将TeamToy安装到域名根目录,如果你希望安装到子目录的话,需要修改config目录下的app.config.php文件。如果文件不存在,请复制app.config.sample.php改名为app.config.php。
请在上图选中的site_url行末加上对应的目录。
然后将代码上传到空间根目录,通过浏览器访问初始化脚本。如果你的网站地址为 http://cool.teamtoy.net,那么请访问 http://cool.teamtoy.net/?c=install。
初始化成功后,会提供默认创建的用户名和密码,这个时候就可以正常使用了。
最佳实践
添加成员
admin账号是为了方便安装而创建的,不建议直接使用,一般我们会保留它用于和其他程序的接口调用,当然你也可以在创建好其他管理员后,将其关闭。
使用admin登入TeamToy,点击顶部导航的团队成员图标 ,进入成员列表。
点击右侧”搜索成员”后的“添加”链接,在滑出的表单中添加同事。TeamToy中姓名是不可以修改的,请填写同事的真实姓名。如果有同事同名,可选其中一个使用英文名。
当鼠标移动到成员名片上时,如果你是管理员,会出现灰色的操作提示,可将该成员提升为管理员或者关闭。
名片中的邮箱、电话都是可以点击的,如果你装有skype,点击电话号码就可以直接拨打;在TeamToy移动客户端中,点击电话会呼叫出手机的拨号界面。使用TeamToy,你就拥有了一个实时更新的团队通讯录,再也不用每隔一段时间就让助理一个人一个人的收集最新通讯录了。
目前TeamToy在成员导入时还比较繁琐,我们以后将支持通过手机直接导入联系人,让架设人能方便的将账号发送给大家。
更新头像和个人资料
接下来,请给自己换个头像。点击顶部右侧的用户菜单,可以看到“更换头像”选项。
如果你使用的浏览器支持FileReader功能,你还可以对头像图片进行在线裁剪,再也不用遍地找PhotoShop啦。
在用户菜单中还包含了“更新个人资料”选项,随时保持自己的资料最新是一个好习惯,这样团队中对你有好感的同学才能在需要的时候速度联系上你:P
开启桌面通知
接下来我们要设置一个TeamToy中将用得最多的功能——桌面消息推送。Desktop notification是HTML5的功能,只要你在浏览器中打开了TeamToy,不管你现在在浏览微博、用PhotoShop修图、还是在和PPT里边码字,右上方(或者右下方)都会升起一个小提示窗口,告诉你TeamToy有你要处理的消息。
遗憾的是目前Firefox还不支持这个HTML5特性,所以我们强烈推荐你使用Chrome浏览器,当然Safari也是支持的。
点击顶部导航中的收件箱按钮 ,如果你的浏览器支持,那么会出现桌面通知的设置图标。
点击图标,浏览器会提示,是否允许网站进行桌面消息推送,点是。然后你在电脑上就可以随时看到TeamToy中的新消息了。
TODO
然后是我们的重头戏,TODO。我们建议你将你想到要做的事情,事无巨细全部记录在这里,为了方便你能在会议室或者地铁上添加TODO,我们特意开发了移动版,让你只要能上网就能看到自己的TODO:)
我们总是希望在一个地方统一管理自己要做的事情,所以TeamToy也支持私人的TODO。发布的时候选择私有就可以,现实时标记为红色的就是私有TODO。在添加私有TODO的时候要在发布前就选好,发布成公共TODO再修改的话,TODO的标题还是会进入团队动态中的。
在添加TODO时,也可以直接把TODO添加给同事,点击输入框右下方的“添加给我”链接就可以选择其他人的。选择完成后在不刷新页面的情况下,转让人会一直有效,就是说如果连着给某位同学添加TODO,只要选择一次就OK啦。
TeamToy最大的特点就是TODO本身除了可以转让外,还可以评论,点击TODO的标题就可以在右侧展开TODO详情页,在评论中还可以用@符号对同事进行点名。这样我们在工作遇到问题时,可以很方便的把负责的同事加入到讨论中。看起来很简单的功能,在实际使用中能发挥意想不到的重要作用。
查看完后,可以点击右上方的减号将详情页收起,这样TODO的添加图层才会重新出来。对TODO标题的修改操作隐藏了起来,其实直接点击详情页里的TODO标题就可以了。
TeamToy2中的TODO没有了“进行中”的状态,取而代之的是“星标”功能。我们一般会对当天要完成的和特别重要的TODO添加星标,这样在TODO列表中会在最上方显示。直接点最右边的小书签图标就可以,点灰色的添加星标,点彩色的去掉星标。
我们建议大家把每天要完成的工作加星,然后在下班前进行检查和核对。遇到的问题和用到的资料可以在TODO的评论中发布,这样其他人查看时就能了解到具体的情况。
点击顶部导航中的引号图标 可以进入团队动态页面,这里你可以看到整个团队的工作进展。TeamToy2采用了in-place的设计,不管是在团队动态页面,还是在收件箱,点击列表时都会直接在右侧显示对应的内容。默认页面右侧是广播功能,广播是从原来的微博功能进化而来的,因为我们发现在工作环境中,没有发布个人微博的需求,所以它不再支持静默发布,而是以通知消息为目标;用它发布消息时必须进行点名,如果不点名则认为是要通知所有人,这也是为什么它被称为广播的原因。
如果你有悄悄话和某人聊,请使用页面右下角的WebIM,这东西使用起来比较简单,就不详细介绍了。
以上就是TeamToy的核心功能,可以看出它是围绕事和沟通来组织的。初次使用时可能会有少许的不习惯,一旦熟悉后你将发现TeamToy能极大地提升团队的效率,尤其是当团队人数在10人以上的时候。每天将一堆TODO划掉的感觉会让团队成员非常有成就感,能真实的感受到项目的一步步前进,是一个非常容易上瘾的好习惯。
和那些“管理”软件不同,TeamToy不改变团队的现有流程,只是让现有的流程更便捷。你可以在开会的同时就给对应的同事添加好的TODO,然后在他完成TODO时得到通知;你也可以在中午吃午饭时广播一句“谁跟我吃肉去”,不用担心办公区太大同事没听到;需要传给同事的账号和网址不用再去问人家的QQ,直接用私信搞定。
移动客户端
TeamToy提供了使用PhoneGap技术开发的移动客户端,目前支持Android和iOS两个平台,可以从TeamToy 官方网站下载到。由于目前还处于开发期,我们还没有上架官方市场,iOS的版本需要越狱,且暂不支持推送。Android版本在Nexus4和小米2上运行良好,但在某些2.x的老机器上会有卡顿,我们希望稍后能推出原生版本的Android客户端(有兴趣的同学请赶紧和我联系,[email protected])。
移动版独有的特色功能包括二维码登入、推送通知、联系人导出和短信群发。
二维码登入
为了省却在手机上输入各种字串的痛苦,TeamToy可以扫码登入。登入网页版后,在顶部导航右侧的用户菜单里点击“通过二维码登入”选项,即可获得登入用二维码,这时候用手机客户端直接扫描就能登入,方便又快捷。
需要注意的是,部分在自己电脑上测试TeamToy的同学,请确保你的手机可以通过网络访问到你的电脑,像127.0.0.1这样的网址是不行哦!
推送通知
登入移动版后,点击最右侧的小齿轮进入设置界面,点击接收推送消息即可打开。推送消息会消耗一定量的流量,在Wifi环境下(Wifi下检测频率会比3G下高)为每天2M左右,3G下为1M左右,流量包月的同学可以放心使用,其他同学请密切留意流量,以免产生不必要的费用。
联系人导出和短信群发
在移动版的团队成员页面顶部,我们提供了联系人导出和短信群发功能。联系人导出可以将TeamToy中的联系人以 [T]name 的形式写入手机通讯录,这样当其他同事给你打电话的时候,你就不用回到TeamToy查看是谁了;定期将TeamToy的联系人导出到手机通讯录可以保持联系人数据最新。短信群发功能是为紧急事件准备的,它会调用当前手机的短信发送功能无差别的向TeamToy中填写了手机号的同学发送短信。在50个成员的TeamToy中每群发一次都会花费4~5元(和手工发送一样的,由你的移动运营商收费),请谨慎使用。
和其他系统的对接
前边已经提到过了,TeamToy提供完全的API接口,这让它和其他系统对接变得非常简单。
TeamToy的标准API采用token认证,只需要先用用户名和密码换取token,之后请求其他接口的时候带上这个token就可以了。API接口地址为domain.com/?c=api。
下边这几行实例代码演示了如何从TeamToy中读取团队通讯录。
<?php
$api = 'http://ttsite.sinaapp.com/index.php?c=api';
// 获取Token
$info = json_decode( file_get_contents( $api .‘&a=user_get_token&email=’ . urlencode(‘[email protected]’) . ‘&password=’. urlencode(‘think_different’) ) , true ) ;
$token = $info['data']['token'];
// 取得团队成员名单
print_r( json_decode(file_get_contents($api.‘&a=team_members&token=’.$token), true ) );
是不是很简单?还有更简单的。从版本776开始自带的SimpleToken插件让上边的代码变得只需要一行。
首先请在顶部导航右侧的用户菜单中选择“通过Token访问数据”选项,点击“启用Token”按钮。这样我们就为当前账号创建了一个永久有效的Token。通过OpenApi获取的token是会过期的,而通过这个插件创建的token则永不过期,除非我们手工停用或者重置它。
OK,一旦我们创建完这个SimpleToken,我们就可以直接通过URL来取得json数据。URL的格式和API文档中描述的一致,但为了区别,我们把参数中的token改为了stoken。比如要获取我们demo站上的成员联系人信息,只需要访问这样一个链接:
http://ttsite.sinaapp.com/?c=api&a=team_members&stoken=[stoken]
够方便吧。更棒的是所有TeamToy的功能都可以通过这种方式实现。要和TeamToy进行通信,你只需要会拼接URL。
如果你不会PHP只会Javascript,那也没有关系,SimpleToken还支持JsonP。只要在上边的链接最后加上&jsonp=callbackfunction , 你就可以在其他网站上通过JsonP跨域引用TeamToy的数据了,不过记住stoken不要暴露给别人,要让用户填写并暂存下来。
API的详细文档请点击右侧 APIDOC
扩展机制和插件开发
很多同学表示要给TeamToy开发新功能,但是TeamToy升级的时候会覆盖原来的代码,修改都丢失了。卡卡,这个时候就需要扩展机制出马了。从版本776开始,TeamToy引入了一套强大的扩展机制,它允许你把代码写到一个地方,却可以修改整个TeamToy的各项功能。
说得那么玄乎,其实我们只是借鉴了WordPress采用的hook机制而已,这种机制将要运行的代码挂接到特定的hook上,当hook触发的时候就会运行我们的代码。通过hook的挂接,我们就可以把一个文件中的代码,分散到系统的各个地方,
首先,在TeamToy中hook分两种,带有返回值的叫做Filter,主要对数据做过滤;不带返回值的叫Action,一般处理完数据后直接输出。Filter和Action的底层实现是一样的,只是一种逻辑上的区分。注册Filter用add_filter函数,调用Filter时用apply_filer函数;注册Action时用add_action函数,调用Action时用do_action函数;虽然名字不一样,其实它们背后都是add_hook和apply_hook函数。
我们在TeamToy中提供三大类hook,分别是API hook,Controller hook和UI hook。
API hook允许你对已有API的输入参数和返回数据进行过滤;通过PHP的魔术方法__call,我们也允许你直接在插件中创建新的API。
创建自己的API时,只需要调用add_action( ‘API_*’ , 回调函数名 );就可以了。在回调函数中,可以用apiController::send_result和apiController::send_error两个静态方法来输出API的返回值。以下是一个返回自定义CSS内容分的API接口实例。
<?php
add_action( "API_MYCSS" , "api_mycss");
function api_mycss()
{
$sql = "SELECT `css` FROM `css` WHERE `uid` ='" . intval(uid()) . "' LIMIT 1";
$data = get_var( $sql ) ;
if( db_errno() != 0 ) apiController::send_error( LR_API_DB_ERROR , "DATABASE ERROR" . db_error() );
return apiController::send_result( $data );
}
修改现有的API时,只需要挂上 API_接口名_INPUT_FILTER 和 API_接口名_OUTPUT_FILTER 两个hook就可以了。
下边的代码拦截user_profile接口调用,并向结果中追加了一个def_css字段。
<?php
add_filter('API_USER_PROFILE_OUTPUT_FILTER' ,'user_profile_css' );
function user_profile_css( $data )
{
$uid = intval($data['id']);
$data['def_css'] = get_var("SELECT `css` FROM `css` WHERE `uid` = '" . intval($uid) . "' LIMIT 1");
return $data;
}
TeamToy采用 LazyPHP3开发,Controller是其核心逻辑组件,Controller hook主要用于修改TeamToy现有的功能,包含一个Controller的Input filter,允许你可以对输入参数进行过滤;同时在负责渲染页面的Render函数中包含了一个Render filter。
<?php
//对dashboardContrlloer的upgrade方法进行输入过滤
add_filter(‘CTRL_DASHBOARD_UPGRADE_INPUT_FILTER’ ,‘clean_upgrade_args’ );
function clean_upgrade_args( $data )
{
$_REQUEST["version"] = 1;
}
//对dashboardContrlloer的upgrade方法用于页面渲染的数据进行过滤
add_filter(‘CTRL_DASHBOARD_UPGRADE_RENDER_FILTER’ ,‘clean_upgrade_data’ );
function clean_upgrade_data( $data )
{
$data["verison"] = 1024;
return $data; // 务必return $data
}
UI hook 主要应用于模板中,通过它你可以在合适的地方插入菜单、显示数据。典型的UI hook如:
- UI_HEAD – 用于在页面head区域追加输出
- UI_NAVLIST_LAST – 用于在顶部栏目导航最后添加新菜单
- UI_USERMENU_BOTTOM - 用于在顶部右侧用户菜单尾部追加新菜单
而在一些常用区块前和后都有UI hook,配合ob_start和ob_get_contents 使用,可以对整个区块输出的html做修改。例如在页脚部分,我们有两个hook:
- UI_FOOTER_BEFORE
- UI_FOOTER_AFTER
<?php
// 创建数据表
if( !mysql_query("SHOW COLUMNS FROM `css`",db()) )
{
// table not exists
// create it
run_sql("CREATE TABLE `css` (
`uid` INT NOT NULL ,
`css` TEXT NOT NULL ,
PRIMARY KEY ( `uid` )
) ENGINE = MYISAM ");
}
接下来我们在app.php中添加一些hook,用来实现我们的逻辑。
// 添加顶部导航按钮
add_action( 'UI_NAVLIST_LAST' , 'mycss_nav_list');
?><li <?php if( g(‘c’) == ‘plugin’ && g(‘a’) == ‘mycss’ ): ?>class=”active”<?php endif; ?>><a href=”?c=plugin&a=mycss” title=”自定义CSS” >
function mycss_nav_list()
{
<div><img src=”plugin/css_modifier/cssicon.png”/></div></a>
</li><?php
}
// 添加显示页面的逻辑
add_action( ‘PLUGIN_MYCSS’ , ‘plugin_mycss’);
function plugin_mycss()
{
$data['top'] = $data['top_title'] = ‘自定义样式表’;
$data['css'] = get_var( “SELECT `css` FROM `css` WHERE `uid` = ’” . intval(uid()) . “‘ LIMIT 1″ );
render( $data , ‘web’ , ‘plugin’ , ‘mycss’ );
}
// 添加保存css的函数
add_action( ‘PLUGIN_MYCSS_SAVE’ , ‘plugin_mycss_save’);
function plugin_mycss_save()
{
$css = z(t(v(‘css’)));
$sql = “REPLACE INTO `css` ( `uid` , `css` ) VALUES ( ’” . intval(uid()) . “‘ , ’” . s( $css ) . “‘ )”;
run_sql( $sql );
$location = ‘?c=plugin&a=mycss’;
if( db_errno() != 0 )
return info_page(‘数据保存失败,请稍后重试。<a href=”‘ . $location . ‘”>点击返回</a>’);
else
header(“Location:” . $location);
}
// 在head标签中输出自定义的CSS
add_action( 'UI_HEAD' ,'mycss_ui_head');
function mycss_ui_head()
{
echo '<script type="text/javascript" src="plugin/css_modifier/tabindent.js"></script>'."\r\n";
if( $css = get_var( "SELECT `css` FROM `css` WHERE `uid` = '" . intval(uid()) . "' LIMIT 1" ) )
{
?><style type="text/css"><?=$css?></style><?php
}
}
上边的代码通过UI_HEAD hook在页面头部输出用户自定义的css,实现了插件的功能;但里边出现了两个我们没有提到过的hook – PLUGIN_*。
其实PLUGIN_* 只是一个内置的Controller hook,如果我们add_action(‘PLUGIN_ABC’); 那么TeamToy就会在用户访问?c=plugin&a=abc的时候调用我们注册的函数,在这个被调用的函数中按平时LazyPHP的action去写就好了。
需要注意的是,plugin_mycss 中的render函数采用了名字为plugin的sharp,并传递了插件目录mycss。这是因为LazyPHP默认的模板需要放到view目录,而通过这种方式,我们可以直接把插件的模板放到插件目录下的view子目录中,这样插件所有的代码都能放置到一起了。
完整的插件代码可以点击右边下载 css_modifier
短短几十行代码,全部集中在业务逻辑上,在不需要的时候,只要将config中的plugin配置去掉,所有的功能就消失了。这种将功能模块化的方式,既维持了核心产品的简洁,又保证了长尾功能的覆盖,在TeamToy升级的时候,插件的功能也不会因为代码更新而受影响,我们推荐所有希望扩展TeamToy的同学都按这个方式来操作。
结语
感谢花如此长的时间读完这篇文章,我们希望TeamToy能成为一个大家可以毫无顾虑去使用的、真正改善大家的工作和生活品质的工具。如果你有任何意见和建议,或是参与到其中,都可以给我们发送邮件,[email protected] :)