首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

DedeCMS (織夢) 核心架構深度解析:模板引擎、標簽解析、靜態生成、緩存機制全流程還原

     編輯:PHP 相關知識

導讀:DedeCMS 作為國產 CMS 典範,其「模板標簽 + 靜態生成」架構極具特色。本文從入口 index.php 切入,深度還原 PartView、DedeTagParse、MakeOneTag 核心類協作流程,揭秘模板編譯、標簽解析、緩存機制、HTML 生成全鏈路,附帶目錄結構圖譜。

整體架構圖譜


請求入口 (index.php)
    │
    ├─ 加載核心配置 (common.inc.php) → 定義常量、載入 DB、函數庫
    │
    ├─ 判斷 upcache 參數 → 是 → 執行靜態化流程
    │       │
    │       ├─ 實例化 PartView
    │       │       │
    │       │       ├─ SetTemplet() → DedeTagParse::LoadTemplate()
    │       │       │       │
    │       │       │       ├─ 讀取模板文件內容
    │       │       │       ├─ 檢查緩存 (tplcache/) → 命中 → 反序列化標簽樹
    │       │       │       └─ 未命中 → ParseTemplate() 解析標簽 → 序列化緩存
    │       │       │
    │       │       └─ ParseTemplet() → MakeOneTag() 綁定數據
    │       │               │
    │       │               ├─ 掃描 taglib/ 目錄獲取所有標簽類
    │       │               ├─ 遍歷標簽樹 (CTags)
    │       │               ├─ field 標簽 → 直接賦值 Fields 陣列
    │       │               ├─ 業務標簽 (arclist/channel 等) → include 標簽文件 → 執行函數 → 返回 HTML
    │       │               └─ 將結果寫入標簽對象 TagValue
    │       │
    │       └─ SaveToHtml() → DedeTagParse::SaveTo() → 替換標簽位置輸出最終 HTML → 寫入 index.html
    │
    └─ 否 → 301 重定向到 index.html (靜態文件直接服務)

核心類職責鏈

類/文件職責關鍵方法
PartView (arc.partview.class.php)視圖控制器,協調模板加載、解析、數據綁定、輸出SetTemplet, ParseTemplet, SaveToHtml
DedeTagParse (dedetag.class.php)模板解析引擎核心,負責詞法分析、語法樹構建、緩存管理、最終輸出LoadTemplate, ParseTemplate, SaveTo, SaveCache
DedeTag (dedetag.class.php)單個標簽節點對象,保存名稱、屬性、內部文本、位置、解析後值屬性: TagName, InnerText, StartPos, EndPos, TagValue, CAttribute
DedeAttributeParse (dedetag.class.php)標簽屬性字符串解析器 (如 row=10 typeid='1,2')SetSource, cAttributes
MakeOneTag (channelunit.func.php)標簽數據綁定總入口,動態加載標簽處理文件,執行並回填值遍歷 CTags, include taglib, 調用標簽函數
TypeLink (typelink.class.php)欄目鏈接生成、面包屑導航、頂級欄目查找GetPositionLink, GetOneTypeLink
ChannelUnit (channelunit.class.php)內容模型字段處理、附加表操作 (較少直接使用)GetFields, MakeField

模板解析算法深度還原 (DedeTagParse::ParseTemplate)

輸入:模板字符串 $this->SourceString

輸出:標簽對象陣列 $this->CTags

核心步驟

  1. 定界符預處理:{dede: (開始)、} (結束)、{/dede:tagname} (塊結束)、/} (自閉合結束)。
  2. 主循環掃描:for ($i=0; $i<$len; $i++) 逐字符掃描,strpos 定位下一個 {dede:。
  3. 提取標簽名:從 {dede: 後讀取直到空格/}/換行,得到 tagname (如 arclist)。
  4. 判斷結束類型:向前查找 /} (自閉合) 或 {/dede:tagname} (塊標簽),取最近者作為結束位置。
  5. 提取屬性與內文:標簽名後到結束符前的內容,首個 } 前為屬性字符串,後為 InnerText (模板片段)。
  6. 屬性解析:DedeAttributeParse 將 row=10 type='image' 解析為鍵值陣列。
  7. 構建 DedeTag 對象:封裝所有信息,存入 CTags 陣列,索引為 TagID。
  8. 緩存序列化:將 CTags 核心屬性序列化為 PHP 代碼陣列 $z[index]=['tagname', innerText, start, end, [attrs]] 寫入 data/tplcache/xxx.inc,同時寫入修改時間戳 .txt 文件。

