架構設計
- 數據存儲:單一 XML 文件(
my_xmldb.xml),根節點 <Message>,每條記錄為 <Rows> 包含 Nickname、Content、Timer
- 核心類:
xmldb extends DOMDocument 封裝 CRUD 與分頁邏輯
- 前端:簡單 HTML 表單 + CSS 佈局 + PHP 模板輸出
核心類代碼(_class.xmldb.inc.php)
<?php
class xmldb extends DOMDocument {
private $xmldb;
public function __construct($xmldbname) {
$this->xmldb = $xmldbname;
if (!file_exists($this->xmldb)) {
$init = '<?xml version="1.0" encoding="utf-8"?><Message></Message>';
$this->loadXML($init);
$this->save($this->xmldb);
} else {
$this->load($this->xmldb);
}
}
// 插入留言
public function insert_message($nickname, $message) {
$msgNode = $this->getElementsByTagName("Message")->item(0);
$row = $this->createElement("Rows");
$nick = $this->createElement("Nickname");
$nick->appendChild($this->createTextNode($nickname));
$row->appendChild($nick);
$content = $this->createElement("Content");
$content->appendChild($this->createTextNode($message));
$row->appendChild($content);
$timer = $this->createElement("Timer");
$timer->appendChild($this->createTextNode(time()));
$row->appendChild($timer);
$msgNode->appendChild($row);
$this->save($this->xmldb);
}
// 獲取節點總數
public function get_node_length($tag) {
return $this->getElementsByTagName($tag)->length;
}
// 分頁讀取列表
public function get_message_list($start, $total, $pageSize) {
$end = min($start + $pageSize, $total);
$list = [];
for ($i = $start; $i < $end; $i++) {
$list[] = [
'Nickname' => $this->getElementsByTagName("Nickname")->item($i)->nodeValue,
'Content' => $this->getElementsByTagName("Content")->item($i)->nodeValue,
'Timer' => $this->getElementsByTagName("Timer")->item($i)->nodeValue,
];
}
return $list;
}
// 通用分頁 HTML 生成
public static function splitpage($pageAll, $page = 1, $urlFile = null, $pageParam = 'page', $pageAvg = 10) {
$pageAvg--;
$page = max(1, min((int)$page, $pageAll));
$start = max(1, $page - (int)ceil($pageAvg / 2));
$start = ($page + (int)ceil($pageAvg / 2) > $pageAll) ? $pageAll - $pageAvg : $start;
$stop = min($start + $pageAvg, $pageAll);
if (empty($urlFile)) $urlFile = $_SERVER['PHP_SELF'];
$sep = strpos($urlFile, '?') ? '&' : '?';
$query = http_build_query(array_diff_key($_GET, [$pageParam => '']));
$base = $urlFile . $sep . $query . ($query ? '&' : '');
$html = '';
if ($page > 1) {
$html .= "<a href='{$base}{$pageParam}=1'>首頁</a> ";
$html .= "<a href='{$base}{$pageParam}=" . ($page - 1) . "'>上一頁</a> ";
} else {
$html .= "首頁 上一頁 ";
}
for ($i = $start; $i <= $stop; $i++) {
$html .= ($i == $page) ? " $i " : " <a href='{$base}{$pageParam}=$i'>$i</a> ";
}
if ($page < $pageAll) {
$html .= "<a href='{$base}{$pageParam}=" . ($page + 1) . "'>下一頁</a> ";
$html .= "<a href='{$base}{$pageParam}=$pageAll'>尾頁</a>";
} else {
$html .= "下一頁 尾頁";
}
return $html;
}
}
?>
前端調用(index.php)
<?php
require "_class.xmldb.inc.php";
$xmldb = new xmldb("./my_xmldb.xml");
if (isset($_POST['btnform1'])) {
$xmldb->insert_message($_POST['nickname'], $_POST['content']);
}
$pageSize = 3;
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$total = $xmldb->get_node_length("Rows");
$pageCount = ceil($total / $pageSize);
$start = ($page - 1) * $pageSize;
$rows = $xmldb->get_message_list($start, $total, $pageSize);
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>XML 留言板</title>
<style>#messagelist ul li{float:left;list-style:none;margin-right:20px;}</style></head>
<body>
<div id="messagelist">
<?php if ($rows): foreach ($rows as $v): ?>
<ul>
<li><?= htmlspecialchars($v['Nickname']) ?> <span>(<?= date('Y-m-d H:i', $v['Timer']) ?>)</span></li>
<li><?= htmlspecialchars($v['Content']) ?></li>
</ul><br>
<?php endforeach; endif; ?>
<div><?= $xmldb::splitpage($pageCount, $page) ?></div>
</div>
<form method="post">
暱稱: <input name="nickname" required><br>
內容: <textarea name="content" required></textarea><br>
<button name="btnform1">提交</button>
</form>
</body></html>
關鍵技術點
- DOMDocument:原生 XML 操作,無需第三方庫,支援 XPath、Schema 驗證。
- 原子寫入:
save() 直接覆蓋文件,併發場景需加文件鎖(flock())或改用 SQLite。
- 分頁邏輯:
splitpage() 靜態方法複用性強,自動保留其他 GET 參數。
- XSS 防護:輸出時統一
htmlspecialchars()。
擴展建議
- 引入
SimpleXMLElement 或 XMLReader 處理大文件流式讀取
- 加入驗證碼、敏感詞過濾、IP 限流
- 遷移至 SQLite/MySQL 獲得併發與索引優勢
- 前端接入 Vue/Alpine.js 實現無刷新提交