刪除記錄時,必須同時清理關聯的上傳檔案,避免孤兒檔案佔用空間。
<?php
// 資料庫連線(建議改用 PDO)
$conn = mysql_connect('127.0.0.1', 'root', 'root') or die('DB 連線失敗');
mysql_select_db('test', $conn);
mysql_query("SET NAMES 'utf8'");
// 假設表結構:id, title, litpic (儲存相對路徑)
$sql = "SELECT id, litpic FROM test1";
$query = mysql_query($sql) or die(mysql_error());
if (mysql_num_rows($query)) {
while ($rs = mysql_fetch_assoc($query)) {
// 先刪除資料庫記錄
$delSql = "DELETE FROM test1 WHERE id = " . (int)$rs['id'];
if (mysql_query($delSql)) {
// 再刪除實體檔案
if (deleteFile($rs['litpic'])) {
echo "ID {$rs['id']}:記錄與圖片皆刪除成功<br>";
} else {
echo "ID {$rs['id']}:記錄刪除成功,但圖片刪除失敗(可能檔案不存在)<br>";
}
} else {
echo "ID {$rs['id']}:資料庫刪除失敗 - " . mysql_error() . "<br>";
}
}
} else {
echo "無資料可刪除";
}
function deleteFile($path) {
// 安全檢查:防止路徑遍歷攻擊
$path = realpath($path);
$uploadDir = realpath(__DIR__ . '/uploads');
if ($path === false || strpos($path, $uploadDir) !== 0) {
return false; // 路徑不在允許目錄下
}
if (is_file($path)) {
return @unlink($path);
}
return false; // 不存在或非檔案
}
| 風險 | 防護措施 |
|---|---|
| 路徑遍歷 (../../../etc/passwd) | realpath() 正規化 + 檢查是否在上傳目錄下 |
| SQL Injection | 使用 (int)$id 或 Prepared Statement |
| 刪除失敗無感知 | 檢查 unlink() 回傳值並記錄日誌 |
| 資料庫刪除成功但檔案刪除失敗 | 記錄異常、人工介入或排程掃描孤兒檔案 |
| 並發刪除競態 | 資料庫交易 + 檔案鎖 (flock) 或軟刪除欄位 |
// 1. 資料表加欄位:deleted_at TIMESTAMP NULL, file_deleted TINYINT DEFAULT 0
// 2. 刪除請求只更新標記
UPDATE test1 SET deleted_at = NOW() WHERE id = ?;
// 3. 背景 Worker 定期掃描
SELECT id, litpic FROM test1 WHERE deleted_at IS NOT NULL AND file_deleted = 0;
// 4. 刪除檔案成功後更新
UPDATE test1 SET file_deleted = 1 WHERE id = ?;
優點:回應快速、可復原、避免長時間交易鎖定資料表。
<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8mb4', 'root', 'root');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query("SELECT id, litpic FROM test1");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$pdo->beginTransaction();
try {
$pdo->prepare("DELETE FROM test1 WHERE id = ?")->execute([$row['id']]);
if (deleteFile($row['litpic'])) {
$pdo->commit();
} else {
$pdo->rollBack();
error_log("檔案刪除失敗,已回滾資料庫:{$row['litpic']}");
}
} catch (Exception $e) {
$pdo->rollBack();
error_log($e->getMessage());
}
}