一、项目分组
下载解压 ThinkPHP 3.2.3,在默认的应用 Application(./Application) 中,包含一个默认的模块 Home(./Application/Home)。
需要在该默认应用中创建一个用于后台管理的 Admin 模块,可以通过在应用入口文件(./index.php)中绑定 Admin 模块来 自动生成 Admin 模块:
define('BIND_MODULE','Admin');
此时访问 http://serverName/index.php 便会自动在 ./Application 下创建 Admin 目录(要记得把上面的定义 删掉,否则通过入口文件访问网站首页就会默认访问 Admin 模块)。
不需要修改入口文件。
此时访问 http://serverName/index.php/Admin 就可以访问后台的 Index 控制器的 index 方法了。
目录结构如下:
二、应用配置和模块配置
应用配置(公共配置文件)位于 ./Application/Common/Conf/config.php ,在 ThinkPHP 调用所有的模块之前加载。
在这里没有做特别的配置,只开启了 Trace 追踪信息:
<?php
return array(
//'配置项'=>'配置值'
//显示页面 Trace 信息
'SHOW_PAGE_TRACE' => true,
);
Admin 模块的模块配置位于 ./Application/Admin/Common/Conf/config.php
该项目开启的配置包括:
① 数据库
//数据库配置信息
'DB_TYPE' => 'mysql', // 数据库类型
'DB_HOST' => '127.0.0.1', // 服务器地址
'DB_NAME' => 'tptest', // 数据库名
'DB_USER' => 'root', // 用户名
'DB_PWD' => '', // 密码
'DB_PORT' => 3306, // 端口
'DB_PARAMS' => array(), // 数据库连接参数
'DB_PREFIX' => 'crm_', // 数据库表前缀
'DB_CHARSET'=> 'utf8', // 字符集
'DB_DEBUG' => TRUE, // 数据库调试模式 开启后可以记录SQL日志
② 配置后台公共文件
由于该项目同时包含 Home、Admin 模块,所以公共文件必须分开,因此可以把 Admin 模块的公共文件放在 ./Application/Public/Admin 下,在 Admin 的模块配置文件中配置:
//后台公共文件路径
'TMPL_PARSE_STRING' => array(
'__PUBLIC__' => __ROOT__.'/Public/Admin'
),
③ 定义异常页面
//异常页面
'TMPL_EXCEPTION_FILE' => './Public/Admin/error.html',
异常页面 error.html 中错误信息可以用 $e['message'] 表示
如果需要有倒计时跳转的功能,可以参考 ./ThinkPHP/Tpl/dispatch_jump.tpl 文件
error.html:
<!DOCTYPE html>
<html>
<head>
<title>error</title>
<meta charset="UTF-8">
</head>
<body>
<?php echo $e['message'];?><br />
<b id="wait">5</b>秒后跳转回首页 或 点击<a id="href" href="/Admin/Index/index">返回首页</a>
<script>
(function(){
var wait = document.getElementById('wait'),href = document.getElementById('href').href;
var interval = setInterval(function(){
var time = --wait.innerHTML;
if(time <= 0) {
location.href = href;
clearInterval(interval);
};
}, 1000);
})();
</script>
</body>
</html>
例如访问了不存在的方法,页面会输出:
无法加载控制器:Login2
3秒后跳转回首页 或 点击返回首页
然后跳转回后台主页。
注:该页面中不能使用 ThinkPHP 的标签,只能使用原生的 PHP 语句。
三、管理员登陆
用于登陆的文件是 Admin 模块下的 LoginController.class.php
<?php
namespace Admin\Controller;
use Think\Controller; class LoginController extends Controller{
public function index() {
$this->display();
} //验证码
public function verify() {
$conf = array(
//'useZh'=>true,//使用中文
'fontSize'=>20,
'length'=>1,
'imageW'=>100,//验证码宽度
);
$Verify = new \Think\Verify($conf);
$Verify->entry();
} public function login() {
if(!IS_POST) {
e('非法登陆');
} //检验验证码
if(!check_verify(I('post.code'))) {
$this->error('验证码错误');
} $username = I('post.username', '');
$user = M('user')->where(array('username'=>$username))->find(); if(!$user || md5(I('post.password')) != $user['password']) {
$this->error('用户名或密码错误');
} if($user['lock']) $this->error('用户被锁定'); //更新用户表
$data = array(
'id'=>$user['id'],
'logintime'=>time(),
'loginip'=> get_client_ip()
);
M('user')->save($data); //保存session
session("user.uid", $user['id']);
session("user.username", $user['username']);
session("user.logintime", date('Y-m-d H:i:s', $user['logintime']));
session("user.loginip", $user['loginip']); //成功跳转
$this->redirect('Admin/Index/index'); }
}
几点说明:
① IS_POST
位于 ./ThinkPHP/Library/Think/App.class.php(ThinkPHP 应用程序类 执行应用程序管理) 的 static public function init() 方法(应用程序初始化)内
定义当前请求的系统常量,源码:
define('REQUEST_METHOD', $_SERVER['REQUEST_METHOD']);
define('IS_POST', REQUEST_METHOD =='POST' ? true : false);
② e 方法
位于 ThinkPHP/Common/functions.php (Think 系统函数库)
/**
* 抛出异常处理
* @param string $msg 异常消息
* @param integer $code 异常代码 默认为0
* @throws Think\Exception
* @return void
*/
function E($msg, $code=0) {
throw new Think\Exception($msg, $code);
}
③ 模板路径
默认情况下,Admin 模块 Login 控制器的 index 方法对应的模板文件应该是 ./Application/Admin/View/Login/index.html
如果不希望目录层级太多的话,可以将该方法对应的模板文件设置为 ./Application/Admin/View/Login_index.html,减少了一层目录,在 Admin 模块的模块配置 config.php 中添加:
//模版路径
'TMPL_FILE_DEPR' => '_',
登陆以后进入后台首页 http://serverName/Admin/Index/index
Admin 模块的 Index 控制器 ./Application/Admin/Controller/IndexController.class.php:
<?php
namespace Admin\Controller; class IndexController extends CommonController {
public function index(){
$this->display();
} //推出登陆
public function loginout() {
session_unset();
session_destroy();
$this->redirect('Admin/Login/index');
}
}
注:要进入后台首页,必须经过登陆,因此在加载该控制器之前应该先检查用户是否登陆,检查的方法可以写在 Admin 模块的 Common 控制器中,然后 Index 控制器继承 Common 控制。Common 控制器位于 ./Application/Admin/Controller/CommonController.class.php:
<?php
namespace Admin\Controller;
use Think\Controller; class CommonController extends Controller{
public function _initialize() {
if(!isset($_SESSION['user']['uid']) || !isset($_SESSION['user']['username'])) {
$this->redirect('Admin/Login/index');
}
}
}
注:在 ./ThinkPHP/Library/Think/Controller.class.php (ThinkPHP 控制器基类)定义了
/**
* 架构函数 取得模板对象实例
* @access public
*/
public function __construct() {
Hook::listen('action_begin',$this->config);
//实例化视图类
$this->view = Think::instance('Think\View');
//控制器初始化
if(method_exists($this,'_initialize'))
$this->_initialize();
}
登陆模块的视图文件位于 ./Application/Admin/View/Login_index.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="__PUBLIC__/Css/login.css" />
<meta charset="utf-8">
<script src="__PUBLIC__/Js/jquery-1.7.2.min.js"></script>
<script src="__PUBLIC__/Js/login.js"></script>
</head>
<body>
<div id="top"> </div>
<div class="login">
<form action="{:U('Admin/Login/login')}" method="post" id="login">
<div class="title">
登录后台
</div>
<table border="1" width="100%">
<tr>
<th>管理员帐号:</th>
<td>
<input type="username" name="username" class="len250"/>
</td>
</tr>
<tr>
<th>密码:</th>
<td>
<input type="password" class="len250" name="password"/>
</td>
</tr>
<tr>
<th>验证码:</th>
<td>
<input type="code" class="len250" name="code"/> <img src="{:U('Admin/Login/verify','','')}" id="code"/> <a href="javascript:void(change_code(this));">看不清</a>
</td>
</tr>
<tr>
<td colspan="2" style="padding-left:160px;"> <input type="submit" class="submit" value="登录"/></td>
</tr>
</table>
</form>
</div>
</body>
</html>
注:
① U 方法
在控制器使用 U 方法的格式是 U(模块/控制器/方法, array('参数1'=>'参数1的值','参数2'=>'参数2的值', '伪静态后缀'))
在模板中使用 U 方法的格式是 {:U(模块/控制器/方法, array('参数1'=>'参数1的值','参数2'=>'参数2的值', '伪静态后缀'))}
② 伪静态后缀
可以在模块配置文件 config.php 中配置伪静态后缀,默认为 .html,即 URL 可能是 http://serverName/Admin/Index/index.html。可以设置为空,即 URL 可能为 http://serverName/Admin/Index/index
//伪静态后缀
'URL_HTML_SUFFIX'=>'',
Admin 模块的文件目录:
crm_user 表:
CREATE TABLE `crm_user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` char(20) NOT NULL DEFAULT '',
`password` char(32) NOT NULL DEFAULT '',
`logintime` int(10) unsigned NOT NULL,
`loginip` varchar(30) NOT NULL,
`lock` tinyint(1) unsigned NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
四、自定义 Session 存储
① 把 Session 存储在 MySQL 数据库中
ThinkPHP 3.2.3 自带了 Db 类型的 Session 驱动,Db 类位于 ./ThinkPHP/Library/Think/Session/Driver/Db.class.php
框架已经准备好了 session 表:
CREATE TABLE think_session (
session_id varchar(255) NOT NULL,
session_expire int(11) NOT NULL,
session_data blob,
UNIQUE KEY `session_id` (`session_id`)
);
修改模块配置 ./Application/Admin/Common/Conf/config.php,增加以下配置即可:
'SESSION_TYPE'=>'Db',
② 把 Session 存储在 Redeis 中
ThinkPHP 3.2.3 自带了 Memcache 驱动,可以参考该驱动来开发 Redis 驱动,新建 Redis.class.php,放在 ./ThinkPHP/Library/Think/Session/Driver 下:
<?php
// +---------------------------------------------------------------------
// | Date:2016/01/09
// +----------------------------------------------------------------------
namespace Think\Session\Driver; class Redis {
//保存Redis连接对象
protected $redis;
//Session有效时间
private $expire; /**
* 打开Session
* @access public
* @param string $savePath
* @param mixed $sessName
*/
public function open($savePath, $sessName) {
$this->expire = C('SESSION_EXPIRE') ? C('SESSION_EXPIRE') : ini_get('session.gc_maxlifetime');
$this->redis = new Redis();
return $this->redis->connect(C('REDIS_HOST'), C('REDIS_PORT'));
} /**
* 关闭Session
* @access public
*/
public function close() {
return $this->redis->close();
} /**
* 读取Session
* @access public
* @param string $sessID
*/
public function read($sessID) {
$sessID = C('SESSION_PREFIX').$sessID;
$sessData = $this->redis->get($sessID);
return $sessID ? $sessID : '';
} /**
* 写入Session
* @access public
* @param string $sessID
* @param String $sessData
*/
public function write($sessID, $sessData) {
$sessID = C('SESSION_PREFIX').$sessID;
return $this->redis->set($sessID, $sessData, $this->expire);
} /**
* 删除Session
* @access public
* @param string $sessID
*/
public function destroy($sessID) {
$sessID = C('SESSION_PREFIX').$sessID;
return $this->redis->delete($sessID);
} /**
* Session 垃圾回收
* @access public
* @param string $sessMaxLifeTime
*/
public function gc($sessMaxLifeTime) {
return true;
}
}
然后修改模块配置文件 config.php:
'SESSION_TYPE'=>'Redis',
//Redeis服务器地址
'REDIS_HOST'=>'127.0.0.1',
//Redis端口号
'REDIS_PORT'=>'6379',
//Session前缀
'SESSION_PREFIX'=>'session_',
//Session有效时间
'SESSION_EXPIRE'=>3600
五、文章管理
文章管理的控制器 ArticleManageController.class.php:
<?php
/*
* 文章管理类
* date:2016/01/10
*/
namespace Admin\Controller;
use Think\Page; class ArticleManageController extends CommonController{
public function index() { $count = M('article')->count(); $page = new Page($count, 3);
$limit = $page->firstRow.','.$page->listRows;
$show = $page->show(); $list = M('article')->limit($limit)->select();
$this->assign('list', $list);
$this->assign('show', $show);
$this->display();
}
}
注:ThinkPHP 3.2.3 的分页类位于 ./ThinkPHP/Library/Think/Page.class.php,因此除了使用 use 关键字引入命名空间为 Think 的 Page类,然后直接实例化 Page类外,还可以在需要实例化 Page 类的时候 new \Think\Page(); Think 前面的 \ 代表根命名空间。
在使用分页类的时候只需要把总条数和每页分配的条数作为参数传给 Page 类以获取 limit 参数。
文章管理的视图文件位于 ./Application/Admin/View/ArticleManage_index.html:
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<css file='__PUBLIC__/Css/public.css'/>
</head>
<body>
<table class='table'>
<tr>
<th>序号</th>
<th>标题</th>
<th>发布人</th>
<th>内容概述</th>
<th>发布时间</th>
<th>操作</th>
</tr>
<volist name='list' id='vo'>
<tr>
<td>{$vo.id}</td>
<td>{$vo.title}</td>
<td>{$vo.creat_id}</td>
<td>{$vo.content}</td>
<td>{$vo.addtime|date="Y-m-d H:i", ###}</td>
<td>
<a href="#">删除</a>
</td>
</tr>
</volist>
<tr>
<td colspan="6" align="center">{$show}</td>
</tr>
</table>
</body>
</html>
注:模板中使用了点语法解析数组,点语法在模板中除了解析数组外,还可以解析对象。可以在模块配置中设置点语法只解析数组,可以使模板解析速度更快:
//点语法默认解析
'TMPL_VAR_IDENTIFY' => 'array',