本文解析一個基於 PHP + MySQL 的後台文章管理模塊,採用單入口動作分發模式 (action 參數控制流程),整合 FCKeditor (現 CKEditor 前身) 富文本編輯器,實現文章的增刪改查、分頁、審核、鎖定/解鎖及批量操作功能。代碼雖屬早期風格 (使用 mysql_* 函數、全局變量、模板引擎),但其業務邏輯分層思想值得學習。
入口文件根據 $_REQUEST['action'] 分發至不同業務處理分支:
<?php
// 初始化與依賴加載
require_once "isLogin.php"; // 權限驗證
require_once "../conn/config.inc.php"; // 數據庫配置 $db 對象
require_once "../FCKeditor/fckeditor.php"; // 富文本編輯器
// 實例化編輯器 (複用)
$oFCKeditor = new FCKeditor("content");
$oFCKeditor->BasePath = '../FCKeditor/';
// 動作路由
$action = empty($_REQUEST['action']) ? 'list' : trim($_REQUEST['action']);
switch ($action) {
case 'add': // 顯示添加表單
$tpl->assign('fck', $oFCKeditor->Create());
$tpl->assign(['tmess' => '添加文章', 'act' => 'insert', 'submitButton' => '添加']);
$tpl->display('admin/addArticle.tpl');
break;
case 'insert': // 執行添加入庫
// 1. 獲取並過濾輸入 (實際項目必須加強過濾)
$data = [
'title' => $_POST['title'],
'zhaiyao' => $_POST['zhaiyao'],
'author' => $_POST['author'],
'laiyuan' => $_POST['laiyuan'],
'keyword' => $_POST['keyword'],
'is_tuijian' => $_POST['is_tuijian'],
'content' => $_POST['content'], // 包含 HTML 標籤
];
// 2. 構建 SQL (警示:直接拼接存在 SQL 注入風險,建議使用預處理)
$sql = "INSERT INTO article (title, zhaiyao, author, laiyuan, keyword, is_tuijian, content, addtime)
VALUES ('{$data['title']}', '{$data['zhaiyao']}', '{$data['author']}', '{$data['laiyuan']}',
'{$data['keyword']}', '{$data['is_tuijian']}', '{$data['content']}', NOW())";
$rs = $db->query($sql);
$rs ? header("Location: /admin/article.php?action=list") : exit("添加失敗!");
break;
case 'list': // 文章列表分頁顯示
$total = getAllArticle(); // 獲取總記錄數
$curpage = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$pageObj = new Page($total, $curpage); // 分頁類
$pageInfo = $pageObj->getPageInfo();
$arts = getArticlePage($pageInfo['row_offset'], $pageInfo['row_num']);
$tpl->assign(['tmess' => '文章列表', 'arts' => $arts, 'pageInfo' => $pageInfo]);
$tpl->display('admin/editArticle.tpl');
break;
case 'edit': // 顯示編輯表單 (回填數據)
$id = (int)$_GET['id'];
$post = editArticle($id);
$oFCKeditor->Value = $post['content']; // 設置編輯器初始內容
// 推薦狀態單選框邏輯
$tpl->assign('recommend', $post['is_tuijian'] == 1 ? 'checked' : '');
$tpl->assign('no_recommend', $post['is_tuijian'] != 1 ? 'checked' : '');
$tpl->assign(['tmess' => '修改文章', 'post' => $post, 'fck' => $oFCKeditor->Create(), 'act' => 'update', 'submitButton' => '修改']);
$tpl->display('admin/addArticle.tpl');
break;
case 'update': // 執行更新
$id = (int)$_POST['id'];
// ... 獲取 POST 數據 ...
$sql = "UPDATE article SET title='{$title}', zhaiyao='{$zhaiyao}', author='{$author}',
laiyuan='{$laiyuan}', keyword='{$keyword}', is_tuijian='{$is_tuijian}', content='{$content}'
WHERE id = '{$id}'";
$rs = $db->query($sql);
$rs ? header("Location: /admin/article.php?action=list") : exit("修改失敗!");
break;
case 'del': // 單條/批量刪除
del($_GET['id']); // 函數內部判斷是否為陣列
break;
case 'shenhe': // 審核 (status=1)
shenhe($_GET['id']);
break;
case 'lock': // 鎖定/下線 (status=0)
lock($_GET['id']);
break;
// 批量操作處理 (表單提交按鈕名稱判斷)
case (isset($_REQUEST['shenhe_x']) || isset($_REQUEST['shenhe_y'])):
shenhe($_POST['del']);
break;
case (isset($_REQUEST['lock_x']) || isset($_REQUEST['lock_y'])):
lock($_POST['del']);
break;
}
?>
將 SQL 操作封裝為獨立函數,降低控制器耦合度。
<?php
// 獲取總記錄數 (用於分頁)
function getAllArticle() {
global $db;
$sql = "SELECT COUNT(*) as total FROM article"; // 優化:不選 *
$rs = $db->query($sql);
$row = $db->fetch_array($rs);
return $row['total'];
}
// 分頁獲取列表
function getArticlePage($offset, $num) {
global $db;
$sql = "SELECT * FROM article ORDER BY id DESC LIMIT $offset, $num";
$rs = $db->query($sql);
$data = [];
while ($row = $db->fetch_array($rs)) {
$data[] = $row;
}
return $data;
}
// 獲取單條記錄 (編輯回填)
function editArticle($id) {
global $db;
$sql = "SELECT * FROM article WHERE id = '$id' LIMIT 1";
$rs = $db->query($sql);
return $db->fetch_array($rs, MYSQL_ASSOC);
}
// 通用刪除 (支持單 ID 或 ID 陣列)
function del($id) {
global $db;
$sql = "DELETE FROM article WHERE id ";
$sql .= is_array($id) ? "IN (" . implode(',', $id) . ")" : "= $id";
$rs = $db->query($sql);
$rs ? header("Location: /admin/article.php?action=list") : exit("刪除失敗!");
}
// 狀態更新通用邏輯 (審核/鎖定)
function updateStatus($id, $status, $msg) {
global $db;
$sql = "UPDATE article SET status='$status' WHERE id ";
$sql .= is_array($id) ? "IN (" . implode(',', $id) . ")" : "= $id";
$rs = $db->query($sql);
$rs ? header("Location: /admin/article.php?action=list") : exit("$msg 失敗!");
}
function shenhe($id) { updateStatus($id, 1, '審核'); }
function lock($id) { updateStatus($id, 0, '鎖定'); }
?>
| 問題點 | 風險等級 | 現代化重構方案 |
|---|---|---|
mysql_* 函數 / 自定義 MyDB 類 |
高 | 棄用。改用 PDO 或 MySQLi (預處理語句防注入)。 |
| SQL 字符串直接拼接變量 | 極高 | 強制使用參數綁定 ($stmt->bind_param() 或 $pdo->prepare())。 |
$_REQUEST 混用 / 缺乏輸入驗證 |
中 | 明確使用 $_GET/$_POST;引入驗證庫 (如 Respect/Validation) 或手動 filter_input。 |
global $db 全局依賴 |
低 | 採用依賴注入 (DI) 容器,或至少在類構造函數中傳入 DB 實例。 |
| FCKeditor (已停止維護) | 中 | 升級至 CKEditor 4/5、TinyMCE 或現代編輯器 (TipTap, Slate)。 |
| 模板引擎變量直接輸出 | 中 | 模板引擎需默認開啟 HTML 轉義 (如 Smarty {$var|escape}, Blade {{ $var }}) 防 XSS。 |
| 業務邏輯與視圖耦合 | 低 | 遵循 MVC 分層,控制器僅負責調度,業務邏輯下沉至 Service 層。 |
這個文章管理模塊展示了 PHP 早期開發的典型模式:頁面控制器模式 + 過程化數據訪問 + 模板引擎渲染。雖然具備完整的 CRUD、分頁、狀態機、批量操作等實用功能,但在現代工程標準下,必須進行安全加固 (預處理、輸入過濾、輸出轉義)、架構分層 (MVC/分層架構)、依賴現代化 (Composer、PSR-4、PDO) 等重構,方可應用於生產環境。理解其核心邏輯有助於維護老項目或從零構建輕量級 CMS。