緩存命中邏輯 (LoadCache)


// 緩存文件名生成
$ckfile = str_replace($templateDir, '', $templateFile) . substr(md5($templateFile), 0, 16) . '.inc';
// 例如: index.htm → index.htm_a1b2c3d4e5f67890.inc

// 驗證有效性
if (!file_exists($ckfile) || !file_exists($ckfile . '.txt')) return false;
$cachedMtime = file_get_contents($ckfile . '.txt');
if ($cachedMtime != filemtime($templateFile)) return false; // 模板已修改

// 載入緩存 (include 直接執行 PHP 陣列定義)
include $ckfile; // 得到 $z 陣列

// 還原 DedeTag 對象樹
foreach ($z as $k => $v) {
    $tag = new DedeTag();
    $tag->TagName = $v[0];
    $tag->InnerText = $v[1];
    $tag->StartPos = $v[2];
    $tag->EndPos = $v[3];
    $tag->TagID = $k;
    if (isset($v[4])) $tag->CAttribute->Items = $v[4];
    $this->CTags[$k] = $tag;
}
$this->Count = count($z) - 1;
return true;

標簽數據綁定 (MakeOneTag) 實戰流程


function MakeOneTag(&$dtp, &$obj) {
    // 1. 掃描所有可用標簽 (taglib/*.lib.php)
    $allTags = glob(DEDEINC . '/taglib/*.lib.php');
    
    // 2. 遍歷當前模板解析出的標簽樹
    foreach ($dtp->CTags as $tag) {
        $tagName = $tag->TagName;
        
        // 3. 特殊標簽: field (直接映射 Fields 陣列)
        if ($tagName == 'field') {
            $fieldName = $tag->CAttribute->Items['name'] ?? '';
            if ($fieldName == 'array') {
                $dtp->Assign($obj->Fields); // 整個 Fields 陣列賦值
            } else {
                $tag->TagValue = $obj->Fields[$fieldName] ?? '';
            }
            continue;
        }
        
        // 4. 別名兼容 (arclist -> list, arclist -> sgpage 等)
        $funcName = $tagName;
        if (in_array($tagName, ['list', 'sgpage'])) $funcName = 'arclist';
        
        // 5. 業務標簽: 動態加載並執行
        $libFile = DEDEINC . '/taglib/' . $funcName . '.lib.php';
        if (file_exists($libFile)) {
            include $libFile; // 定義函數 lib_$funcName(&$dtp, &$tag)
            $func = 'lib_' . $funcName;
            if (function_exists($func)) {
                $func($dtp, $tag); // 執行標簽邏輯,結果寫入 $tag->TagValue
            }
        }
    }
}

標簽函數規範 (以 arclist.lib.php 為例)


