文件:gb.dat,每行一條記錄,字段間用 | 分隔:
IP|Datetime|Name|Email|Homepage|Face|QQ|Avatar|Content|Reply 192.168.1.1|2024-01-15 10:30:00|Alice|alice@ex.com|https://a.com|smile.gif|12345|avatar.png|Hello!| 10.0.0.5|2024-01-15 10:31:22|Bob|bob@ex.com||cry.gif|||Hi there|Admin reply
file = $filePath;
if (!file_exists($this->file)) {
touch($this->file);
chmod($this->file, 0644);
}
}
// 讀取所有記錄 (返回關聯數組)
private function readAll(): array {
$lines = file($this->file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$records = [];
foreach ($lines as $line) {
$parts = explode($this->delimiter, $line, count($this->fields));
$records[] = array_combine($this->fields, $parts);
}
return array_reverse($records); // 最新在前
}
// 寫入所有記錄 (原子性: 寫臨時文件 + rename)
private function writeAll(array $records): bool {
$tmpFile = $this->file . '.tmp';
$fp = fopen($tmpFile, 'w');
if (!$fp) return false;
foreach (array_reverse($records) as $rec) { // 存儲時舊在前
fwrite($fp, implode($this->delimiter, $rec) . "\n");
}
fclose($fp);
return rename($tmpFile, $this->file); // 原子替換
}
// CREATE
public function insert(array $data): bool {
$records = $this->readAll();
$record = array_merge(array_fill_keys($this->fields, ''), $data);
$record['datetime'] = date('Y-m-d H:i:s');
$record['ip'] = $_SERVER['REMOTE_ADDR'] ?? 'cli';
$records[] = $record;
return $this->writeAll($records);
}
// READ (分頁)
public function select(int $page = 1, int $pageSize = 20): array {
$records = $this->readAll();
$total = count($records);
$offset = ($page - 1) * $pageSize;
return [
'data' => array_slice($records, $offset, $pageSize),
'total' => $total,
'page' => $page,
'pageSize' => $pageSize,
'totalPages' => (int)ceil($total / $pageSize)
];
}
// UPDATE (按 datetime 唯一標識)
public function update(string $datetime, array $data): bool {
$records = $this->readAll();
foreach ($records as &$rec) {
if ($rec['datetime'] === $datetime) {
$rec = array_merge($rec, $data);
return $this->writeAll($records);
}
}
return false;
}
// DELETE
public function delete(string $datetime): bool {
$records = $this->readAll();
$filtered = array_filter($records, fn($r) => $r['datetime'] !== $datetime);
if (count($filtered) === count($records)) return false;
return $this->writeAll(array_values($filtered));
}
// SEARCH (關鍵字模糊匹配)
public function search(string $keyword): array {
$records = $this->readAll();
$keyword = strtolower(trim($keyword));
return array_filter($records, function($r) use ($keyword) {
return strpos(strtolower(implode(' ', $r)), $keyword) !== false;
});
}
}
insert([
'name' => $_POST['name'],
'email' => $_POST['email'],
'home' => $_POST['home'],
'face' => $_POST['face'],
'qq' => $_POST['qq'],
'avatar' => $_POST['avatar'],
'content' => $_POST['content'],
]);
header('Location: index.php'); exit;
} elseif ($action === 'reply') {
$db->update($_POST['datetime'], ['reply' => $_POST['reply']]);
header('Location: index.php'); exit;
} elseif ($action === 'delete') {
$db->delete($_POST['datetime']);
header('Location: index.php'); exit;
}
}
// 讀取數據
$page = max(1, (int)($_GET['page'] ?? 1));
$result = $db->select($page, 10);
$searchResults = [];
if (!empty($_GET['q'])) {
$searchResults = $db->search($_GET['q']);
}
?>
留言本
留言本
= nl2br(htmlspecialchars($msg['content'])) ?>
| 點 | 實現 | 作用 |
|---|---|---|
| 原子寫入 | tmp + rename | 防止寫入中斷損壞數據文件 |
| 併發安全 | 文件鎖 flock (可加) | 多進程寫入不丟數據 |
| XSS 防護 | htmlspecialchars 輸出轉義 | 防止腳本注入 |
| CSRF 防護 | 可加 token 隱藏域 | 防跨站請求偽造 |
| 性能優化 | 大數據遷移 SQLite | 超過 10 萬行建議遷移 |
當出現以下信號,請遷移至 SQLite / MySQL:
文本數據庫是理解「持久化本質」的最佳教具。掌握其 CRUD 邏輯,遷移到任何數據庫都將得心應手。