# 登录
先看效果:
![2015-07-03/5595db3b5f057](http://box.kancloud.cn/2015-07-03_5595db3b5f057.png)
后台的权限比较大,什么资源都能管。
有的时候,为了安全考虑 很多产品把后台设计的很复杂。比方说,密码复杂度、验证码、错误几次输入密码后,锁死账号、子管理员账号不同权限等。
为了简单的实现安全,我后台干脆不用表存管理员账号,配置中写死。这样就不存在说注入的问题。也不会实现多管理员登录。然后密码弄稍微复杂的自己能记忆的密码。 账号:freelog 密码:jay2015 写在admin模块配置注释里防止忘了。
由于登录页没有导航这样公共的模块,所以所有模板代码直接写在一个html里。
我们看下代码:
~~~
<!DOCTYPE HTML>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{:C('WEB_SITE_TITLE')}</title>
<script type="text/javascript" src="__BOWER__/jquery/dist/jquery.js"></script>
<!--[if lt IE 9]>
<script src="__STATIC__/html5shiv.js"></script>
<script src="__STATIC__/respond.js"></script>
<![endif]-->
<script type="text/javascript" src="__BOWER__/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript">
var url = '';
</script>
<script type="text/javascript" src="__JS__/admin.js"></script>
<link rel="stylesheet" type="text/css" href="__BOWER__/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="__CSS__/admin_custom.css">
</head>
<body>
<!--[if lt IE 8]>
<div class="browsehappy">当前网页 <strong>不支持</strong> 你正在使用的浏览器. 为了正常的访问, 请 <a href="http://browsehappy.com/">升级你的浏览器</a>.</div>
<![endif]-->
<div class="container-fluid">
<div class="row-fluid">
<div class="col-md-5"></div>
<div class="col-md-2">
<form class="login-form" method="post" action="{:U('System/check')}">
<fieldset>
<h3 class="welcome"><i class="login-logo"></i>freelog</h3>
<div class="form-group">
<div class="controls">
<input type="text" name="loginName" placeholder="用户名" class="form-control" required title="请填写用户名">
</div>
</div>
<div class="form-group">
<div class="controls">
<input type="password" name="loginPwd" placeholder="密码" class="form-control" required title="请填写密码">
</div>
</div>
<div class="form-group">
<div class="controls">
<button class="btn-block primary ajax-post" type="submit">登录</button>
<a class="btn-link" href="/">返回首页</a>
</div>
</div>
</fieldset>
</form>
</div>
<div class="col-md-5"></div>
</div>
</div>
</body>
</html>
~~~
~~~
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
~~~
这样的写法叫浏览器条件注释,专门针对ie低版本的写法。
~~~
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
~~~
是为了保证兼容性,针对ie8的写法,意思是装了chrome的系统,ie打开时用chrome渲染,没装继续用ie的edge引擎渲染。
~~~
<script type="text/javascript" src="__BOWER__/jquery/dist/jquery.js"></script>
<!--[if lt IE 9]>
<script src="__STATIC__/html5shiv.js"></script>
<script src="__STATIC__/respond.js"></script>
<![endif]-->
<script type="text/javascript" src="__BOWER__/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript">
var url = '';
</script>
<script type="text/javascript" src="__JS__/admin.js"></script>
<link rel="stylesheet" type="text/css" href="__BOWER__/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="__CSS__/admin_custom.css">
~~~
引入一些常规的jquery、bootstrap组件和自己后台用的admin.js。
~~~
<!--[if lt IE 8]>
<div class="browsehappy">当前网页 <strong>不支持</strong> 你正在使用的浏览器. 为了正常的访问, 请 <a href="http://browsehappy.com/">升级你的浏览器</a>.</div>
<![endif]-->
~~~
做了一个ie8向下的提示更换浏览器的处理。让ie6、7去见鬼吧。
~~~
<div class="container-fluid">
<div class="row-fluid">
<div class="col-md-5"></div>
<div class="col-md-2">
<form class="login-form" method="post" action="{:U('System/check')}">
<fieldset>
<h3 class="welcome"><i class="login-logo"></i>freelog</h3>
<div class="form-group">
<div class="controls">
<input type="text" name="loginName" placeholder="用户名" class="form-control" required title="请填写用户名">
</div>
</div>
<div class="form-group">
<div class="controls">
<input type="password" name="loginPwd" placeholder="密码" class="form-control" required title="请填写密码">
</div>
</div>
<div class="form-group">
<div class="controls">
<button class="btn-block primary ajax-post" type="submit">登录</button>
<a class="btn-link" href="/">返回首页</a>
</div>
</div>
</fieldset>
</form>
</div>
<div class="col-md-5"></div>
</div>
</div>
~~~
这里,用了bootstrap的网格流动布局。为了让登录框居中,
我按照bootstrap默认表单宽度,大概算出它占2列,然后左右留 各5个空白的列。
当屏幕小于5列宽时就会 表单撑开,全宽,达到响应式。
![2015-07-05/559897c7df75d](http://box.kancloud.cn/2015-07-05_559897c7df75d.png)
更小宽度也是如此。
![2015-07-05/559897e0dd49f](http://box.kancloud.cn/2015-07-05_559897e0dd49f.png)
由于只是后台,没分太细的宽度,如果要匹配不同宽度,需要div上加 col-sm col-sm 之类的类去控制。后台登录页面没必要做太细。
后台登录逻辑和前台差不多,只不过不用查库了,直接配置里的键去比较表单项。
~~~
/* 登录验证 */
public function check() {
//接收数据
$loginName = trim(I('post.loginName'));
$loginPwd = trim(I('post.loginPwd'));
if (C('ADMIN.LOGIN_NAME') == $loginName) {
if (md5($loginPwd) == C('ADMIN.PWD')) {
$user = array(
'admin_id' => 1,
'admin_name' => $loginName,
'login_time' => NOW_TIME, //上次登录时间
);
//设置登录SESSION
session(C('USER_AUTH_KEY'), $user);
session(C('USER_AUTH_SIGN_KEY'), user_auth_sign($user));
$this->success('登录成功', U('System/index'));
} else {
$this->error('密码错误');
}
} else {
$this->error('该管理员不存在');
}
}
~~~
里面的签名方法复用了OneThink的。
然后注意的一点是 NOW_TIME 常量。每次获取当前时间戳用time() 多次用就浪费效率了。
所以TP定义了一个常量供框架使用。
`System/check` 比`User/login` 难让黑客们猜到。
# 导航
登录过后,就可以看见除了登录页面外,每个页面都有的导航
![2015-07-05/55989d05a979a](http://box.kancloud.cn/2015-07-05_55989d05a979a.png)
我是在bootstrap3 默认的带下拉菜单的导航基础上加上反转背景色、加上子菜单箭头和高亮当前导航实现的效果。
~~~
<div class="navbar navbar-inverse">
<div class="navbar-inner">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="{:U('System/index')}">首页 <span class="sr-only">(current)</span></a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">资源 <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="{:U('Resource/tags')}">标签</a></li>
<li><a href="{:U('Resource/pics')}">图片库</a></li>
<li><a href="{:U('Resource/files')}">文件库</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">文章 <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="{:U('Post/text')}">文章</a></li>
<li><a href="{:U('Post/picture')}">图片</a></li>
<li><a href="{:U('Post/music')}">音乐</a></li>
<li><a href="{:U('Post/video')}">视频</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">微信 <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="{:U('Weixin/index')}">服务器</a></li>
<li><a href="{:U('Weixin/menu')}">菜单</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">配置 <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="{:U('Config/group?id=1')}">网站设置</a></li>
<li><a href="{:U('Config/index')}">配置列表</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="divider-vertical"></li>
<li><a href="javascript:;">{:get_admin_name()}</a></li>
<li class="divider-vertical"></li>
<li><a href="{:U('System/cleancache')}" class="ajax-get">清除缓存</a></li>
<li class="divider-vertical"></li>
<li><a href="{:U('System/logout')}">登出</a></li>
<li class="divider-vertical"></li>
<li><a href="/">网站</a></li>
</ul>
</div>
<!-- /.nav-collapse -->
</div>
</div>
<!-- /navbar-inner -->
</div>
~~~
![高亮效果](http://box.kancloud.cn/2015-07-05_55989d87ca32e.png)
高亮代码实现参考了OneThink的后台高亮。
先js获取当前url:
~~~
var url = window.location.pathname + window.location.search;
url = url.replace(/(\/(p)\/\d+)|(&p=\d+)|(\/(id)\/\d+)|(&id=\d+)|(\/(group)\/\d+)|(&group=\d+)/, "");
url = url.replace('.html', '');
~~~
然后admin.js里定义一个高亮菜单的函数:
~~~
function highlight_menu(url) {
$('.nav li').removeClass('active');
$('.nav .dropdown-menu>li>a[href*="'+url+'"]').parent().addClass('active').parents('.dropdown').addClass('active');
$('.nav-collapse .nav li a[href*="'+url+'"]').parent().addClass('active');
}
~~~
admin.js最后, 初始话时调用这个函数。
~~~
$(function(){
//导航子页面高亮选中
highlight_menu(url);
});
~~~
# 配置
在我遇到typecho 时,我就被它的简洁所打败了。它的后台也是如此。因此,在我开发OneBlog时,我就用OneThink的功能,然后移植typecho的后台风格,包括登录页之类的。
像这样:
![2015-07-05/5598a16156584](http://box.kancloud.cn/2015-07-05_5598a16156584.png)
配置我是直接移植OneThink的模板加控制器。前面大家前台也看到了,同样的初始化里,读取数据库配置,合并到配置数组里去。
后台的模板代码我就不帖了,和OneBlog里的差不多。只不过继承的父模板不一样。
主要说一下保存。
OneBlog 里的配置,我是copy OneThink里的 控制器方法。
在freelog里,我们不是写了api了吗?
我就把那个配置里的保存方法,给他整合到api里面了。其他和OneBlog一样,列表、编辑页读数据什么的。
用过OneThink的开发人员都清楚,OneThink里的配置保存有两种,一种是所有配置保存,一种是单独配置项的保存。
![全部配置](http://box.kancloud.cn/2015-07-05_5598a3333300d.png)
![某一项配置](http://box.kancloud.cn/2015-07-05_5598a356b256f.png)
![全部配置保存](http://box.kancloud.cn/2015-07-05_5598a3a283079.png)
全部配置保存的html里保存的是个多维数组,然后提交到 api.php/config/save方法里。
而单独配置的保存,我还是api的config,只不过提交时表单里带id,键名不是数组了,是配置项的对应键,如sort。
![2015-07-05/5598a42869472](http://box.kancloud.cn/2015-07-05_5598a42869472.png)
![2015-07-05/5598a4412b180](http://box.kancloud.cn/2015-07-05_5598a4412b180.png)
因此,api里,config不能像普通表的curd那么去做。
我们回去看api的EmptyController
![2015-07-05/5598a49a8d3bb](http://box.kancloud.cn/2015-07-05_5598a49a8d3bb.png)
先定义了其他处理的资源,config。
然后控制器里单独实现config方法:
~~~
public function config($name = 0){
$model = D('Config');
$result = true;
$data = array();
$code = 404;
$url = '';
switch ($this->_method){
case 'head':
break;
case 'option':
break;
case 'get': // 列出资源
if('list' == $name){
$data = $model->select();
}else{
$id = intval($name);
$data = $model->find($id);
}
if($model->getError() || $model->getDbError()){
$result = false;
}else{
$code = 200;
}
break;
case 'put':
if('save' == $name){
// 批量更新资源
$config = I('put.config');
if(empty($config)){
$result = false;
$data = '表单为空';
}else{
if($config && is_array($config)){
foreach ($config as $name => $value) {
$map = array('name' => $name);
$model->where($map)->setField('value', $value);
}
}
S('DB_CONFIG_DATA',null);
$code = 200;
}
}else{
$puts = $model->create(I('put.'));
if(false === $puts){
$result = false;
$data = $model->getError();
}else{
$id = $puts['id'];
if($find = $model->find($id)){
$result = false !== $model->save($puts);
$code = $result? 200: 404;
}else{
$result = false;
$data = "record not found";
$code = 412;
}
}
}
break;
case 'post': // 新增资源
$posts = $model->create();
if(false == $posts){
$data = $model->getError();
$result = false;
}else{
$id = $model->add();
if(!$id){
$result = false;
}else{
$code = 201;
$data = $id;
$url = '/admin.php/Config/index';
}
}
break;
case 'delete':// 删除资源
// parse_str(file_get_contents('php://input'), $_DELETE);
// slog($_DELETE);
$id = array_unique((array)I('get.id',0));
slog($id);
if ( empty($id) ) {
$code = 404;
$data = '请选择要操作的数据';
}else{
$code = 200;
$map = array('id' => array('in', $id) );
if(M('Config')->where($map)->delete()){
S('DB_CONFIG_DATA',null);
//记录行为
$url = '/admin.php/Config/index';
$data = '删除成功';
} else {
$code = 412;
$result = false;
$data = '删除失败!';
}
}
break;
}
if($result){
$this->success($data, $code, $url);
}else{
$this->error($data, $code, $url);
}
}
~~~
先看更新:
~~~
if('save' == $name){
// 批量更新资源
$config = I('put.config');
if(empty($config)){
$result = false;
$data = '表单为空';
}else{
if($config && is_array($config)){
foreach ($config as $name => $value) {
$map = array('name' => $name);
$model->where($map)->setField('value', $value);
}
}
$code = 200;
}
}else{
$puts = $model->create(I('put.'));
if(false === $puts){
$result = false;
$data = $model->getError();
}else{
$id = $puts['id'];
if($find = $model->find($id)){
$result = false !== $model->save($puts);
$code = $result? 200: 404;
}else{
$result = false;
$data = "record not found";
$code = 412;
}
}
S('DB_CONFIG_DATA',null);
}
~~~
通过$name 是否为save,区分是批量更新还是单个更新。单个更新和之前所有数据表资源的更新一样。而批量更新,获取的是 I('put.config') 多维数组,并且通过遍历去更新每一个:
~~~
foreach ($config as $name => $value) {
$map = array('name' => $name);
$model->where($map)->setField('value', $value);
}
~~~
最后清除一下缓存。
# 资源
资源导航包括了文件、图片和标签。
我以文件为例,其他的都差不多,只是模板里个别字段不一样。
![2015-07-05/55994a872cfab](http://box.kancloud.cn/2015-07-05_55994a872cfab.png)
模板resource/files.html代码:
~~~
<extend name="Public/base" />
<block name="body">
<div class="main-title location">
<h2>文件</h2>
</div>
<div class="data-table table-striped"></div>
<div class="typecho-table-wrap">
<table class="table table-hover" id="del_table">
<thead>
<tr>
<th>ID</th>
<th>原始名</th>
<th>后缀</th>
<th>路径</th>
<th>大小</th>
<th>上传时间</th>
</tr>
</thead>
<tbody>
<notempty name="list">
<volist name="list" id="file">
<tr>
<td>{$file.id}</td>
<td>{$file.savename}</td>
<td>{$file.ext}</td>
<td><a href="{$file.path}" target="_blank">{$file.path}</a></td>
<td>{$file.size|format_bytes}</td>
<td>{$file.create_time|date="Y-m-d h:i:s",###}</td>
</tr>
</volist>
<else/>
<td colspan="6" class="text-center"> aOh! 暂时还没有内容! </td>
</notempty>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<div class="pull-right">
{$_page}
</div>
</div>
</block>
~~~
处理了大小的格式化显示,和创建时间格式化。
控制器代码也简单:
~~~
<?php
namespace Admin\Controller;
use Think\Controller;
class ResourceController extends CommonController {
//标签
public function tags(){
/* 查询条件初始化 */
$map = array('status' => 1);
if(isset($_GET['title'])){
$map['title'] = array('like', '%'.(string)I('title').'%');
}
$this->meta_title = '标签';
$this->_list(array('source' => 'Tags', 'map' => $map, 'order' => '`id`'));
}
//图片
public function pics(){
/* 查询条件初始化 */
$map = array('status' => 1);
$this->meta_title = '图片';
$this->_list(array('source' => 'Picture', 'map' => $map, 'order' => '`id`'));
}
public function files(){
/* 查询条件初始化 */
$map = array('status' => 1);
$this->meta_title = '文件';
$this->_list(array('source' => 'File', 'map' => $map, 'order' => '`id`'));
}
}
~~~
图片和标签只不过换了资源名,并且标签加了title搜索。
这一切复用了common控制器里的_list方法。
因为文件、图片、标签都是用户产生,本来后台就不应该有删除的行为。因为删除了文章里关联的数据就会有问题。
显示出来是为了方便审查,万一用户上传了黄色图片和音频怎么办。
读取时按照status=1处理的。到时候加删除也简单,通过删除链接和ajax类更新该记录为status=0就好了。
# 文章
文章,为了方便管理,将四种类型通过菜单区分管理列表。
![2015-07-05/55994d96cf877](http://box.kancloud.cn/2015-07-05_55994d96cf877.png)
为了实现动态菜单,
控制器里方法也是通过空操纵实现:
~~~
<?php
namespace Admin\Controller;
use Think\Controller;
class PostController extends CommonController {
public function _empty($action = 'text'){
if(!in_array($action, array('text', 'picture', 'video', 'music')))
$this->error('错误的文章类型');
/* 查询条件初始化 */
$map = array();
$map['type'] = $action;
if($search = I('get.title', '', 'trim')){
$map['_string'] = "`title` LIKE '%{$search}%' OR `description` LIKE '%{$search}' OR FIND_IN_SET('{$search}', tags)";
}
$this->meta_title = '文章管理';
$this->_list(array('source' => 'Post', 'map' => $map, 'order' => '`id`', 'tpl'=>strtolower($action)));
}
}
~~~
不过比之前标签搜索,提供多了一些字段,如描述、和标签。模板也是动态的。如文章模板。
~~~
<extend name="Public/base" />
<block name="body">
<div class="main-title location">
<h2>文章</h2>
</div>
<div class="data-table table-striped">
<div class="clearfix">
<!-- 高级搜索 -->
<div class="search-form pull-right clearfix form-inline">
<div class="input-group mb10">
<form action="{:U()}" method="GET">
<input type="text" name="title" class="form-control text-s" value="{:I('title')}" placeholder="请输入标签名称">
<span class="input-group-btn">
<button class="btn btn-default"><span class="glyphicon glyphicon-search" aria-hidden="true"></span></button>
</span>
</form>
</div><!-- /input-group -->
</div>
</div>
<div class="typecho-table-wrap">
<table class="table table-hover" id="del_table">
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>描述</th>
<th>作者</th>
<th>标签</th>
<th>浏览数</th>
<th>最后更新时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<notempty name="list">
<volist name="list" id="post">
<tr>
<td>{$post.id}</td>
<td>{$post.title}</td>
<td>{$post.description}</td>
<td>{$post.author}</td>
<td>{$post.tags}</td>
<td>{$post.views}</td>
<td>{$post.update_at}</td>
<td><a href="/api.php/post?id={$post.id}" class="ajax-delete confirm">删除</a></td>
</tr>
</volist>
<else/>
<td colspan="6" class="text-center"> aOh! 暂时还没有内容! </td>
</notempty>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<div class="pull-right">
{$_page}
</div>
</div>
</block>
~~~
这里为了以后扩展方便预留了。每个类别文章没用一个模板,是为了以后实现不同的预览和编辑着想,先只显示基础数据吧。
至于搜索,一个get表单搞定:
~~~
<form action="{:U()}" method="GET">
<input type="text" name="title" class="form-control text-s" value="{:I('title')}" placeholder="请输入标签名称">
<span class="input-group-btn">
<button class="btn btn-default"><span class="glyphicon glyphicon-search" aria-hidden="true"></span></button>
</span>
</form>
~~~
至于删除,和前台一样,交给api、ajax-delete。
`<a href="/api.php/post?id={$post.id}" class="ajax-delete confirm">删除</a>`
# 微信
本来微信想做很多功能,实现像公众平台一样的功能(多媒体管理,消息查看)。可是后来尝试发现,个人开发者未认证用户,没那些高级接口的权限。
所以干脆,只做了2个功能页:微信服务器ip列表,和微信平台(iframe实现,要怎么管理自己登录去吧)。
## 服务器ip
![效果](http://box.kancloud.cn/2015-07-05_5599509c92098.png)
直接看控制器 :
~~~
<?php
namespace Admin\Controller;
use Think\Controller;
use Com\Wechat;
use Com\WechatAuth;
class WeixinController extends CommonController {
//初始化方法
protected function _initialize() {
if(!C('WEIXIN.APPID')){
$this->error('请先配置好微信');
}
}
//首页
public function index(){
$wechatauth = new WechatAuth(C('WEIXIN.APPID'), C('WEIXIN.SECRET'), C(''));
$access_token = $wechatauth->getAccessToken();
$result = $wechatauth->getServerIp($access_token);
if(isset($result['errcode']))
$this->error($result['errmsg']);
slog($result);
$this->assign('ip', $result['ip_list']);
$this->display();
}
//菜单
public function menu(){
$this->display();
}
}
~~~
weixin/index.html 代码:
~~~
<extend name="Public/base" />
<block name="body">
<div class="col-md-4"></div>
<div class="col-md-4">
<table class="table table-hover">
<thead><th>微信服务器IP:</th></thead>
<tbody>
<volist name="ip" id="vo">
<tr><td>{$vo}</td></tr>
</volist>
</tbody>
</table>
</div>
<div class="col-md-4"></div>
</block>
~~~
我也是盲人摸象,摸索出WechatAuth类的使用。并且,自己扩展了一个getServerIp的方法:
~~~
public function getServerIp($token){
return $this->api('getcallbackip', array('access_token'=>$token));
}
~~~
后面微信开发里会细讲。
## 菜单
没啥技术含量,但是思路不错:
~~~
<extend name="Public/base" />
<block name="body">
<iframe width="100%" height="700" src="https://mp.weixin.qq.com/"></iframe>
</block>
~~~
- 序
- 前言
- 内容简介
- 目录
- 基础知识
- 起步
- 控制器
- 模型
- 模板
- 命名空间
- 进阶知识
- 路由
- 配置
- 缓存
- 权限
- 扩展
- 国际化
- 安全
- 单元测试
- 拿来主义
- 调试方法
- 调试的步骤
- 调试工具
- 显示trace信息
- 开启调试和关闭调试的区别
- netbeans+xdebug
- Socketlog
- PHP常见错误
- 小黄鸭调试法,每个程序员都要知道的
- 应用场景
- 第三方登录
- 图片处理
- 博客
- SAE
- REST实践
- Cli
- ajax分页
- barcode条形码
- excel
- 发邮件
- 汉字转全拼和首字母,支持带声调
- 中文分词
- 浏览器useragent解析
- freelog项目实战
- 需求分析
- 数据库设计
- 编码实践
- 前端实现
- rest接口
- 文章发布
- 文件上传
- 视频播放
- 音乐播放
- 图片幻灯片展示
- 注册和登录
- 个人资料更新
- 第三方登录的使用
- 后台
- 微信的开发
- 首页及个人主页
- 列表
- 归档
- 搜索
- 分页
- 总结经验
- 自我提升
- 进行小项目的锻炼
- 对现有轮子的重构和移植
- 写技术博客
- 制作视频教程
- 学习PHP的知识和新特性
- 和同行直接沟通、交流
- 学好英语,走向国际
- 如何参与
- 浏览官网和极思维还有看云
- 回答ThinkPHP新手的问题
- 尝试发现ThinkPHP的bug,告诉官方人员或者push request
- 开发能提高效率的ThinkPHP工具
- 尝试翻译官方文档
- 帮新手入门
- 创造基于ThinkPHP的产品,进行连带推广
- 展望未来
- OneThink
- ThinkPHP4
- 附录