function lib_arclist(&$dtp, &$tag) {
    // 1. 解析屬性
    $att = $tag->CAttribute->Items;
    $row = $att['row'] ?? 10;
    $typeid = $att['typeid'] ?? 0;
    // ... 更多屬性
    
    // 2. 構建 SQL 查詢
    $sql = "SELECT * FROM dede_archives WHERE arcrank > -1";
    if ($typeid) $sql .= " AND typeid IN ($typeid)";
    $sql .= " ORDER BY pubdate DESC LIMIT $row";
    
    // 3. 執行查詢
    $dsql = $GLOBALS['dsql']; // 全局 DB 對象
    $dsql->SetQuery($sql);
    $dsql->Execute();
    
    // 4. 遍歷結果集,構建輸出 HTML
    $innerText = $tag->InnerText; // 模板片段: 
  • [field:title/]
  • $result = ''; while ($row = $dsql->GetArray()) { // 為每行創建臨時 Fields $fields = $row; // 解析 InnerText 中的 [field:xxx/] 標簽 $itemHtml = $dtp->GetResult($innerText, $fields); // 內部調用 MakeOneTag 遞歸解析 $result .= $itemHtml; } // 5. 回填最終值 $tag->TagValue = $result; }

    靜態生成最終輸出 (DedeTagParse::SaveTo)

    
    public function SaveTo($filename) {
        $content = $this->SourceString; // 原始模板
        // 從後向前替換,避免位置偏移
        for ($i = $this->Count; $i >= 0; $i--) {
            $tag = $this->CTags[$i];
            if ($tag->IsReplace) continue; // 已替換跳過
            // 替換 [StartPos, EndPos] 區間為 TagValue
            $content = substr_replace($content, $tag->TagValue, $tag->StartPos, $tag->EndPos - $tag->StartPos + 1);
        }
        // 寫入文件
        $dir = dirname($filename);
        if (!is_dir($dir)) @mkdir($dir, 0755, true);
        return file_put_contents($filename, $content);
    }
    

    DedeCMS 目錄結構圖譜 (關鍵路徑)

    
    /
    ├── index.php                 # 入口,靜態化觸發
    ├── data/
    │   ├── common.inc.php        # DB 配置
    │   ├── config.cache.inc.php  # 系統配置緩存
    │   ├── tplcache/             # 模板解析緩存 (*.inc, *.txt)
    │   └── sessions/             # Session 文件
    ├── include/
    │   ├── dedetag.class.php     # 核心解析引擎
    │   ├── arc.partview.class.php # 視圖控制器
    │   ├── typelink.class.php    # 欄目鏈接
    │   ├── channelunit.class.php # 模型處理
    │   ├── taglib/               # 業務標簽庫 (arclist.lib.php, channel.lib.php ...)
    │   ├── tpllib/               # 動態標簽 (少用)
    │   └── FCKeditor/            # 編輯器
    ├── dede/                     # 後台管理目錄
    ├── templets/
    │   ├── default/              # 前台模板
    │   │   ├── index.htm         # 首頁模板
    │   │   ├── article_article.htm # 文章頁
    │   │   └── list_article.htm  # 列表頁
    │   ├── plus/                 # 插件模板
    │   └── system/               # 系統底層模板
    ├── plus/                     # 插件入口 (搜索、留言板、RSS...)
    ├── uploads/                  # 上傳文件 (allimg, litimg, media, soft)
    └── special/                  # 專題頁面
    

    架構優缺點總結與現代化遷移建議

    維度優點缺點/風險現代化建議
    模板語法簡單易學、HTML 友好、非程序員可改邏輯表達弱、無繼承/塊機制、XSS 風險遷移至 Twig/Blade,保留標簽語法兼容層
    靜態生成極致性能、抗高併發、安全無動態執行發布延遲、動態交互難 (評論/用戶態)、文件系統壓力CDN + 邊緣緩存 + ESI / SSI / AJAX 動態片段
    標簽機制擴展性強、PHP 直寫、無編譯步驟全局變量污染、無依賴注入、難測試、安全隱患 (include 任意文件)服務化標簽 (RPC/HTTP)、沙箱執行、PSR-4 自動加載
    緩存策略文件緩存、無需 Redis、部署簡單無失效策略、併發競爭、無分佈式支援Redis/Memcached + 標簽化失效
    安全性後台目錄可重命名歷史漏洞多 (SQL注入、任意文件上傳、模板注入)、停止維護全面審計、WAF、禁用危險函數、升級 PHP 8+

    DedeCMS 的架構在「靜態化性能」與「模板易用性」上達到了當時 PHP CMS 的極致平衡,但其全局狀態依賴、缺乏現代工程規範 (命名空間/自動加載/依賴注入/中間件)、安全歷史包袱重,已不適合新項目。存量項目建議凍結核心、外掛服務化、逐步剝離業務邏輯至微服務/Laravel,而非繼續在核心代碼上修補。

    1. 上一篇文章:
    2. 下一篇文章:
    Copyright © 程式碼之家 All Rights Reserved