ThinkPHP 3.2 性能优化,实现高性能API开发

需求分析

目前的业务全站使用ThinkPHP 3.2.3,前台、后台、Cli、Api等。目前的业务API访问量数千万,后端7台PHP 5.6,平均CPU使用率20%。

测试数据

真实业务
php5.6:500 QPS
php7.0:850 QPS

真实业务中减少一次Mysql查询业务或者减少一次Redis读写
php5.6:800 QPS
php7.0:1250 QPS

目前优化的结果:
ThinkPHP可以完整的跑在缓存中;
在不需要mysql查询时,不建立mysql连接;
不读写redis时,不建立redis连接。

以上数据在开发机器使用ab获取,同时也跟其它的框架做了简单对比,性能不低于其它框架。
使用zend debugger profile 可以看到框架层的时间开销占比约24%,相对于yaf这样的C语言框架10%的性能损失,一个包含缓存和ORM的框架已经算比较好的性能了。
再次吐槽一提ThinkPHP框架就喷性能不好的人,任何一个框架拿过来多做几次数据库操作,测试性能都渣得不逼,只测试输出一个HelloWorld并什么卵用。

优化过程

0x00

在项目中早期,开发压力大,没有什么时间进行项目和架构优化。
经过测试,通过添加 mysql 长连接和redis长连接,api稳定性得到非常大提升,业务最慢响应时间从4s优化到0.5s,曲线非常平稳。
PHP-FPM单机200进程,2000Request,7台PHP后端,长连接数稳定在1700左右。

产生的问题
长连接数超过5k时,性能会下降。出现过两次Mysql Server 内存用光的情况。

0x01

经过分析,发现很多API请求,是不需要建立Mysql连接的。调整代码,Mysql的查询逻辑尽量缓存到Redis里,减少对Mysql的压力。
同时对ThinkPHP的代码逻辑进行化,调用 Model 中的方法、属性,不建立Mysql连接,只有在读写db时才建立连接。减少了非常多的资源开销。
经过上述调整,Mysql的连接从1700下降到100以内,query and read QPS从5k下降到50。

优化的ThinkPHP的代码已推送到Github:

https://github.com/vus520/thinkphp/tree/shuhai/db_link_lazzy

后续是对ThinkPHP中Mysql主从、读写分离进行深度测试,增加Mysql的读能力。

0x03

当业务都严重依赖redis时,Redis的QPS一度飙升到7k,内存占用6G左右。
为了缓解redis的读压力,生产中使用了4台Redis Standalone做了1主3从架构。
并给ThinkPHP添加Redis读写分离的支持,减少Redis的压力。

https://github.com/vus520/thinkphp/blob/shuhai/db_link_lazzy/ThinkPHP/Library/Think/Cache/Driver/Redisd.class.php

目前存在的问题
Redis的高可用运维,本身也比较复杂,遇上网络抖动等原因,Redis会出现同步失败和延迟问题。
特别是在云服务器架构的环境中,网络瓶颈和延迟问题对分布式应用有非常大的影响。
很可惜,我们目前使用的青云,目前尚不能实现Redis超高可用,也不能实现无缝扩容,私网内的网络传输性能、延迟都有很大优化空间。

后续的优化计划
对redis业务进行清理,减少不必要的请求;
压缩内容;
key:value => hash;
一主多从,每个php后端部署一个redis从,优先读本机,减少网络延迟;

0x04

API项目中,禁用ThinkPHP的Session、路由、视图、行为等,进行精简加速。
经测试,性能有30%的提升。

https://github.com/vus520/thinkphp/tree/shuhai/tiny

  • 1,去掉路由
  • 2,去掉URL调度
  • 3,去掉行为、Hook
  • 4,去掉视图
  • 5,去掉控制器的反射、空操作
  • 6,去掉Session,可实现无状态的Api

0x05

在PHP7中进行深度测试,升级到PHP7,ThinkPHP 3.2的性能会有50+%的提升

ThinkPHP下MongoDB的操作方法

https://github.com/liu21st/thinkphp/commits/master/ThinkPHP/Library/Think/Db/Driver/Mongo.class.php?author=vus520
向tp添加了一些mongodb操作方法,demo如下

Model

<?php
namespace Home\Model;
use Think\Model\MongoModel;
class UserActlogModel extends MongoModel
{
	
}

Controller

<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller
{
	function index()
	{
		echo __CLASS__ .'\\'. __FUNCTION__;
	}

	function hello()
	{
		echo "Hello world";
	}

    public function mongo()
    {
    	$model = D("UserActLog");

    	//distinct查询语法,支持where、cache操作链
    	$result = $model->cache(false)->where( array('device.channelid'=>"10017") )->distinct('device.imei', array("day"=>"20140706"));
    	print_r(count($result));
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//command查询语法
    	$data = $model->command(array('buildInfo'=>1));
    	//print_r($data);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	$data = $model->status();
    	//print_r($data);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//MapReduce查询语法
    	$key    = array('device.channelid'=>1);
    	$init   = array('num'=>0);
    	$reduce = "function(obj, prev){prev.num++;}";
    	$option = array(
    			'table' => 'user_act_log',
    			'condition'=>array("day"=>"20140701"),
    	);
    	$result = $model->cache(true)->where( array("day"=>"20140708") )->group($key, $init, $reduce);
    	print_r(count($result));
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//find查询语法,支持where、order、cache、field操作链
    	//field方法支持field("act,group"),field(array("act"=>true, "_id"=>false)),field(array("act", "group"))
    	$result = $model->where(array("day"=>"20140701"))->field(array("act"=>true, "group"=>true, "_id"=>false))->order('_id asc')->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

		//子查询OR逻辑
		$map['device.channel'] = "obx_03";
		$map['_logic'] = 'and';
		$map['_complex'] = array("act"=>"down", "group"=>"download", '_logic'=>"or");
		$result = $model->field(array("act"=>true, "group"=>true, "_id"=>false))->where($map)->limit(2)->select();
		print_r($result);
		print_r($model->_sql());
		echo PHP_EOL;

		//子查询OR逻辑
		$map['_complex'] = array('_logic'=>"or", array("act"=>"down"), array("group"=>"download"));
		$result = $model->where($map)->find();
		print_r($result);
		print_r($model->_sql());
		echo PHP_EOL;

		//子查询OR逻辑
    	$map = array();
    	$map['_complex'] = array('_logic'=>"or", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>false))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//子查询 AND 逻辑
    	$map = array();
    	$map['_complex'] = array('_logic'=>"and", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>true))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//子查询 nor 逻辑
    	$map = array();
    	$map['_complex'] = array('_logic'=>"nor", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>true))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//$not查询
    	$map = array();
    	$map['day'] = '20140710';
    	$map['act'] = array('$exists'=>true, '$not'=>array('$in'=>array("click", "listview")) );
    	$map['_complex'] = array('_logic'=>"nor", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>true))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;

    	//$not查询
    	$map = array();
    	$map['day'] = '20140710';
    	$map['_logic'] = 'or';
    	$map['act'] = array('$exists'=>true, '$not'=>array('$in'=>array("click", "listview")) );
    	$map['_complex'] = array('_logic'=>"nor", array("act"=>"down"), array("group"=>"download"));
    	$result = $model->field(array("act"=>true, "group"=>true, "_id"=>true))->where($map)->find();
    	print_r($result);
    	print_r($model->_sql());
    	echo PHP_EOL;
    }
}

