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

PHP 桌面中心系列(二):數據寫入表單與防禦性編程實踐

     編輯:PHP 綜合知識

功能流程

管理員打開寫入頁面 → 填寫 4 組圖片資訊 (原圖/縮圖/說明) + 選擇分類 → 提交表單 → 後端驗證 → 寫入資料庫 → 顯示結果。原始碼採用 `mysql_*` 過時擴展、直接拼接 SQL、無驗證、無 CSRF 防護,本文給出生產級重構版。

重構要點

  • PDO + 參數化查詢 徹底防 SQL 注入
  • 輸入驗證白名單:分類表名、URL 格式、必填欄位
  • CSRF Token:Session 隨機令牌 + `hash_equals()` 驗證
  • 統一錯誤處理:Try-catch + 友善提示 + 詳細日誌
  • htmlspecialchars 輸出編碼:防 XSS
  • utf8mb4 全鏈路:支援 Emoji、生僻字

代碼結構

insert/
├── config.php
├── Database.php
├── WallpaperRepository.php
├── Auth.php
├── create.php      # 顯示表單 (GET)
└── store.php       # 處理提交 (POST)

核心實作

1. 表單頁 (create.php) — GET

<?php
// create.php
require_once 'config.php';
require_once 'Auth.php';

Auth::startSession();
$config = require 'config.php';
$csrfToken = Auth::generateCsrfToken($config['app']['csrf_token_name']);
?<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8">
  <title>新增桌面圖片記錄</title>
  <style>
    .form-section { margin-bottom: 24px; padding-bottom: 16px; border-bottom: 1px solid #eee; }
    .form-row { display: grid; grid-template-columns: 140px 1fr; gap: 12px; align-items: center; margin-bottom: 12px; }
    .form-row label { font-weight: 500; }
    .form-row input[type="url"], .form-row input[type="text"] { padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; width: 100%; box-sizing: border-box; }
    .required::after { content: " *"; color: #dc3545; }
    .btn { padding: 12px 28px; border: none; border-radius: 4px; font-size: 1rem; cursor: pointer; }
    .btn-primary { background: #28a745; color: #fff; }
    .hint { color: #6c757d; font-size: 0.875rem; margin-top: 4px; }
  </style>
</head>
<body style="max-width: 900px; margin: 40px auto; padding: 0 20px;">
  <h1>新增桌面圖片記錄</h1>
  <p><a href="display.php">← 返回列表</a></p>
  
  <form method="post" action="store.php" novalidate>
    <input type="hidden" name="" value="">
    
    <div class="form-row">
      <label class="required">分類</label>
      <div>
        <label><input type="radio" name="table" value="jpg" checked> 動漫桌布</label>
        <label><input type="radio" name="table" value="jpg1"> 水彩畫</label>
        <label><input type="radio" name="table" value="jpg2"> 明星寫真</label>
        <label><input type="radio" name="table" value="jpg3"> 廣告創意</label>
        <label><input type="radio" name="table" value="jpg4"> 其他類</label>
      </div>
    </div>
    
    <div class="form-section">
      <h3>第 1 組圖片 (必填)</h3>
      <div class="form-row">
        <label class="required">原圖網址</label>
        <input type="url" name="url" required placeholder="https://example.com/full.jpg">
      </div>
      <div class="form-row">
        <label class="required">縮圖網址</label>
        <input type="url" name="mess" required placeholder="https://example.com/thumb.jpg">
      </div>
      <div class="form-row">
        <label class="required">說明/來源</label>
        <input type="text" name="fromx" required maxlength="200" placeholder="圖片描述、作者、來源網站">
      </div>
    </div>
    
    <div class="form-section">
      <h3>第 2 組圖片 (選填)</h3>
      <div class="form-row"><label>原圖網址</label><input type="url" name="url1" placeholder="https://..."></div>
      <div class="form-row"><label>縮圖網址</label><input type="url" name="mess1" placeholder="https://..."></div>
      <div class="form-row"><label>說明/來源</label><input type="text" name="fromx1" maxlength="200"></div>
    </div>
    
    <div class="form-section">
      <h3>第 3 組圖片 (選填)</h3>
      <div class="form-row"><label>原圖網址</label><input type="url" name="url2" placeholder="https://..."></div>
      <div class="form-row"><label>縮圖網址</label><input type="url" name="mess2" placeholder="https://..."></div>
      <div class="form-row"><label>說明/來源</label><input type="text" name="fromx2" maxlength="200"></div>
    </div>
    
    <div class="form-section">
      <h3>第 4 組圖片 (選填)</h3>
      <div class="form-row"><label>原圖網址</label><input type="url" name="url3" placeholder="https://..."></div>
      <div class="form-row"><label>縮圖網址</label><input type="url" name="mess3" placeholder="https://..."></div>
      <div class="form-row"><label>說明/來源</label><input type="text" name="fromx3" maxlength="200"></div>
    </div>
    
    <button type="submit" class="btn btn-primary">提交儲存</button>
  </form>
</body>
</html>

2. 處理提交 (store.php) — POST

<?php
// store.php
require_once 'config.php';
require_once 'Database.php';
require_once 'WallpaperRepository.php';
require_once 'Auth.php';

Auth::startSession();
$config = require 'config.php';
$db = Database::getInstance($config['db']);
$repo = new WallpaperRepository($db->getPdo(), $config['app']['table_prefix'], $config['app']['allowed_tables']);

// CSRF 驗證
$csrfToken = $_POST[$config['app']['csrf_token_name']] ?? '';
if (!Auth::validateCsrfToken($config['app']['csrf_token_name'], $csrfToken)) {
    die('CSRF 驗證失敗,請重新提交');
}

// 收集並驗證輸入
$table = $_POST['table'] ?? '';
if (!in_array($table, $config['app']['allowed_tables'], true)) {
    die('無效的分類');
}

$required = ['url', 'mess', 'fromx'];
$data = [];
foreach ($required as $field) {
    $val = trim($_POST[$field] ?? '');
    if ($val === '') {
        die("必填欄位 {$field} 不可為空");
    }
    $data[$field] = $val;
}

// 選填欄位 (1-4 組)
for ($i = 1; $i <= 3; $i++) {
    foreach (['url', 'mess', 'fromx'] as $field) {
        $key = $field . $i;
        if (isset($_POST[$key])) {
            $val = trim($_POST[$key]);
            if ($val !== '') {
                // URL 格式基本驗證
                if (str_starts_with($field, 'url') && !filter_var($val, FILTER_VALIDATE_URL)) {
                    die("{$key} 格式不正確,需為完整網址");
                }
                $data[$key] = $val;
            }
        }
    }
}

try {
    $id = $repo->create($table, $data);
    ?>
    
    
    
      
      新增成功
      
    
    
      
✅ 新增成功!自動分配 ID:
$v): ?>
欄位值

繼續新增 查看列表

getMessage()); die('系統錯誤,請稍後再試或聯繫管理員'); }

3. Repository 新增方法 (WallpaperRepository.php)

<?php
// WallpaperRepository.php (新增 create 方法)
    public function create(string $table, array $data): int {
        $this->validateTable($table);
        
        $fields = array_keys($data);
        $placeholders = array_map(fn($f) => ":$f", $fields);
        
        $sql = "INSERT INTO `{$this->prefix}{$table}` (" . implode(', ', array_map(fn($f) => "`$f`", $fields)) . ") 
                VALUES (" . implode(', ', $placeholders) . ")";
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($data);
        
        return (int)$this->pdo->lastInsertId();
    }

防禦性編程關鍵點總結

層面原始碼風險重構對策
輸入驗證無任何驗證,直接入庫必填檢查、白名單表名、URL 格式驗證、長度限制
SQL 安全字串拼接 `insert into $jpg values (...)`PDO Prepared Statements + 動態參數綁定
CSRF無防護Session Token + `hash_equals()` 常時間比較
XSS直接輸出變數到 HTML`htmlspecialchars()` 所有動態輸出
錯誤處理`die("系統出了問題")` 洩漏細節Try-catch + 通用訊息 + `error_log()` 記錄堆疊
編碼`charset=gb2312` 不支援完整 Unicode資料庫/連線/HTML/JSON 全鏈路 `utf8mb4`
資源管理`mysql_connect` 無連線池、無關閉PDO 單例模式、自動連線管理

擴展建議

  • 檔案上傳:替換 URL 輸入為 ``,後端驗證 MIME、尺寸、上傳至雲存儲 (S3/OSS) 並回寫 URL
  • 圖片處理:介入 Intervention Image/GD 自動生成多規格縮略圖、WebP 轉換、加水印
  • 異步處理:大量寫入時推送至隊列 (Redis/RabbitMQ) 由 Worker 處理,避免阻塞 HTTP 請求
  • 審核流程:新增 `status` 欄位 (草稿/待審/已發/拒絕),引入簡易工作流
  • API 化:`store.php` 輸出 JSON,前端採用 fetch + 表單驗證庫 (如 Vue + VeeValidate) 實現 SPA 體驗

總結

p>「數據寫入」是 Web 應用最基礎也最關鍵的環節。重構版展示了 **表單設計、輸入驗證、CSRF 防護、參數化查詢、異常處理、安全輸出** 的完整防禦體系。這套模式適用於所有「建立資源」場景,建議封裝為 `CreateController` 基類,配合表單請求驗證類 (FormRequest) 進一步標準化、減少樣板代碼。

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