<form id="myForm" action="/submit" method="post">
<input type="text" name="content" required>
<button type="submit" id="btnSubmit">送出</button>
</form>
<script>
document.getElementById('myForm').addEventListener('submit', function(e) {
const btn = document.getElementById('btnSubmit');
btn.disabled = true;
btn.textContent = '送出中...';
// 可選:設定逾時恢復,防止 JS 錯誤導致永久禁用
setTimeout(() => { btn.disabled = false; btn.textContent = '送出'; }, 10000);
});
</script>
<script>
let submitted = false;
document.getElementById('myForm').addEventListener('submit', function(e) {
if (submitted) {
e.preventDefault();
alert('請勿重複提交');
return;
}
submitted = true;
});
</script>
原理:表單產生時產生唯一 Token 存入 Session 並置於隱藏欄位。提交時比對並銷毀 Token,後續重複提交將失效。
<?php
session_start();
$token = bin2hex(random_bytes(32));
$_SESSION['form_token'] = $token;
?>
<form action="submit.php" method="post">
<input type="hidden" name="token" value="<?php echo $token; ?>">
<textarea name="content" required></textarea>
<button type="submit">發表</button>
</form>
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') exit('Method not allowed');
$token = $_POST['token'] ?? '';
if (empty($token) || !isset($_SESSION['form_token']) || $token !== $_SESSION['form_token']) {
// Token 無效或已使用
http_response_code(400);
exit('請勿重複提交或表單已過期,請重新整理頁面。');
}
// 驗證通過,銷毀 Token
unset($_SESSION['form_token']);
// 業務邏輯:儲存資料...
echo '提交成功!';
?>
<?php
// 產生 Token
$token = 'form_token:' . bin2hex(random_bytes(16));
$redis->setex($token, 3600, '1'); // 1小時有效
// 表單隱藏欄位帶入 $token
// 驗證並刪除(原子操作)
$lua = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
$valid = $redis->eval($lua, 1, $token, '1');
if (!$valid) exit('重複提交或 Token 無效');
?>
訂單系統常用:建立訂單前先插入 `idempotency_key` (唯一索引),重複請求會拋出 Duplicate Entry 錯誤,直接回傳既有結果。
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
idempotency_key VARCHAR(64) NOT NULL UNIQUE,
user_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);