ThinkPHP3.2 使用repl模式

1,新建一个Controller,如ReplController.class.php

<?php
namespace Home\Controller;
use Think\Controller;
class ReplController extends Controller
{
	function index()
	{
		$Boris = new \Behavior\BorisBehavior();
		$Boris ->run();
	}

2,使用命令行进入repl模式

php index.php Repl

使用效果如下

➜  thinkphp git:(master) ✗ php index.php
REPL MODE FOR THINKPHP 
THINKPHP_VERSION: 3.2.3beta, PHP_VERSION: 5.5.14, BORIS_VERSION: 1.0.8
[1] >>> $model = D("UserActLog");
// object(Home\Model\UserActlogModel)(
// 
// )
[2] >>> $model->count();
// 1322112
[3] >>> $model->_sql();
// 'kingsgame.user_act_log.count()'
[4] >>> A("Index")->hello();
Hello world// NULL
[5] >>> 

ThinkPHP的多应用模式

ThinkPHP
Source
	-Module
		-Admin
		-User
		-Vote
			-Class
			-Function
			-Model
			-Lang
			-Tpl
	-Lib
		-Class
		-Function
		-Model
		-Lang
	-Tpl
Config
	-Dev
	-Production
		-Database
		-Cache
Data
	-Cache
	-Queue
Public
	-Upload
	-Html
Static
	-Js
	-Css

ThinkPHP多应用模式,可以将独立应用放到Module下,多个应用零耦合,通过主模块的控制和调用来实现,但也可以自由跨模块调用。该模式有望在今年下半年实现。

ThinkPHP的项目位于二级目录,Nignx下的伪静态设置方法

server{
	listen *:80;
	
	server_name www.baidu.com;
	index index.html index.php index.htm;
	root /wwwroot/www.baidu.com;

	error_log /var/log/nginx/error_www.baidu.com.log;	
	
	location ~.*\.(php|php5)?$ {
		fastcgi_pass 127.0.0.1:9000;
		fastcgi_index index.php;
		include fastcgi_params;
	}

	location /www.4wei.cn/ {
		if (!-e $request_filename){
			rewrite  ^/www.4wei.cn/(.*)$  /www.4wei.cn/index.php?s=$1  last;
		}
	}
}

路径为/wwwroot/www.baidu.com/www.4wei.cn/

ThinkPHP 2.x升级到3.0

ThinkPHP将于2012年1月15日发布3.0版本,由于本次的版本新增了不少特性,在配置、模型、控制器方面新增不少功能,但也同时做了一些调整,修改或者删除了一些用法。

鉴于上述变化,本次发布的版本将会是一个跨度比较大的里程碑版本,官方不建议用户从2.0直接升级3.0。考虑到旧用户的升级需求,官方会同时发布2.X分支的后续版本2.2版,以便用户进行升级。

以下文章便是建立在有部分用户分有将2.X 的项目升级到3.0的需求,以下记录了本人的升级过程,仅供参考。

在升级之前,请大家阅读后文的附一和附二文章。
以下记录本人升级的重要步骤

1,通过SVN下载Tp最新版,并将ThinkPHP目录放到旧版本TP的同级目录下,TP3.0正式发布以后,大家可以到官方网站下载。如果是从SVN下载的同学,请将Trunk下的Extend目录放到THINKPHP框架目录下。

2,移走旧项目的Runtime、Conf等Tp会自动生成的目录,以便项目重新编译,我将Conf.php做了备份,各位要注意,后面会用到

3,重建入口文件,TP3.0中的入口文件做了比较大的修改,修改入口文件 确保你的THINK_PATH APP_PATH 以/结束,去掉入口文件中的App::run()代码,建议在入口文件中添加 define('APP_DEBUG',TRUE); 开启调试模式,2.0版本中在配置文件中定义Debug的方法已经被废弃,修改后入口文件如下:

<?php
//定义项目名称和路径
define('APP_NAME', 'User/');
define('APP_PATH', './User/');

//开启Debug模式
define('APP_DEBUG',        TRUE);

// 定义ThinkPHP框架路径
define('THINK_PATH', './Core/');

// 加载框架入口文件
require(THINK_PATH."/ThinkPHP.php");
?>

4,访问入口文件,重新生成Conf、Runtime目录

5,打开新的Conf.php,将旧版本的配置文件,按下面附一的的变化,进行修改,完成配置文件的修改,各位要注意几项已经修改的配置方法,要一一修改正确,注意DEFAULT_THEME参数

6,如果你启用了分组模式,则需要对模板目录结构进行调整。2.0使用THEME/Gorup/Action的目录结构,3.0修改成了Gorup/THEME/Action结构。需要将以前的分组模板目录整体上移一个目录,并将下面的模板文件夹,放到指定的DEFAULT_THEME目录。

如2.0:default/Admin/Index的目录要修改成Admin/default/Index

7,对照下文,对模板、模型、控制器进行修改,比较常见的修改内容有:

