织梦的核心魅力在于其“标签化”的模板调用和“模型/频道管理思想,我们将围绕这两个核心点,来设计如何用 ThinkPHP 实现一个“类织梦”的系统。
核心思想对比
| 特性 | 织梦 | ThinkPHP 实现方案 |
|---|---|---|
| 核心理念 | 标签驱动,模板分离 | MVC (Model-View-Controller),但可以封装成标签化 |
| 模板调用 | {dede:arclist} 等专用标签 |
自定义模板标签引擎,封装 ThinkPHP 的查询构建器 |
| 后台逻辑 | PHP 文件直接处理请求 | 控制器 + 路由,逻辑更清晰 |
| 扩展性 | 修改核心文件或使用插件 | 基于 composer 的依赖和规范的扩展开发 |
第一步:项目架构与数据库设计
ThinkPHP 项目初始化
使用 Composer 创建一个 ThinkPHP 项目:
composer create-project topthink/think tp_dedemv
进入项目目录 tp_dedemv。
数据库设计 (仿织梦的核心)
织梦的数据库设计非常经典,我们参考它来设计。
核心表:
-
dede_arctype(栏目表)id: 主键typename: 栏目名称typedir: 栏目目录 (用于生成URL)reid: 上级栏目ID (用于树形结构)isdefault: 是否是默认栏目 (首页)ishidden: 是否隐藏content: 栏目描述model: 关联的内容模型ID (比如1代表文章,2代表图集)addtime: 创建时间
-
dede_archives(文档主表)id: 主键typeid: 所属栏目ID (关联arctype)title: 文章标题litpic: 缩略图pubdate: 发布时间click: 点击量iscommend: 是否推荐arcrank: 审核状态 (0为已审核)writer: 作者source: 来源
-
dede_addonarticle(文章附加表)aid: 关联archives表的idbody: 文章正文 (使用 TEXT 或 LONGTEXT 类型)
-
dede_arcmulti(多图/软件等附加表示例)aid: 关联archives表的idbody: 内容字段 (可以是多图信息、软件信息等)
设计思路:
archives表是所有内容类型的“主表”,存放公共字段。- 通过
model字段属于哪个模型(文章、图集、下载等)。 - 每个模型对应一个“附加表”,存放该模型特有的字段(如文章的
body)。 - 这种设计使得系统可以灵活地增加新的内容类型,而无需修改核心表结构。
第二步:后台管理系统实现
录入和管理的地方,遵循标准的 MVC 模式。
模型层
创建对应的模型来操作数据库。
-
app/model/Arctype.php(栏目模型)namespace app\model; use think\Model; class Arctype extends Model { // 设置数据表名 protected $name = 'arctype'; // 设置字段信息 protected $schema = [ 'id' => 'int', 'typename'=> 'string', // ... 其他字段 ]; } -
app/model/Archives.php(文档主表模型)namespace app\model; use think\Model; class Archives extends Model { protected $name = 'archives'; // 定义关联,一个文档属于一个栏目 public function arctype() { return $this->belongsTo(Arctype::class, 'typeid'); } }
控制器层
创建控制器来处理后台的请求逻辑。
-
app/controller/admin/Arctype.php(栏目管理控制器)namespace app\controller\admin; use app\BaseController; use app\model\Arctype; use think\facade\View; class Arctype extends BaseController { public function index() { $list = Arctype::order('id', 'asc')->select(); View::assign('list', $list); return View::fetch(); } public function add() { if ($this->request->isPost()) { $data = $this->request->post(); if (Arctype::create($data)) { return json(['code' => 1, 'msg' => '添加成功']); } return json(['code' => 0, 'msg' => '添加失败']); } // 获取所有栏目作为上级选项 $types = Arctype::where('reid', 0)->select(); View::assign('types', $types); return View::fetch(); } // ... edit, del 等方法 }
视图层
后台视图使用 ThinkPHP 的模板引擎,可以和 Bootstrap、Layui 等前端框架结合,快速搭建美观的界面。
app/view/admin/arctype/index.html<table class="table table-striped"> <thead> <tr> <th>ID</th> <th>栏目名称</th> <th>目录</th> <th>操作</th> </tr> </thead> <tbody> {volist name="list" id="vo"} <tr> <td>{$vo.id}</td> <td>{$vo.typename}</td> <td>{$vo.typedir}</td> <td> <a href="{:url('add', ['reid' => $vo.id])}" class="btn btn-xs btn-primary">添加子栏目</a> <a href="{:url('edit', ['id' => $vo.id])}" class="btn btn-xs btn-info">编辑</a> <a href="{:url('del', ['id' => $vo.id])}" class="btn btn-xs btn-danger" onclick="return confirm('确定删除吗?')">删除</a> </td> </tr> {/volist} </tbody> </table>
第三步:前台实现与标签化封装 (核心难点)
前台要实现织梦那样的 {dede:xxx} 标签调用,我们需要在 ThinkPHP 中“模拟”这个行为。
创建自定义标签引擎
我们可以创建一个服务类来解析和执行这些“标签”。
-
app/service/TagService.phpnamespace app\service; use think\facade\Db; use think\facade\View; class TagService { /** * 解析并执行标签 * @param string $tagName 标签名,如 'arclist' * @param array $attrs 标签属性,如 ['row' => '10', 'typeid' => '1'] * @param string $content 标签内容 (用于 {dede:loop} 等) * @return string */ public function parse(string $tagName, array $attrs, string $content = ''): string { switch ($tagName) { case 'arclist': return $this->arclist($attrs); case 'channel': return $this->channel($attrs); case 'field': return $this->field($attrs); // ... 更多标签 default: return ''; // 未知标签返回空 } } /** * 实现 {dede:arclist} 标签 */ protected function arclist(array $attrs): string { $query = Db::name('archives') ->alias('a') ->join('arctype t', 'a.typeid = t.id') ->where('a.arcrank', 0); // 只调用已审核的文章 if (isset($attrs['typeid'])) { $query->where('a.typeid', $attrs['typeid']); } if (isset($attrs['row'])) { $query->limit($attrs['row']); } if (isset($attrs['limit'])) { $limit = explode(',', $attrs['limit']); $query->limit($limit[0], $limit[1] ?? 0); } // ... 更多属性处理 $list = $query->order('a.id', 'desc')->select()->toArray(); // 模拟织梦的字段命名 foreach ($list as &$item) { $item['title'] = $item['title']; $item['litpic'] = $item['litpic']; $item['description'] = mb_substr(strip_tags($this->getBodyByAid($item['id'])), 0, 100) . '...'; $item['arcurl'] = $this->getArcUrl($item['id']); } // 将数据传递给模板 View::assign('list', $list); // 返回渲染后的HTML片段 return View::fetch('public/arclist_item'); } // ... channel, field 等标签的实现 /** * 根据文档ID获取正文 (从附加表) */ protected function getBodyByAid(int $aid): string { // 这里简化处理,实际应根据 model 字段查询不同附加表 return Db::name('addonarticle')->where('aid', $aid)->value('body') ?? ''; } /** * 生成文档URL (伪静态) */ protected function getArcUrl(int $aid): string { // 实际项目中应从缓存或数据库获取栏目信息来构建URL // 这里简化为 /view/{id}.html return "/view/{$aid}.html"; } }
在模板中使用标签
我们需要一个机制,让模板引擎在编译时能识别 {dede:xxx} 并调用我们的 TagService。
ThinkPHP 的模板引擎支持自定义标签库,我们可以注册一个自定义的标签库。
-
在
app.php或应用配置中注册标签库:// config/app.php 'taglib' => [ 'app\taglib\Dede' => '', // 注册我们的标签库 ], -
创建标签库类:
-
app/taglib/Dede.phpnamespace app\taglib; use app\service\TagService; use think\template\TagLib; class Dede extends TagLib { // 标签定义 protected $tags = [ // 标签名 => 属性列表 'arclist' => ['attr' => 'typeid,row,limit', 'close' => 0], // close=0 表示是单标签 'channel' => ['attr' => 'typeid,row', 'close' => 0], 'field' => ['attr' => 'name', 'close' => 0], ]; public function tagArclist($tag, $content) { $attrs = $tag['attr']; // 调用服务层 $service = new TagService(); return $service->parse('arclist', $attrs); } public function tagChannel($tag, $content) { $attrs = $tag['attr']; $service = new TagService(); return $service->parse('channel', $attrs); } public function tagField($tag, $content) { // field 标签通常用于循环内部,获取当前循环项的某个字段 // 实现起来稍微复杂,需要配合循环标签 // 这里简化处理 $name = $tag['attr']['name']; // 假设我们有一个变量在循环中,如 $vo return "<?= isset(\$vo['{$name}']) ? \$vo['{$name}'] : ''; ?>"; } }
-
-
前台模板文件
index.html:<!DOCTYPE html> <html> <head> <title>{dede:field name='typename'/} - {:$site_name}</title> </head> <body> <h1>{dede:field name='typename'/}</h1> <h2>最新文章列表</h2> {dede:arclist row='5' typeid='1'} <li> <a href="[field name='arcurl'/]">[field name='title'/]</a> <p>[field name='description'/]</p> </li> {/dede:arclist} <h2>栏目列表</h2> {dede:channel row='8'} <li><a href="[field name='typedir'/]/">[field name='typename'/]</a></li> {/dede:channel} </body> </html>
展示
-
配置路由
route/app.php:use think\facade\Route; // 首页 Route::get('/', 'Index/index'); // 文章详情页,使用路由变量绑定模型 Route::get('view/:id', 'Index/detail')->pattern(['id' => '\d+']); -
创建前台控制器
app/controller/Index.php:namespace app\controller; use app\BaseController; use app\model\Archives; use app\model\Arctype; use think\facade\View; class Index extends BaseController { public function index() { // 获取当前栏目ID,可以从路由或参数中获取 $typeid = $this->request->param('typeid', 1); // 获取栏目信息 $type = Arctype::find($typeid); View::assign('typename', $type->typename); // 模板渲染和标签解析由框架自动完成 return View::fetch(); } public function detail($id) { $archives = Archives::with(['arctype'])->find($id); if (!$archives || $archives->arcrank != 0) { // 文章不存在或未审核 abort(404, '文章不存在或未通过审核'); } View::assign('archives', $archives); return View::fetch('detail'); } }
总结与进阶
通过以上步骤,我们就用 ThinkPHP 搭建了一个具有“织梦风格”的 CMS 框架。
优点:
- 逻辑清晰:后台遵循 MVC,代码易于维护和扩展。
- 安全稳定:ThinkPHP 提供了安全防护和稳定的底层。
- 灵活强大:可以轻松利用 ThinkPHP 的生态(如验证器、中间件、事件等)。
- 标签化友好:通过自定义标签库,完美复刻了织梦的模板调用方式,对前端和内容编辑人员友好。
进阶方向:
- 模型管理后台:仿照织梦的“模型管理”,做一个可视化的后台,让用户可以动态创建新的内容模型(如“产品”、“招聘”等),并自动生成数据表。
- 富文本编辑器集成:集成如 UEditor、TinyMCE 等富文本编辑器,实现图文混排。
- 缓存机制:对栏目列表、首页内容等使用缓存,大幅提升访问速度。
- 伪静态与路由:完善 URL 生成规则,实现更灵活的伪静态。
- 会员系统:开发会员中心、评论、收藏等互动功能。
- 插件/钩子系统:开发一个插件机制,方便扩展功能。
这个项目是一个很好的练手和深入理解 ThinkPHP 全家桶的机会,祝你开发顺利!
