发布日期: 2023年12月15日
ThinkPHP 6.0简介与新特性
ThinkPHP是一款国内领先的PHP开源框架,6.0版本在之前版本的基础上进行了全面重构,引入了更多现代化特性:
- 完全支持PHP 7.1+特性
- 采用更加严谨的PSR规范
- 引入中间件机制替代原有的行为扩展
- 依赖注入和支持更加完善
- 改进了数据库查询和模型功能
- 提供了更强大的命令行工具
本教程将通过开发一个完整的企业级内容管理系统(CMS),深入讲解ThinkPHP 6.0的核心功能和最佳实践。
环境搭建与项目初始化
环境要求
PHP >= 7.1.0 PDO PHP Extension MBstring PHP Extension JSON PHP Extension Composer
使用Composer创建项目
# 创建ThinkPHP项目 composer create-project topthink/think tp-cms # 进入项目目录 cd tp-cms # 启动内置服务器 php think run
基础配置修改
修改config目录下的配置文件:
// config/database.php 数据库配置 return [ 'default' => 'mysql', 'connections' => [ 'mysql' => [ 'type' => 'mysql', 'hostname' => '127.0.0.1', 'database' => 'tp_cms', 'username' => 'root', 'password' => 'password', 'charset' => 'utf8mb4', 'prefix' => 'cms_', ], ], ]; // config/app.php 应用配置 return [ 'app_debug' => true, 'default_lang' => 'zh-cn', 'default_timezone' => 'Asia/Shanghai', 'url_html_suffix' => 'html', ];
系统架构设计
目录结构规划
tp-cms/ ├── app/ # 应用目录 │ ├── controller/ # 控制器层 │ │ ├── admin/ # 后台控制器 │ │ └── home/ # 前台控制器 │ ├── model/ # 模型层 │ │ ├── admin/ # 后台模型 │ │ └── home/ # 前台模型 │ ├── view/ # 视图层 │ │ ├── admin/ # 后台模板 │ │ └── home/ # 前台模板 │ └── middleware/ # 中间件 ├── config/ # 配置目录 ├── public/ # web入口目录 ├── route/ # 路由定义 ├── database/ # 数据库文件 └── extend/ # 扩展类库
多应用模式配置
ThinkPHP 6.0支持多应用模式,我们需要创建后台管理应用:
# 安装多应用扩展 composer require topthink/think-multi-app # 创建admin应用目录结构 php think build admin
路由规划
// route/app.php use thinkfacadeRoute; // 前台路由 Route::get('/', 'home/Index/index'); Route::get('article/:id', 'home/Article/detail'); Route::get('category/:id', 'home/Category/index'); // 后台路由组 Route::group('admin', function() { Route::get('/', 'admin/Index/index'); Route::get('login', 'admin/Login/index'); Route::post('login', 'admin/Login/check'); })->prefix('admin/');
数据库设计与模型实现
数据库表设计
// 用户表 CREATE TABLE `cms_user` ( `id` int(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名', `password` varchar(255) NOT NULL DEFAULT '' COMMENT '密码', `email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态', `create_time` int(11) NOT NULL DEFAULT '0', `update_time` int(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; // 文章表 CREATE TABLE `cms_article` ( `id` int(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, `category_id` int(11) NOT NULL DEFAULT '0' COMMENT '分类ID', `title` varchar(255) NOT NULL DEFAULT '' COMMENT '标题', `content` text COMMENT '内容', `author` varchar(50) NOT NULL DEFAULT '' COMMENT '作者', `keywords` varchar(255) NOT NULL DEFAULT '' COMMENT '关键词', `description` varchar(500) NOT NULL DEFAULT '' COMMENT '描述', `thumb` varchar(255) NOT NULL DEFAULT '' COMMENT '缩略图', `views` int(11) NOT NULL DEFAULT '0' COMMENT '浏览量', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态', `create_time` int(11) NOT NULL DEFAULT '0', `update_time` int(11) NOT NULL DEFAULT '0', `publish_time` int(11) NOT NULL DEFAULT '0' COMMENT '发布时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章表'; // 分类表 CREATE TABLE `cms_category` ( `id` int(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, `name` varchar(50) NOT NULL DEFAULT '' COMMENT '分类名称', `pid` int(11) NOT NULL DEFAULT '0' COMMENT '父级ID', `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态', `create_time` int(11) NOT NULL DEFAULT '0', `update_time` int(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分类表';
模型实现
// app/model/User.php namespace appmodel; use thinkModel; use thinkmodelconcernSoftDelete; class User extends Model { use SoftDelete; protected $name = 'user'; protected $autoWriteTimestamp = true; protected $deleteTime = 'delete_time'; // 密码自动加密 public function setPasswordAttr($value) { return password_hash($value, PASSWORD_DEFAULT); } // 状态获取器 public function getStatusTextAttr($value, $data) { $status = [0 => '禁用', 1 => '正常']; return $status[$data['status']]; } } // app/model/Article.php namespace appmodel; use thinkModel; class Article extends Model { protected $name = 'article'; protected $autoWriteTimestamp = true; // 分类关联 public function category() { return $this->belongsTo(Category::class); } // 发布状态获取器 public function getStatusTextAttr($value, $data) { $status = [0 => '草稿', 1 => '已发布']; return $status[$data['status']]; } }
使用迁移和填充
// 创建迁移文件 php think migrate:create CreateCmsTables // database/migrations/20231215000000_create_cms_tables.php use thinkmigrationMigrator; class CreateCmsTables extends Migrator { public function change() { $table = $this->table('user', ['comment' => '用户表', 'engine' => 'InnoDB']); $table->addColumn('username', 'string', ['limit' => 50, 'default' => '', 'comment' => '用户名']) ->addColumn('password', 'string', ['limit' => 255, 'default' => '', 'comment' => '密码']) ->addTimestamps() ->create(); } }
后台管理系统实现
后台登录实现
// app/admin/controller/Login.php namespace appadmincontroller; use thinkfacadeView; use appmodelUser; class Login { public function index() { return View::fetch(); } public function check() { $username = input('post.username'); $password = input('post.password'); $captcha = input('post.captcha'); // 验证码验证 if (!captcha_check($captcha)) { return json(['code' => 0, 'msg' => '验证码错误']); } // 用户验证 $user = User::where('username', $username)->find(); if (!$user || !password_verify($password, $user->password)) { return json(['code' => 0, 'msg' => '用户名或密码错误']); } // 登录成功 session('admin_user', $user->toArray()); return json(['code' => 1, 'msg' => '登录成功', 'url' => url('admin/index')]); } }
后台中间件实现
// app/admin/middleware/Auth.php namespace appadminmiddleware; use thinkfacadeSession; class Auth { public function handle($request, Closure $next) { // 排除登录页面 if (!preg_match('/admin/login/', $request->url())) { if (!Session::has('admin_user')) { return redirect('/admin/login'); } } return $next($request); } } // 注册中间件 app/admin/middleware.php return [ appadminmiddlewareAuth::class ];
文章管理CRUD实现
// app/admin/controller/Article.php namespace appadmincontroller; use thinkfacadeView; use appmodelArticle; use appmodelCategory; class Article extends Base { public function index() { $page = input('page', 1); $limit = input('limit', 15); $keyword = input('keyword', ''); $map = []; if ($keyword) { $map[] = ['title', 'like', "%{$keyword}%"]; } $list = Article::with('category') ->where($map) ->order('id', 'desc') ->paginate(['page' => $page, 'list_rows' => $limit]); return View::fetch('index', [ 'list' => $list, 'page' => $list->render(), 'keyword' => $keyword ]); } public function add() { if (request()->isPost()) { $data = input('post.'); $data['publish_time'] = strtotime($data['publish_time']); $result = Article::create($data); if ($result) { return json(['code' => 1, 'msg' => '添加成功', 'url' => url('admin/article/index')]); } else { return json(['code' => 0, 'msg' => '添加失败']); } } $categories = Category::where('status', 1)->select(); return View::fetch('add', ['categories' => $categories]); } public function edit($id) { $article = Article::find($id); if (!$article) { $this->error('文章不存在'); } if (request()->isPost()) { $data = input('post.'); $data['publish_time'] = strtotime($data['publish_time']); if ($article->save($data)) { return json(['code' => 1, 'msg' => '更新成功', 'url' => url('admin/article/index')]); } else { return json(['code' => 0, 'msg' => '更新失败']); } } $categories = Category::where('status', 1)->select(); return View::fetch('edit', [ 'article' => $article, 'categories' => $categories ]); } public function delete($id) { $article = Article::find($id); if (!$article) { return json(['code' => 0, 'msg' => '文章不存在']); } if ($article->delete()) { return json(['code' => 1, 'msg' => '删除成功']); } else { return json(['code' => 0, 'msg' => '删除失败']); } } }
前端展示功能实现
首页控制器实现
// app/home/controller/Index.php namespace apphomecontroller; use thinkfacadeView; use appmodelArticle; use appmodelCategory; class Index { public function index() { // 获取推荐文章 $featuredArticles = Article::where('status', 1) ->order('views', 'desc') ->limit(5) ->select(); // 获取最新文章 $latestArticles = Article::with('category') ->where('status', 1) ->order('publish_time', 'desc') ->paginate(10); return View::fetch('index', [ 'featuredArticles' => $featuredArticles, 'latestArticles' => $latestArticles, 'page' => $latestArticles->render() ]); } }
文章详情页实现
// app/home/controller/Article.php namespace apphomecontroller; use thinkfacadeView; use appmodelArticle; class Article { public function detail($id) { $article = Article::with('category')->find($id); if (!$article || $article->status != 1) { abort(404, '文章不存在'); } // 增加浏览量 $article->views = ['inc', 1]; $article->save(); // 获取相关文章 $relatedArticles = Article::where('category_id', $article->category_id) ->where('id', '', $id) ->where('status', 1) ->order('publish_time', 'desc') ->limit(5) ->select(); return View::fetch('detail', [ 'article' => $article, 'relatedArticles' => $relatedArticles ]); } }
分类页面实现
// app/home/controller/Category.php namespace apphomecontroller; use thinkfacadeView; use appmodelArticle; use appmodelCategory; class Category { public function index($id) { $category = Category::find($id); if (!$category) { abort(404, '分类不存在'); } // 获取分类下的文章 $articles = Article::where('category_id', $id) ->where('status', 1) ->order('publish_time', 'desc') ->paginate(15); return View::fetch('index', [ 'category' => $category, 'articles' => $articles, 'page' => $articles->render() ]); } }
安全防护与性能优化
安全措施实现
// 表单令牌防护 // 在模板中添加 <input type="hidden" name="__token__" value="{:token()}"> // 在控制器中验证 $check = $request->checkToken('__token__'); if (false === $check) { throw new ValidateException('令牌验证错误'); } // XSS过滤 public function add() { $data = input('post.'); // 过滤HTML标签 $data['content'] = clean($data['content']); // 或者使用HTMLPurifier等专业库 } // SQL注入防护 // 使用查询构造器自动参数绑定 $list = Article::where('title', 'like', "%{$keyword}%")->select(); // 或者使用预处理 $list = Db::query('SELECT * FROM cms_article WHERE title LIKE ?', ["%{$keyword}%"]);
性能优化策略
// 使用缓存 public function index() { $cacheKey = 'home_index_data'; $data = cache($cacheKey); if (!$data) { $data = [ 'featuredArticles' => Article::where('status', 1) ->order('views', 'desc') ->limit(5) ->select(), 'latestArticles' => Article::with('category') ->where('status', 1) ->order('publish_time', 'desc') ->paginate(10) ]; // 缓存10分钟 cache($cacheKey, $data, 600); } return View::fetch('index', $data); } // 数据库索引优化 // 为常用查询字段添加索引 ALTER TABLE `cms_article` ADD INDEX `idx_category_status` (`category_id`, `status`); ALTER TABLE `cms_article` ADD INDEX `idx_publish_time` (`publish_time`); // 使用延迟关联优化分页 public function index() { $page = input('page', 1); $limit = input('limit', 15); // 先获取主键ID $ids = Article::where('status', 1) ->order('publish_time', 'desc') ->page($page, $limit) ->column('id'); // 再获取完整数据 $list = Article::with('category') ->whereIn('id', $ids) ->order('publish_time', 'desc') ->select(); }
项目部署与总结
生产环境部署
# 关闭调试模式 // config/app.php 'app_debug' => false, 'app_trace' => false, # 优化路由缓存 php think optimize:route # 生成配置缓存 php think optimize:config # 类库映射优化 php think optimize:autoload # 使用Composer优化自动加载 composer dump-autoload --optimize # 设置目录权限 chmod -R 755 public/uploads chmod -R 755 runtime
Nginx配置示例
server { listen 80; server_name example.com; root /path/to/tp-cms/public; index index.php index.html; location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; break; } } location ~ .php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /.ht { deny all; } }
项目总结
通过本教程,我们完成了一个基于ThinkPHP 6.0的企业级内容管理系统,涵盖了:
- ThinkPHP 6.0框架的核心特性使用
- 多应用模式的配置与开发
- 数据库设计与模型关系处理
- 完整的后台管理系统实现
- 响应式前端展示页面
- 安全防护与性能优化策略
- 生产环境部署配置
这个CMS系统可以作为企业网站、博客系统、新闻发布系统的基础框架,根据实际需求进行功能扩展和定制开发。
进一步学习建议
- 学习使用ThinkPHP的门面模式和依赖注入
- 掌握更多中间件的应用场景
- 深入了解事件系统和钩子行为
- 学习API接口开发与身份认证
- 掌握队列处理和定时任务
- 学习Docker容器化部署