  1. FindAll别名废弃:M()->findAll()修改成M()->select()
  2. setField getField setInc setDec废弃,需要重写查询条件
  3. 路由和快速调用方法的改进,如A("Amin.Login")修改成A("Amin/Login"),路由的规则请见下文
  4. 修改模板中已经废弃的标签

8,由于TP3.0在核心精简、性能优化方面的改进,以前一些自动加载的扩展类,现在已经无法自动加载了,同时,还有些常用类,被移到了扩展类中,如SESSION和Cookie类。如果项目中提供函数和Class找不到,则需要在错误出现的地方,Import一下。

到目前止,升级差不多就完了。

正式升级的时候,还需要进行一段时间的测试,各位可以将Debug打开,看一下程序运行日志,同时关注一下PHP的错误信息,以便定位错误,进行修改。

附一:
[ 升级须知 ]
请注意:3.0版本要求PHP的版本5.2.0以上 如果低于该版本 不建议使用或升级
首先下载3.0最新核心版本
删除项目的Runtime目录
备份现有的ThinkPHP系统目录后删除 ThinkPHP目录
把3.0的ThinkPHP目录放入原来的项目对应位置
然后按照下面的升级步骤进行检查:

1、入口文件
修改入口文件 确保你的THINK_PATH APP_PATH 以/结束
去掉入口文件中的App::run()代码
建议在入口文件中添加 define('APP_DEBUG',TRUE); 开启调试模式
如果定义则删除入口文件中的APP_CACHE_NAME、NO_CACHE_RUNTIME、RUNTIME_ALLINONE、STRIP_RUNTIME_SPACE常量定义代码
开启调试模式后就已经具有了NO_CACHE_RUNTIME的效果。
注: 新版TP已经取消了对配置文件修改时间的判断。 在没有开启调试的情况下,如果修改了配置文件,需要手动删除runtime核心缓存。

2、配置文件
[调整] 当前模板主题名配置TEMPLATE_NAME更改为THEME_NAME
[调整] 当前扩展名称的定义THINK_MODE 更改为MODE_NAME
[调整] APP_CONFIG_LIST配置参数改为字符串方式,用逗号分隔
[调整] LOG_RECORD_LEVEL 配置参数改为字符串方式,用逗号分隔
[调整] 数据库端口设置DB_PORT默认为空
[调整] TMPL_ACTION_ERROR和TMPL_ACTION_SUCCESS默认采用内置的错误提示模板
[调整] TMPL_STRIP_SPACE 默认为true
[调整] APP_AUTOLOAD_PATH参数设置最后不需要加. 例如 ORG.Util 原先为ORG.Util.
[调整] DEFAULT_THEME 参数默认为空,升级原有项目需要设置为default 如果启用了分组的话,模板主题目录需要移动到分组目录下面
[删除] 删除原调试模式配置参数APP_DEBUG
[删除] 删除 APP_GROUP_DEPR 参数
[删除] 删除APP_CONFIG_LIST 参数
[删除] 删除APP_PLUGIN_ON参数
[删除] 删除APP_AUTOLOAD_REG参数
[删除] 删除APP_DOMAIN_DEPLOY参数 只需要设置APP_NAME 为空即可
[删除] 删除URL_PATHINFO_MODEL参数 只保留原来的智能模式
[删除] 惯例配置中去掉 VAR_PAGE定义
[删除] 取消模块配置文件
[删除] 取消taglibs.php(标签库定义) 标签库会自动加载
[删除] 取消modules.php(扩展模块), actions.php(扩展操作)

控制器
[删除] 废除操作链功能
[删除] 去掉Action类的相关trace方法
[改进] A方法格式调整为: [项目://][分组/]模块
[改进] R方法格式为:[项目://][分组/]模块/操作

路由定义规范更改如下:
1、正则路由
'路由正则'=>'[分组/模块/操作]?参数1=值1&参数2=值2...'
'路由正则'=>array('[分组/模块/操作]','参数1=值1&参数2=值2...')
'路由正则'=>'外部地址' '路由正则'=>array('外部地址','重定向代码')
参数值和外部地址中可以用动态变量 采用 :1 :2 的方式
2、规则路由
'路由规则'=>'[分组/模块/操作]?额外参数1=值1&额外参数2=值2...'
'路由规则'=>array('[分组/模块/操作]','额外参数1=值1&额外参数2=值2...')
'路由规则'=>'外部地址' '路由规则'=>array('外部地址','重定向代码')
路由规则中 :开头 表示动态变量 外部地址中可以用动态变量 采用 :1 :2 的方式
路由规则加上变量的数字约束定义,例如: 'news/:id|d'=>'News/read'
规则路由可以支持 全动态和动静结合定义,
例如 ':user/blog/:id'=>array('Home/Blog/user')

模型
[删除] 废除model类的findall 如需要用select替代或者在公共模型类里面添加findall定义
[删除] 删除高级模型类里面的切换数据库相关方法 2.1版本以上推荐直接使用模型类的db方法切换
[删除] 去掉setField getField setInc setDec方法的condition条件参数 用连贯操作where替代
[删除] 删除了tableSuffix属性 可以用trueTableName属性替代
[改进] D方法格式为:[项目://][分组/]模型

模板
[调整] 当前模板文件名配置 更名为 TEMPLATE_NAME
[调整] 默认模板主题设置为空 原来为default
[调整] Think模板引擎的load方法更名为fetch
[删除] 去掉Cx标签库的iterate方法定义
[删除] 取消模板快捷变量输出 统一使用标准变量输出方式
[删除] 废弃模板引擎的普通标签方式的include和load标签 以下用法不再支持 {include:public/header.html} {load:/public/css/common.css} 替代用法 因为XML标签方式的include和load用法更强大 ,包括可以传人模板变量名等,XML方式的load标签还有很多的别名用法,完全可以取代普通标签加载。 目前,普通标签仅仅用于输出变量和一些快捷输出。
[改进] 去除Cx标签库的layout标签 增加新的layout标签用法
[改进] 模板输出规则调整为 [模板主题:][模块:][操作] 不支持 跨项目和跨分组调用 该用法可用于display方法和include标签

其他
[调整] 更改模块扩展和操作扩展方式 采用hack方式 分别定义__hack_module和__hack_action

附二:
ThinkPHP 3.0 功能特性和更新说明

新版特性概述:
全新的CBD(核心+行为+驱动)架构模式,打造DIY框架和类AOP编程体验;
全新改进的项目编译机制,更快更方便,并且支持编译文件直接作为入口载入,并且支持常量外部载入,利于产品发布;
调试模式更易用,可以设置不同的项目状态 并自动加载对应不同的项目配置文件,并且详细记录行为执行日志;
路由的增强更加灵活 支持新的规则路由,改进正则路由,并改进定义方式;
内部导入机制和自动加载机制优化 全面提升import A D R等方法的性能;
URL模式简化,兼容URL地址优先判断;
查询语言进一步增强,query和execute方法增强 支持连贯操作和SQL解析;
模板引擎性能提升,PHP语法检测,include用法改进 支持变量传输;
全新的布局模板实现,支持布局嵌套;
实现了扩展和驱动的完全分离,并且驱动集中管理;
AJAX返回数据支持扩展,提供更好的AJAX支持;
更强大和易用的模式扩展;
U函数重新设计;
去除Think基类设计,Think重新设计为入口类和静态类;
增加MongoDb支持;
增加REST模式,提供RESTFul支持;
增加SAE模式扩展提供新浪SAE部署和本地调试支持;
增加自动验证规则;
动态配置文件和函数文件支持;

更新说明
[入口文件和编译]
[新增] 调试模式增加不同的应用状态 自动加载不同的项目配置文件
[新增] 入口文件增加RUNTIME_FILE常量定义
[调整] THINK_PATH 和APP_PATH 常量定义最后必须是 / 结束
[调整] 调试模式设置改为在入口文件中使用APP_DEBUG常量
[调整] 默认调试模式配置调整 默认情况下不开启运行信息和页面Trace信息
[改进] 部署模式和原ALLINONE模式合并 最终只生成一个项目编译缓存文件
[改进] runtime自动生成和目录检测优化
[改进] 核心编译生成文件支持常量分离到单独的文件外部加载
[改进] CORE_PATH TMPL_PATH HTML_PATH EXTEND_PATH MODE_PATH VENDOR_PATH常量可以在入口重新定义
[改进] 部署编译生成新的入口文件 可以用于替换入口
[改进] 支持 自定义编译缓存文件名 可以在入口文件增加常量 RUNTIME_FILE 默认位置不变
[改进] 调试模式切换删除编译缓存 便于调试后直接切换到部署模式
[删除] 废除入口文件中的APP_CACHE_NAME、NO_CACHE_RUNTIME、RUNTIME_ALLINONE、STRIP_RUNTIME_SPACE常量定义
[删除] 去掉入口文件中的App::run() 代码

[配置]
新版的惯例配置中去掉了行为才需要使用的配置项
部分惯例配置参数移动到行为扩展中
所以我们建议
[新增] 增加APP_STATUS 参数 用于调试模式下面设置不同的应用状态 自动加载不同的项目配置文件
[新增] 增加TMPL_LAYOUT_ITEM 参数,用于配置布局内容替换标识 默认是 {__CONTENT__}
[新增] 增加LOAD_EXT_FILE和LOAD_EXT_CONFIG参数,用于动态加载外部文件和配置文件
[新增] 增加APP_TAGS_ON参数 用于设置是否开启系统行为扩展,默认开启
[新增] 新增DB_SQL_BUILD_CACHE和DB_SQL_BUILD_LENGTH 参数用于sql缓存
[新增] 增加DB_MASTER_NUM参数用于设置主(写)服务器数量
[新增] 增加MEMCACHE_HOST和MEMCACHE_PORT配置
[新增] 增加LANG_LIST参数,用于设置允许切换的语言列表,用逗号分隔
[新增] 增加TMPL_DENY_PHP参数,用于设置模板是否禁用PHP代码
[新增] 增加DEFAULT_FILTER参数,用于设置默认的参数过滤方法
[新增] 增加VAR_SESSION_ID参数,用户可以指定session_id
[新增] 增加SHOW_LOAD_FILE 参数,用于控制是否显示加载文件数
[新增] 增加TOKEN_RESET参数,用于设置令牌错误后是否重置 默认为true
[新增] 惯例配置添加 APP_SUB_DOMAIN_RULES和APP_SUB_DOMAIN_DENY 默认设置
[调整] 当前模板主题名配置TEMPLATE_NAME更改为THEME_NAME
[调整] 当前扩展名称的定义THINK_MODE 更改为MODE_NAME
[调整] APP_CONFIG_LIST配置参数改为字符串方式,用逗号分隔
[调整] LOG_RECORD_LEVEL 配置参数改为字符串方式,用逗号分隔
[调整] 数据库端口设置DB_PORT默认为空
[调整] TMPL_ACTION_ERROR和TMPL_ACTION_SUCCESS默认采用内置的错误提示模板
[调整] TMPL_STRIP_SPACE 默认为true
[调整] APP_AUTOLOAD_PATH参数设置最后不需要加. 例如 ORG.Util 原先为ORG.Util.
[删除] 删除原调试模式配置参数APP_DEBUG 改为常量定义
[删除] 删除APP_GROUP_DEPR 参数
[删除] 删除APP_CONFIG_LIST 参数
[删除] 删除APP_PLUGIN_ON参数
[删除] 删除APP_AUTOLOAD_REG参数
[删除] 删除APP_DOMAIN_DEPLOY参数 只需要设置APP_NAME 为空即可
[删除] 删除URL_PATHINFO_MODEL参数 只保留原来的智能模式
[删除] 惯例配置中去掉 VAR_PAGE定义
[删除] 取消模块配置文件
[删除] 取消taglibs.php(标签库定义) 标签库会自动加载
[删除] 取消modules.php(扩展模块), actions.php(扩展操作)

[控制器]
[新增] 增加_get _post _put _request _session _cookie _server _globals方法 并支持参数过滤和默认值,例如: $this->_post('name','h'); 等效于 h($_POST['name']); $this->_get('id','intval',0); 等效于 intval($_GET['id']) 如果没有传入过滤方法 则采用惯例配置默认配置的方法 由DEFAULT_FILTER设置(默认为htmlspecialchars) 因此 $this->post('name'); 等效于 htmlspecialchars($_POST['name']);
[新增] 增加 __hack_module 方法支持
[新增] 增加规则路由支持
[新增] 增加全新的静态缓存机制,无需定义任何静态缓存规则
[新增] 增加 $_GET[1] $_GET[2]用法支持
[修正] 修正Action类的display方法 配置TMPL_CONTENT_TYPE参数无效的bug
[修正] 修正分组模块不存在 自动加载上层模块的bug
[改进] 完善正则路由支持
[改进] U函数支持路由定义 例如 U('/cate/3') 以/开头的地址定义表示是路由 不再解析为模块和操作
[改进] 模块和操作不存在 发送404状态信息
[改进] 改进兼容模式的判断 优先判断兼容模式URL
[改进] 分组目录的Action类 也支持自动加载 并且优先
[改进] A方法格式调整为: [项目://][分组/]模块
[改进] R方法格式为:[项目://][分组/]模块/操作
[改进] 增加一个P_ACTION_NAME 常量定义 用于获取实际的操作名称(区分大小写) 便于空操作使用
[改进] 改进U函数用法 格式统一为:U('[分组/模块/操作][?参数]','参数','伪静态后缀','是否跳转','显示域名')
[改进] 扩展ajax返回数据, 可以在Action文件中定义ajaxAssign方法来扩展ajax返回数据。
[删除] 废除操作链功能
[删除] 去掉Action类的相关trace方法

[模型]
[新增] 添加自动验证的批量验证功能 需要在模型类里面设置patchValidate属性为true 默认为false,批处理验证采用getError() 方法获取后的错误信息是一个数组 需要自行处理。
[新增] Model增加check方法 用于个别需要的情况手动验证数据,支持部分自动验证的规则 用法 check('验证数据','验证规则','验证类型') 验证类型支持 in between equal length regex expire ip_allow ip_deny,默认为regex 结果返回布尔值 $model->check($value,'email'); $model->check($value,'1,2,3','in');
[新增] 自动验证规则增加between 用于判断提交数据是否在一个范围之内 例如 array('num','10,100','必须在10到100之间',0,’between’) 完善in规则,支持逗号分隔的字符串 array('num','1,2,5','只能选择1,2,5',0,’in’) 和下面的定义等效 array('num',array(1,2,5),'只能选择1,2,5',0,’in’)
[新增] Model增加子查询支持,使用子查询的时候 select方法的参数必须为false,例如: // 首先构造子查询SQL $subQuery = $model->field('id,name')->table('tablename')->group('field')->where($where)->order('status')->select(false); // 进行查询 $model->table($subQuery.' a')->where()->order()->select() 构造的子查询SQL可用于TP的连贯操作方法,例如table where等
[新增] 视图模型的视图定义添加实际表名定义支持 使用 '_table'=>'test_db.test_table'
[新增] 自动验证添加操作有效期验证 支持时间戳和日期格式定义 定义格式 array('任意字段','2011-10-1,2011-12-31','已经过了投票时间',self::MUST_VALIDATE,'expire',self::MODEL_INSERT)
[新增] 字段验证方式 增加长度验证,格式定义如下: 区间格式定义 array('username','3,6','用户名长度必须大于等于3小于等于6',self::MUST_VALIDATE,'length',self::MODEL_INSERT) 指定长度定义 array('mobile','11','手机号码长度必须11位',self::MUST_VALIDATE,'length',self::MODEL_INSERT)
[新增] 增加查询缓存支持
[新增] 增加联合查询union支持
[新增] 查询条件增强 支持 $map['a&b'] $map['a|b']
[新增] 增加了getFieldBy*** 查询
[新增] 增加了_sql方法 替代 getlastsql方法
[新增] Model类的field方法支持NOT机制 例如 field('id,name') 表示查询id,name字段 field('id,name',true); 表示查询除id,name之外的字段
[新增] Model增加parseFieldsMap方法 用于处理字段映射的数据写入和读取 create方法已经内置对字段映射的数据写入支持 字段映射的读取需要手动处理 在find方法之后调用,例如: $data = $Model->where()->find(); parseFieldsMap($data);
[新增] query和execute增加第二个参数 表示是否需要解析SQL 默认为false 表示直接执行sql 如果设为true 则会解析sql中特殊字符串 (需要配合连贯操作)例如 支持 $model->table()->where()->field()->query('select %FIELD% from %TABLE% %WHERE%);
[新增] Db类增加buildSelectSql方法 用于生成查询sql Model类增加buildSql方法 用于生成子查询SQL 用法 $Model->where()->field()->order()->limit()->buildSql(); 和select用法一样 buildSql方法必须用在连贯操作的最后
[新增] Model类增加_parseType 用于字段类型检测和转换
[新增] Model类增加_after_db回调接口
[修正] 修正模型类的find方法当主键值为0的时候的BUG
[改进] 改进Model类和M方法 Model类架构方法增加dbName支持和tablePrefix参数 M方法可以直接用于跨库和不同数据表前缀支持
[改进] 表单令牌验证增强 支持多窗口令牌
[改进] 自动验证增强 增加between length expire ip_allow ip_deny等规则
[改进] 优化unique验证规则
[改进] 多数据库操作支持,包括M方法支持和Model类的切换数据库的Db方法 支持
[改进] join方法增强
[改进] getField方法增加用法支持 并支持跨库和表操作
[改进] Model的add和save方法 如果用了$this->data 则及时清空 避免混淆
[改进] Model的getlastsql方法支持当前模型 全局请用M()
[改进] 关联模型跨库操作的改进 操作关联的时候M方法改用D方法
[改进] D方法格式为:[项目://][分组/]模型
[改进] Model类的db方法的第二个config参数支持配置参数
[改进] create方法中的检查字段映射 放到状态检查之前 用于需要映射主键的情况
[改进] Model类create方法中生成数据对象 增加 autoCheckFields属性的判断 如果autoCheckFields关闭 则不过滤表单中的非数据表字段
[改进] 优化Model类的flush方法 在没有获取到字段信息则直接返回false 也不会记录字段缓存
[改进] 字段缓存文件名 增加数据库前缀
[改进] Db类对page连贯操作的完善支持
[改进] 令牌验证放到自动验证成功后 避免验证错误后 表单令牌错误的情况
[改进] 改进模型类的create方法对数据的验证
[改进] 修正Model类的getTableName中关于dbName的获取
[改进] Model类db方法增加params参数 用于切换数据库的时候传人额外的模型参数 原则上支持任意Model类的属性
[删除] 废除model类的findall 如需要用select替代或者在公共模型类里面添加findall定义
[删除] 删除高级模型类里面的切换数据库相关方法 2.1版本以上推荐直接使用模型类的db方法切换
[删除] 去掉setField getField setInc setDec方法的condition条件参数 用连贯操作where替代
[删除] 删除了tableSuffix属性 可以用trueTableName属性替代

[数据库]
[新增] IN 查询增加表达式参数支持 用于某些特殊情况的IN查询 $map['id'] = array('IN',$subQuery,'exp'); 其中subQuery是一个子查询或者SQL函数语法等 就可以使用exp参数在后面
[新增] Mongo驱动增加调试SQL支持 也可以在MongoModel中采用getlastsql或者_sql方法调试最后执行命令 但是仅限于调试模式支持
[新增] db类增加查询sql封装的缓存支持 惯例配置增加 'DB_SQL_BUILD_CACHE' => false, // 数据库查询的SQL创建缓存 'DB_SQL_BUILD_QUEUE' => 'file', // SQL缓存队列的缓存方式 支持 file xcache和apc 'DB_SQL_BUILD_LENGTH' => 20, // SQL缓存的队列长度
[新增] 增加pdo的sqlsrv数据库类型的支持
[新增] 增加sqlsrv数据库驱动
[新增] 查询条件增加定义方式,并增加条件字段安全检测,例如: $map['name|title'] = array('like','%Think'); 等效于 $map['title'] = array('like','%Think'); $map['name'] = array('like','%Think'); $map['_logic'] = 'OR';
[修正] 修正数据库驱动类的getFields方法
[修正] 修正Db类的parseWhere方法在not between 查询条件下面的BUG
[改进] 改进mysql对存储过程查询的支持
[改进] 改进数据库的不等于判断符号 neq 解析为 <>
[改进] 完善Db类的parseFields方法 改进对 Model中field('key,value')的支持
[改进] 改进Db类的parseWhere方法对between数据的处理
[改进] 完善Db类的table解析方法 可以支持数组方式 model类的table方法支持数组方式参数 支持表别名定义,例如 table(array('think_user'=>'user','think_article'=>'article'))
[改进] Db类addSpecialChar方法更名为parseKey 用于统一处理字段名中的关键字
[改进] 数据库驱动类的__destruct方法移到Db类
[改进] 将__TABLE_NAME__这样的字符串替换成正规的表名,并且带上前缀和后缀 例如 $dao->join("__CHANNEL__ as c on c.pid = __COMPANY__.channelpid")->limit(10)->select(); 其中__CHANNEL__ 和 __COMPANY__ 将会翻译成对应的表名 这样使代码更好看也更易理解。
[改进] 改进数据库驱动类对长连接配置的支持,配置下面参数即可 'DB_PARAMS'=>array('persist'=>true),
[改进] 改进PDO驱动类的getFields方法对sqlite的支持
[改进] 完善mssql驱动类的端口支持
[改进] Db类parseConfig方法支持数组配置信息
[改进] mysql和mysqli驱动insertAll方法增加replace参数
[删除] 删除Db类中多余的addConnect和switchConnect方法

[模板]
[新增] 增加布局模板支持
[新增] 在模版中的include标签,可以使用自定义属性, 替换被包含模版中格式为[var]的变量。 如 则被导入的模版中 [varname] 标签会被替换为 value
[新增] Cx标签库增加for标签支持
[新增] 模板禁用PHP语法支持
[新增] 模板标签增加 {++$a} {--$b} 用法支持
[修正] 修正Cx标签库的volist标签的mod属性
[修正] 修正模板变量的函数解析的参数中带有=的BUG
[改进] 去除Cx标签库的layout标签 增加新的layout标签用法
[改进] 分组的模板结构改变为 Tpl/分组名/主题名/模块/操作.html
[改进] 优化include标签解析
[改进] 模板输出规则调整为 [模板主题:][模块:][操作] 不支持 跨项目和跨分组调用 该用法可用于display方法和include标签
[改进] 改进模板输出替换变量__GROUP__ 如果当前没有分组则输出同 __APP__
[调整] 当前模板文件名配置 更名为 TEMPLATE_NAME
[调整] 默认模板主题设置为空 原来为default
[调整] Think模板引擎的load方法更名为fetch
[删除] 去掉Cx标签库的iterate方法定义
[删除] 取消模板快捷变量输出 统一使用标准变量输出方式
[删除] 废弃模板引擎的普通标签方式的include和load标签 以下用法不再支持 {include:public/header.html} {load:/public/css/common.css} 替代用法 因为XML标签方式的include和load用法更强大 ,包括可以传人模板变量名等,XML方式的load标签还有很多的别名用法,完全可以取代普通标签加载。 目前,普通标签仅仅用于输出变量和一些快捷输出。

[扩展]
[新增] 增加REST模式
[新增] CLI模式扩展 增加参数获取功能 所有命令行参数均可以采用 $_GET 方式获取
[修正] 修正amf phprpc模式扩展在allinone模式下面的编译缓存错误
[调整] Model目录移出核心类库 作为扩展模型 放入Extend
[调整] Html标签库移入扩展标签库目录
[调整] auto_charset函数移入扩展函数库
[调整] 更改模块扩展和操作扩展方式 采用hack方式 分别定义__hack_module和__hack_action
[改进] CLI模式支持两种URL方式: pathinfo方式调用(默认) php index.php module/action/id/4/name/hello 并且支持pathinfo分隔符定制 配置URL_PATHINFO_DEPR即可 php index.php module_action_id_4_name_hello 普通方式调用 php index.php module action id 4 name hello
[改进] Cli模式的Action类增加_initialize方法支持
[删除] 移除兼容模式扩展Compat

[其他]
[新增] 添加标签执行的日志记录
[新增] 添加行为的执行日志记录
[新增] 添加add_tag_behavior方法 用于动态添加行为扩展到某个标签
[修正] 修正load方法重复导入冲突的问题
[修正] 修正 核心编译列表文件定义后 再次import的冲突bug
[修正] 优化内存开销计算
[修正] 修复分页参数的一个漏洞
[修正] 修正PHP 5.4版本支持
[改进] msubstr函数改进
[改进] 修正String类的msubstr方法不添加...的问题
[改进] 修正扩展函数库的msubstr方法不添加...的问题
[修正] Widget类修正路径
[改进] Widget支持第三方模板引擎扩展
[改进] 框架加载底层默认语言包 并编译缓存
[改进] 优化import方法 去掉同名类库冲突检测
[改进] 优化require_cache函数 去掉realpath处理
[改进] 实现了驱动类库的扩展分离 包括数据库驱动、缓存驱动和模板引擎驱动
[改进] 去掉核心php文件最后的?>
[改进] 重写get_client_ip函数 修正iis下面的问题, 优化而且增加合法性验证,并且从扩展函数库移到内置函数库
[改进] 改进tag方法 没有执行任何行为的话返回false
[修正] 修正init运行时间的显示问题
[改进] View类 output方法并入fetch方法 并删除templateFile属性
[改进] 改进tag方法 允许应用标签扩展覆盖系统标签扩展 默认情况下是合并标签扩展 如果在应用扩展中设置 '_overlay'=>1 则表示完全覆盖系统标签扩展的(对应的系统标签扩展将不被执行) 例如, 'app_begin'=>array('appBehavior1','appBehavior2','_overlay'=>1) 则表示app_begin标签将不再读取系统定义的标签扩展行为
[改进] 自动生成目录的时候 生成项目的Behavior、Widget和Filter目录
[改进] Action析构方法增加日志保存操作
[改进] 优化编译缓存
[改进] 日志信息添加当前URL记录
[调整] 去掉Think基类的依赖和继承 Think类用于放置静态方法
[调整] Dispatcher类移动到Think/Core包
[调整] 系统内置模板文件统一为tpl 后缀
[删除] 去掉compat函数文件

[缓存]
[新增] Cache类增加队列功能
[新增] 增加redis缓存方式支持
[新增] S方法添加options参数 用于传人缓存类的连接配置参数 cache驱动类取消type属性,调整expire参数 加入连接配置参数,另外配置参数增加 length 驱动队列的长度 默认为0 表示不限制 queue 驱动队列的缓存方式 默认为file 支持xcache和apc
[新增] Model增加查询缓存支持,使用连贯操作cache调用,用法如下: 采用默认配置缓存 $Model->cache()->select(); 采用memcche缓存1小时 $Model->cache(true,3600,'memcache')->select(); 采用memcache缓存一小时 并设置缓存别名cache_name $Model->cache('cache_name',3600,'memcache')->select();
[修正] 修正CacheDb缓存驱动类
[改进] 完善Memcache缓存驱动 增加MEMCACHE_HOST和MEMCACHE_PORT配置 Db缓存类的缓存表去掉id字段 cachekey字段改为唯一
[改进] 优化缓存驱动类
[改进] F方法缓存的时候去掉空白和注释 修正C方法的二级配置大小写问题

ThinkPHP+Redis 缓存类

为了让ThinkPHP支持Redis,写了这个Redis的缓存类,里面只有简单的Get和Set方法,方便在S方法直接使用,复杂的使用方法,可以直接使用phpRedis。

本类要求支持phpRedis安装方法:http://www.4wei.cn/archives/1001521

在TP项目中的路径

ThinkPATH\Lib\Think\Util\Cache\CacheRedis.class.php

Extend\Driver\Cache\CacheRedis.class.php

配置方法,在ThinkPHP项目配置文件中,添加以下参数:

	/* 系统缓存 */
	'DATA_CACHE_TYPE'					=> 'Redis',
	'REDIS_HOST'						=> '192.168.0.243',
	'REDIS_PORT'						=> 6379,
	'DATA_CACHE_TIME'					=> 3600,

上述参数分别表示缓存类型,主机,端口和超时时间。

附件下载:CacheRedis.class SVN中已经更新
源码如下:

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 尘缘 <130775@qq.com>
// +----------------------------------------------------------------------
// $Id: CacheRedis.class.php 2728 2012-02-12 04:12:51Z liu21st $

/**
 +-------------------------------------
 * CacheRedis缓存驱动类
 * 要求安装phpredis扩展:https://github.com/owlient/phpredis
 +-------------------------------------
 */
class CacheRedis extends Cache {

    /**
     +----------------------------------------------------------
     * 架构函数
     +----------------------------------------------------------
     * @access public
     +----------------------------------------------------------
     */
    public function __construct($options='') {
        if ( !extension_loaded('redis') ) {
            throw_exception(L('_NOT_SUPPERT_').':redis');
        }
        if(empty($options)) {
            $options = array (
                'host'  => C('REDIS_HOST') ? C('REDIS_HOST') : '127.0.0.1',
                'port'  => C('REDIS_PORT') ? C('REDIS_PORT') : 6379,
                'timeout' => C('DATA_CACHE_TIMEOUT') ? C('DATA_CACHE_TIMEOUT') : false,
                'persistent' => false,
                'expire'   => C('DATA_CACHE_TIME'),
                'length'   => 0,
            );
        }
        $this->options =  $options;
        $func = $options['persistent'] ? 'pconnect' : 'connect';
        $this->handler  = new Redis;
        $this->connected = $options['timeout'] === false ?
            $this->handler->$func($options['host'], $options['port']) :
            $this->handler->$func($options['host'], $options['port'], $options['timeout']);
    }

    /**
     +----------------------------------------------------------
     * 是否连接
     +----------------------------------------------------------
     * @access private
     +----------------------------------------------------------
     * @return boolen
     +----------------------------------------------------------
     */
    private function isConnected() {
        return $this->connected;
    }

    /**
     +----------------------------------------------------------
     * 读取缓存
     +----------------------------------------------------------
     * @access public
     +----------------------------------------------------------
     * @param string $name 缓存变量名
     +----------------------------------------------------------
     * @return mixed
     +----------------------------------------------------------
     */
    public function get($name) {
        N('cache_read',1);
        return $this->handler->get($name);
    }

    /**
     +----------------------------------------------------------
     * 写入缓存
     +----------------------------------------------------------
     * @access public
     +----------------------------------------------------------
     * @param string $name 缓存变量名
     * @param mixed $value  存储数据
     * @param integer $expire  有效时间(秒)
     +----------------------------------------------------------
     * @return boolen
     +----------------------------------------------------------
     */
    public function set($name, $value, $expire = null) {
        N('cache_write',1);
        if(is_null($expire)) {
            $expire  =  $this->options['expire'];
        }
        if(is_int($expire)) {
            $result = $this->handler->setex($name, $expire, $value);
        }else{
            $result = $this->handler->set($name, $value);
        }
        if($result && $this->options['length']>0) {
            // 记录缓存队列
            $this->queue($name);
        }
        return $result;
    }

    /**
     +----------------------------------------------------------
     * 删除缓存
     *
     +----------------------------------------------------------
     * @access public
     +----------------------------------------------------------
     * @param string $name 缓存变量名
     +----------------------------------------------------------
     * @return boolen
     +----------------------------------------------------------
     */
    public function rm($name) {
        return $this->handler->delete($name);
    }

    /**
     +----------------------------------------------------------
     * 清除缓存
     +----------------------------------------------------------
     * @access public
     +----------------------------------------------------------
     * @return boolen
     +----------------------------------------------------------
     */
    public function clear() {
        return $this->handler->flushDB();
    }
}

ThinkPHP提示“系统不支持:xxxx”

如果你在使用ThinkPHP,当你的模块出现以下提示时,则表明你的服务器不支持对应的扩展。

如:
系统不支持:mysql
系统不支持:pgsql
系统不支持:mssql
系统不支持:memcache
系统不支持:zip
等等。

这表示你的服务器还没有安装mysql、memcache等扩展,你可以到你的php.ini中看看有没有启用对应的extension。

ThinkPHP Debug类及实例

类名:Debug

mark(name) 标记调试位置,静态方法

useTime(start,end,decimals = 6) 调试区间所用的时间,静态方法

useMemory(start,end) 调试区间所用的内存,静态方法

getMemPeak(start,end) 调试区间的内存占用峰值,静态方法

实例:


$start = 'debug_start';
$end = 'debug_end';
Debug::mark($start);
//处理你的业务逻辑
Debug::mark($end);

printf("Use Time:%s\n", Debug::useTime($start,$end));
printf("Use Memory:%s\n", Debug::useMemory($start,$end));

还有一个简化的方法:

使用下面的方法可以很方便的获取某个区间的运行时间和内存占用情况
//debug_start($label='') //记录调试开始时间
//debug_end($label='')  //输出调试范围运行时间(相同label属于一个调试范围)
//例如:
debug_start('run');

$blog = D("Blog");

$blog->select();

debug_end('run');

//会输出下面的运行信息:
Process run: Times 0.007730s Memories 76 k

Memcache IN ThinkPHP

ThinkPHP中启用Memcache缓存是比较简单的。

首先导入一下扩展缓存类,在TP的扩展完整包中,然后在项目配置文件config.php添加以下配置信息。

其中,Memcache默认端口是11211

设置缓存为Memcache
'DATA_CACHE_TYPE' => 'Memcache',
'MEMCACHE_HOST'   =>  'tcp://127.0.0.1:11211',
'DATA_CACHE_TIME' => '3600',

方法直接使用S函数即可。

当然,也可以清除S中的所有缓存

$cache  = Cache::getInstance();
$cache ->clear();

Nginx+ThinkPHP不支持PathInfo的解决办法

应集团要求,公司的服务器全收到集团机房统一管理了,失去了服务器的管理配置权限。

杯具就此开始。

首先要解决文件大小写的问题。哥在开发的时候,比较注意大小写、文件名、相对路径的问题,程序整体迁移没有遇到任何问题。

其次是WebServer不支持PathInfo的问题。集团的运维同事,在所有服务器上都跑着Linux+Nginx,导致Apache开发的PathInfo模式出现艰难的迁移问题。

由于Nginx+Pathinfo有一定的不安全因素,要求开启PathInfo的请求被拒绝,找到TP论坛,发现官方的同志是这样解决问题的。

继续阅读Nginx+ThinkPHP不支持PathInfo的解决办法

ThinkPHP中RBAC数据库详解

    许多人提到有关ThinkPHP都会对它的高效,方便为之折服。但与很多朋友交流后,都对它当中的RBAC权限控制,感到头疼不已。看完这个文档,相信大家一定会有很清晰的了解。并且,以后关于这个框架的权限控制非常熟析,并且能熟练掌握。
    RBAC许多人听到后会觉得很头疼,不知道这是什么东西。用英文来讲的话就是Role-Based Access Control,用中文来说就是——基于角色的权限访问控制。呵呵,是不是听的头晕了?没那么复杂。你只需要了解一点,它是来控制用户组权限的就得了,有的用户组的用户可以添加,但是有的用户组的用户却可以,就是这样一个简单的东西。如果再搞得大家头晕一点的话,他还包括什么:安全拦截器,认证管理器...等等概念咯。我觉得如果仅仅是要使用的话,这些东西在最开始的时候其实没必要了解。只要清楚,怎么用的,以后对于这些概念即可以一看就懂,一用就通。
    大家在电影当中看到过一种场景吗?——需要两个人的密码卡,指纹和眼球扫描才能够打开金库。
    或者说是在现实生活中:一个营业人员能进行一些操作,但是输入错误或者一些特殊情况下面,会叫“经理,权限不够”。这个时候,他们的经理就会拿着自己的IC卡过来,然后输入密码,该营业员就能够进行该项操作了。
    类似于这样一种两个身份登陆认证才能进行操作的功能,目前ThinkPHP2.0版本中,还不能实现。如果想使用这样的功能,只有大家自己写一个操作类或者是在ThinkPHP进行修改或者扩展了。
    废话不多说,我们先去下载一个ThinkPHP的框架(注:在1.5版本以后ThinkPHP关于RBAC等扩展类就不包含在核心包当中了,如果大家要下载的话可以去下载最新的ThinkPHP2.0带扩展、示例和文档完整包)。下载地址如下:http://www.thinkphp.cn/Down/(别下成核心包了哦!)
    下载解压完成,将其放入我们的根目录或者子目录下。
    我们先跳过去这一段,讲一个权限化分中最重要,大家最容易弄混的地方,就是RBAC需要用到的表和字段。我们先来了解一下RBAC需要用到的数据表。将建立一个五张表,这五张表分别是:
    1.    用户表(包含三个字段用户编号,用户名和用户密码)
    2.    用户组表(用户组编号和名字)
    3.    用户与组的对应关系表(组编号和用户编号)
    4.    节点表(节点编号,名字,注释,父路径编号,等级)(注:这个表很容易将人搞晕)
    5.    权限表(组编号,节点编号,父路径编号,等级)

-- 表的结构 `think_access`此表为五张表中的权限表
CREATE TABLE IF NOT EXISTS `think_access` (
  `role_id` smallint(6) unsigned NOT NULL,
  `node_id` smallint(6) unsigned NOT NULL,
  `level` tinyint(1) NOT NULL,
  `pid` int(11) DEFAULT NULL,
  KEY `groupId` (`role_id`),
  KEY `nodeId` (`node_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- 表的结构 `think_node`此表为五张表中的节点表
CREATE TABLE IF NOT EXISTS `think_node` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `title` varchar(50) DEFAULT NULL,
  `pid` smallint(6) unsigned NOT NULL,
  `level` tinyint(1) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `level` (`level`),
  KEY `pid` (`pid`),
  KEY `name` (`name`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
-- 表的结构 `think_role`此表为五张表中的用户组表
CREATE TABLE IF NOT EXISTS `think_role` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `pid` (`pid`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
-- 表的结构 `think_role_user`此表为用户和组对应关系表
CREATE TABLE IF NOT EXISTS `think_role_user` (
  `role_id` mediumint(9) unsigned DEFAULT NULL,
  `user_id` char(32) DEFAULT NULL,
  KEY `group_id` (`role_id`),
  KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- 表的结构 `think_user`此表为用户表
CREATE TABLE IF NOT EXISTS `think_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) NOT NULL,
  `password` char(32) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
 

    以下五个表,我现在写好演示用的SQL执行语句帮助大家建表,大家在实验建表的过程中只需要复制使用下面的SQL语句即可:
    看着是不是又晕了?呵呵,没关系,我们来用一个简单的图表和简易图来像大家说明:
    表Think_access:
    Role_ID       Node_id        pid            level
    用户组的编号    节点表    节点表中的父ID项    节点表中的等级项
    注:如果用户组id和对应的节点id存在这张表中,就表示用户所在的用户组有权限进行对应的操作权限。
    表Think_node
    id    name    title    pid    level
    节点编号,用来产生关联关系,主键,自增方便索引    项目,模块或者动作的名字(严格区分大小写)    项目或模块的备注,方便管理员认识和了解    注①    只能为1,2,3分别代表项目,模块,操作动作
    注①    :
    1)如果是项目名称则项目是顶级的,它上面没有父路径编号(pid),因此pid则为0;例如,网站下有一个Admin项目,它是顶级的。因此没有父路径编号(pid),此处应填0,如果它为第一次插入数据库的,它的id则为1。全字段插入数据库应为:
    id    name    title    pid    level
    1    Admin    Admin项目节点    0    1
    2)如果某项目下有一个模块。则父路径编号(pid)则为该某块所在的项目编号(id)。例如,Admin下面有一个模块为User,它是第二个插入数据库的。因此,模块的父路径(pid)应填1。全字段插入数据库应为:
    id    name    title    pid    level
    2    User    User模块节点    0    2
    3)如果在某模块下面有三个动作(或成员方法),分别Index(显示)、insert(插入)、delete(删除),它们三个的父路径为该模块的编号。例如User模块下有Index、insert、delete三个动作或成员方法,它们分别是第三,四,五次插入数据库的。他们的父路径编号(pid)应填2。全字段插入数据库应为:
    id    name    title    pid    level
    3    User    User模块下显示动作    0    2
    4    Index    User模块下插入动作    0    2
    5    insert    User模块下删除动作    0    2
    表think_role
    id    name
    组的编号(主键)    组的名字
    注:此表中可以插入父路径编号(pid)产生组的包含关系。对应的父路径编号(pid)为所属组的编号(id)。
    表think_user
    id    username    password
    用户编号(主键,int类型)    用户名(varchar类型)    密码(char类型32位)
    注:id在下一张表中结合think_role产生用户与组的关联关系。建议username设为唯一的,password采用32位md5加密。
    表think_role_user
    group_id    User_id
    组编号(主键,int类型)    用户编号(主键,int类型)
    注:用户编号(user_id)对应哪一个group_id,则哪个用户就属于哪一个组。可以让同一个用户对应多个组。就意味着,一个用户具有多个组的属性和操作权限。
    最难理解,也是最核心的数据库结构和存储结构说完了,对照着这个数据库进行插入,添加修改即可。<

Sqlite批量插入速度慢的解决方法【ThinkPHP】

使用ThinkPHP大批量转移数据一份,结果发现SQLite插入速度慢,几分钟才插入几千条,要了我的小命了。

后来在网上找到SQLite的插入机制,知道要在插入时使用事务,可以加速。

SQLite的事务请使用begin和commit,在TP中,可以直接使用startTrans和commit;

$db = M("userlist");
$db->startTrans();
foreach ($userList as $qq=>$userName)
{
	$data['id']	= NULL;
	$data['username']	= $userName;
	$data['psw']		= '';
	$data['email']		= '';
	$data['qq']			= $qq;
	$data['dateline']	= 0;
	$data['status']		= 0;
	
	$db	->data($data)
		->add();
}
$db->commit();

ThinkPHP 2.1RC 的自动验证的小BUG。

Windows上:
UserlistModel.class.php

<?php
class UserlistModel extends Model {

// 自动验证设置
protected $_validate = array(
array('u_email','email','请您正确填写邮件地址!'),
array('u_name','require','请您正确填写姓名!'),
array('u_phone','number','请您正确填写手机号码'),
array('u_age','number','请您填写正确的年龄,只能填写数字!'),
array('sourceid','number','请正确选择您所在的城市!'),
);

// 自动填充设置
protected $_auto = array(
array('ifcheck','0',self::MODEL_INSERT),
array('dateline','time',self::MODEL_INSERT,'function'),
);

}
?>

移植到Linux上,自动验证不自动启动,检查了一下,发现是文件名的问题,修改文件名为小写以后,问题解决。
Linux上:
userlistModel.class.php

这个问题不知道有没有其它兄弟遇上,茫然中,求确认。

后确认此纠结源于TP未对自动验证的文件名做出验证,跟踪时也不会提示有错误。

正确的解决办法是严格按照TP的语法创建模型。
如:

$m = D("userlist");

$m = D("Userlist");

按TP的标准语法,应该是后者,对应的是UserlistModel.class.php。

如是,这里的大小写标准有增加开发成本的嫌疑!