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

PHP 入門實戰(九):文件存儲版投票/調查系統——從數據讀寫到百分比圖表渲染

     編輯:PHP 綜合知識

前言:無數據庫也能做動態互動

很多虛擬主機不提供 MySQL,但 PHP 強大的文件操作功能完全能支撐投票、留言本、計數器、簡易 CMS等應用。本文以一個完整的「新版頁面調查」為例,講解文件鎖併發控制、數據結構設計、百分比計算、HTML 表單處理、結果可視化全流程。

功能需求

  • 單選投票,四個選項:信息量更大 / 網頁更精美 / 沒什麼改進 / 其它
  • 防重複投票:Cookie 24 小時有效
  • 實時顯示結果:表格 + 橫條長度視覺化百分比 + 總票數
  • 數據持久化:文本文件 data.pol,每行 選項ID 票數

核心代碼結構(mypolls.php)

<?php
// 配置
$pollFile = __DIR__ . '/data.pol';
$cookieName = 'polled_ce';
$options = [
    1 => '信息量更大',
    2 => '網頁更精美',
    3 => '沒什麼改進',
    4 => '其它',
];
$barHeight = 9;   // 百分比條高度(px)
$barScale  = 1;   // 1% = 1px (max 100px)

// 1. 檢查 Cookie 防刷
$voted = isset($_COOKIE[$cookieName]);
if (!$voted && isset($_GET['poll']) && isset($_GET['poll_voteNr'])) {
    $choice = (int)$_GET['poll_voteNr'];
    if (isset($options[$choice])) {
        // 2. 文件鎖寫入(併發安全)
        $fp = fopen($pollFile, 'c+');           // 打開/創建,指針在開頭
        if (flock($fp, LOCK_EX)) {              // 獨佔鎖
            $counts = [];
            rewind($fp);
            while (($line = fgets($fp)) !== false) {
                if (preg_match('/(\d+)\s+(\d+)/', $line, $m)) {
                    $counts[(int)$m[1]] = (int)$m[2];
                }
            }
            $counts[$choice] = ($counts[$choice] ?? 0) + 1;
            ftruncate($fp, 0);                  // 清空文件
            rewind($fp);
            foreach ($counts as $id => $v) {
                fwrite($fp, "$id $v\n");
            }
            fflush($fp);
            flock($fp, LOCK_UN);
        }
        fclose($fp);
        // 3. 設置 Cookie
        setcookie($cookieName, '1', time() + 86400, '/');
        $voted = true; // 立即顯示結果
    }
}

// 4. 讀取統計數據(共用邏輯)
function readPoll(string $file): array
{
    $counts = array_fill_keys(array_keys($GLOBALS['options']), 0);
    if (is_file($file)) {
        $fp = fopen($file, 'r');
        if (flock($fp, LOCK_SH)) {              // 共享鎖讀取
            while (($line = fgets($fp)) !== false) {
                if (preg_match('/(\d+)\s+(\d+)/', $line, $m)) {
                    $counts[(int)$m[1]] = (int)$m[2];
                }
            }
            flock($fp, LOCK_UN);
        }
        fclose($fp);
    }
    return $counts;
}

$counts = readPoll($pollFile);
$total  = array_sum($counts);
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>新版頁面調查</title>
<style>
body{font-family:system-ui,sans-serif;max-width:600px;margin:2rem auto}
table{border-collapse:collapse;width:100%} td,th{border:1px solid #009933;padding:.5rem;text-align:center}
.bar{height:9px;background:#009933;display:inline-block;vertical-align:middle}
</style></head><body>
<h2>與舊版頁面相比較您覺得新版頁面:</h2>

<?php if (!$voted): ?>
  <form method="get">
    <?php foreach ($options as $id => $label): ?>
      <label><input type="radio" name="poll_voteNr" value="" >  $label):
        $v = $counts[$id] ?? 0;
        $pct = $total ? round(100 * $v / $total, 2) : 0;
        $barW = (int)($pct * $barScale);
    ?>
    <tr>
      <td>
      <td>
      <td><div class="bar" style="width:px"></div> %</td>
    </tr>
    <?php endforeach; ?>
  </table>
  <p>總投票數: <strong></p>
  <p><a href="?">返回首頁</a> | <a href="?" onclick="document.cookie='polled_ce=;max-age=0;path=/';location.reload();">清除 Cookie 重新投票</a></p>
<?php endif; ?>
</body></html>

關鍵技術點解析

1. 文件鎖 flock():併發寫入的生命線

  • LOCK_EX 獨佔鎖(寫入前獲取)
  • LOCK_SH 共享鎖(讀取時獲取,允許並發讀)
  • LOCK_UN 釋放鎖
  • 模式 c+:文件不存在則創建,存在則打開,指針在開頭,不截斷(關鍵!)
  • 寫入流程:flock → 讀取舊數據 → 計算新數據 → ftruncate(0) → rewind → fwrite → fflush → flock(UN)

2. 數據格式:極簡鍵值對

1 12
2 5
3 3
4 1

每行 ID 空格 數量,preg_match('/(\d+)\s+(\d+)/') 解析,無需 JSON/serialize 開銷,人工可讀、可編輯。

3. Cookie 防刷:客戶端狀態 + 服務端校驗

setcookie('polled_ce', '1', time() + 86400, '/'); // 24h,全站路徑

局限:用戶可清除 Cookie、換瀏覽器、用無痕模式繼續投票。進階方案:IP 限制 + 登入用戶 ID 綁定 + 驗證碼。

4. 百分比條純 CSS 實現

<div class="bar" style="width:px"></div>

.bar { height: 9px; background: #009933; display: inline-block; },無需圖片、無需 JS,輕量高效。

擴展:多選投票、加權選項、結果導出

// 多選:表單 name="poll_voteNr[]",後端遍歷 $_GET['poll_voteNr'] 逐個加鎖寫入
// 加權:options 陣列改為 ['id'=>1, 'label'=>'...', 'weight'=>2],統計時 $counts[$id] += $weight
// 導出 CSV:
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="poll_'.date('Ymd').'.csv"');
echo "選項,票數,百分比\n";
foreach ($options as $id=>$label) {
    $v = $counts[$id]??0;
    $p = $total?round(100*$v/$total,2):0;
    echo "\"$label\",$v,$p%\n";
}

總結

這個 mypolls.php 雖小,却包含了Web 應用核心要素:HTTP 協議(GET/POST)、狀態管理、併發控制、數據持久化、模板渲染、用戶交互。掌握文件存儲模式,你就能在任何支援 PHP 的環境(甚至無數據庫的虛擬主機、嵌入式設備)快速構建互動功能。下一步嘗試:將數據層抽象為 PollStorageInterface,實現 FilePollStorage / MySQLPollStorage / RedisPollStorage 多驅動切換。

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