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

PHP + MySQL 實現「每 IP 每天僅能投票一次」完整代碼

     編輯:PHP 相關知識

表結構

CREATE TABLE `ip_vote_log` (
    `ip` VARBINARY(16) NOT NULL COMMENT 'inet_pton() 二進制 IP',
    `vote_date` DATE NOT NULL COMMENT '投票日期',
    `item_id` INT UNSIGNED NOT NULL COMMENT '投票項目 ID (可選)',
    `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`ip`, `vote_date`, `item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='IP 每日投票記錄';

核心業務邏輯

<?php
/**
 * 檢查並記錄投票
 * @param int $itemId 投票項目 ID (單項目可固定為 1)
 * @return array ['success'=>bool, 'message'=>string]
 */
function processVote(int $itemId = 1): array {
    $mysqli = new mysqli('localhost', 'user', 'pass', 'vote_db');
    $mysqli->set_charset('utf8mb4');

    // 獲取真實 IP (需配合 CDN/代理環境)
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'];
    $ip = trim(explode(',', $ip)[0]); // 取第一個
    $ipBin = inet_pton($ip);
    $today = date('Y-m-d');

    // 1. 嘗試插入投票記錄 (原子操作)
    $sql = "
        INSERT IGNORE INTO ip_vote_log (ip, vote_date, item_id)
        VALUES (?, ?, ?)
    ";
    $stmt = $mysqli->prepare($sql);
    $stmt->bind_param('bsi', $ipBin, $today, $itemId);
    $stmt->execute();

    if ($mysqli->affected_rows > 0) {
        // 插入成功 = 首次投票
        return ['success' => true, 'message' => '投票成功!'];
    } else {
        // 插入失敗 = 重複投票 (主鍵衝突)
        return ['success' => false, 'message' => '您今天已經投過票了,請明天再來。'];
    }
}

// 使用示例
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $result = processVote((int)($_POST['item_id'] ?? 1));
    header('Content-Type: application/json');
    echo json_encode($result);
    exit;
}
?>
<!DOCTYPE html>
<html>
<body>
    <h2>投票頁面</h2>
    <form method="post">
        <input type="hidden" name="item_id" value="1">
        <button type="submit">投票支持</button>
    </form>
    <div id="msg"></div>
    <script>
    document.querySelector('form').addEventListener('submit', async (e) => {
        e.preventDefault();
        const fd = new FormData(e.target);
        const res = await fetch('', {method:'POST', body:fd}).then(r=>r.json());
        document.getElementById('msg').innerHTML = 
            res.success ? '<span style="color:green">'+res.message+'</span>' 
                        : '<span style="color:red">'+res.message+'</span>';
    });
    </script>
</body>
</html>

關鍵技術點解析

技術點說明
INSERT IGNORE主鍵衝突時不報錯、不回滾、affected_rows=0,完美適配「已存在則忽略」語義
聯合主鍵 (ip, vote_date, item_id)保證同 IP、同天、同項目唯一;支持多項目獨立投票
VARBINARY(16) + inet_pton()高效存儲 IPv4/IPv6,支持索引範圍查詢,比 VARCHAR(45) 節省空間
日期分區隱含在 vote_date 字段,配合定時任務 DROP PARTITION 或 DELETE WHERE vote_date < ? 清理

進階:防刷投票增強

  1. 驗證碼:前端集成 Google reCAPTCHA v3 / 滑塊驗證,提交時驗證 Token
  2. 設備指紋:JS 采集 Canvas/UA/分辨率/時區等生成指紋,後端關聯 IP + 指紋雙重判斷
  3. 登錄投票:強制登錄,改為 user_id 唯一約束,徹底解決 IP 共用問題 (公司/學校/家庭共用出口 IP)
  4. 異常檢測:統計單 IP 短時間高頻請求、單指紋多 IP、投票時間分布異常,自動標記人工審核
  5. Redis 預檢:高併發下先用 Redis SETNX ip_vote:{ip}:{date}:{item} 快速攔截,減輕 MySQL 壓力

定時清理腳本

<?php
// cleanup_vote_log.php - 每月 1 號執行
$mysqli = new mysqli('localhost', 'user', 'pass', 'vote_db');
$keepMonths = 6;
$cutoff = date('Y-m-d', strtotime("-$keepMonths months"));

$mysqli->query("DELETE FROM ip_vote_log WHERE vote_date < '$cutoff'");
echo "清理 $cutoff 前數據,影響行數: " . $mysqli->affected_rows;
?>

擴展:支持修改投票

若業務允許「修改投票」,將 INSERT IGNORE 改為 INSERT ... ON DUPLICATE KEY UPDATE 並記錄 updated_at,但需權衡「一次投票」原則。

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