以前看過很多各種防止表單重複提交js或jquery程式,但這種只是一個簡單的方法,如果我們不從這個頁面提交表單,直接找到接受資料的頁面這種js處理方法就無效了,下面我利用php一些方法來解決。
以前用的js表單防止重複提交方法
重點其實是form與input元素,p元素只是paperen私自加上去的,對後續的說明沒有任何影響,其實很簡單,所謂input就是輸入了,你可以完全將input 元素理解為是用作使用者輸入,只是某些屬性的(type)不能作為輸入而已(這裡就是submit),而form元素你完全可以將它理解為是一個袋子,將所有使用者輸入資料到裝在它裡面之後用 來提交回服務端處理,但對於form元素值得注意的是method屬性,一般來說有get與post兩種方法,其實不要想得太複雜(因為深入的不需要太理解,對於後續的內容沒有太多關係,如 有興趣不妨可以使用瀏覽器的調試工具查看要求標頭部資訊與發送資訊,例如firebug),表現出來就是,使用get提交表單的話所有的input元素的值將會在地址欄處出現,而post則不會, 例如使用get提交此表單後的瀏覽器地址欄
| 代碼如下 |
複製代碼 |
http://localhost/mytest/token/form.php?data=test&submit=%E6%8F%90%E4%BA%A4 |
post則在 地址欄看不到了,使用fiebug可以看到如下資訊
可以簡單認為get是顯式傳送資料的,而 post則是隱式傳送資料的,但還有一個很大區別的是post支援更多更大的資料傳送。
Next,當表單代碼寫好了,那麼讓我們來進行伺服器指令碼的編寫(這裡就是PHP)。很簡單 ~
| 代碼如下 |
複製代碼 |
if ( isset( $_POST['submit'] ) ) { //表單提交處理 $data = isset( $_POST['data'] ) ? htmlspecialchars( $_POST['data'] ) : ''; //Insert or Update資料庫 $sql = "insert into test (`string`) values ('$data')"; //do query echo $sql; } ?> |
因為這裡是post傳送資料的,所以使用PHP的$_POST全域變數就能擷取到表單提交的資料,所有使用post方法的表單資料提交到服務端都會被儲存在這個$_POST全域變 量中,不妨可以試試print_r( $_POST )這個變數你就明白了。
首先檢查一下是否在$_POST數組裡面存在submit,如果存在則證明是表單提交過來的,正如asp.net中好像有個 叫ispostback的一樣,只是這樣沒那麼嚴謹而已,但是不要緊之後會解決這個問題的。
之後接收輸入框的資料,就是$_POST['data'],別忘了使用htmlspecialchars對這個進 行一下html過濾,因為防止輸入了html標籤或javascript造成問題(貌似叫做XSS漏洞)。最後就是拼接到sql語句中送入資料庫跑了(只是這裡paperen並沒有很詳細使用一些操作資料庫的 函數例如mysql_query,有興趣自己完成它)。恭喜,到了這裡你已經順利地完成了一個資料錄入的功能了,但是有個地方你總得改善吧,插入資料後總得給操作者一個提示吧~~至少提示 我操作失敗還是成功。所以整個代碼paperen寫成以下樣子。
| 代碼如下 |
複製代碼 |
if ( isset( $_POST['submit'] ) ) { //表單提交處理 $data = isset( $_POST['data'] ) ? htmlspecialchars( $_POST['data'] ) : ''; //connect mysql_connect( 'localhost', 'root', 'root' ); //select db mysql_select_db( 'test' ); //設定字元集防止亂碼 mysql_query( 'set names "utf8"' ); //SQL $sql = "insert into `token` (string) values ('$data')"; //query mysql_query( $sql ); $insert_id = mysql_insert_id(); if ( $insert_id ) { $state = 1; } else { $state = 0; } } ?> && $state ) { //資料插入成功 ?> 插入成功 返回 作 ?> |
html的聲明與head還有body都省略了,對比於一開始的代碼其實主要是實現了真正插入資料庫動作與給出 了操作反饋(通過$state變數),不妨自己拷貝代碼然後試試(當然請根據自己實際情況修改資料庫操作部分的代碼)。代碼正常,邏輯沒問題,但是有個問題,就是在顯示插入成功後再重新整理頁 面又會執行了表單處理動作,又插了一遍資料!這就是所謂的重複插入問題。在放出解決方案之前您可以自己思考一下該如何解決。
你會不會認為是接收資料與顯示處理結果都是 這個頁面所以才會導致這個問題?也對,也可以這麼認為,使用一些調試工具你會發現,瀏覽器還對post的資料進行了保留,故在提交完表單後再重新整理的話該post資料會重新提交了一遍。
如果有辦法將瀏覽器的這個臨時儲存的post資料清空掉不就解決問題了,但服務端是沒法 做到這點的,因為這是瀏覽器自身的事情,要麼我們就重新導向了不然再重新整理還是會重複提交資料。
到目前為止也許你已經瞭解到重複提交的意思與問題的惡劣所在,如果 你不是選用重新導向的辦法那麼就得另外想一個辦法了,所以令牌解決辦法就是這麼過來的。
正如令牌本身代表著許可權,操作權,身份標誌等等,所以我能不能為我的表單加上這麼 一個身份標誌,在用戶端每次請求這個表單的時候同時產生一個令牌其掛鈎,在提交時再進行判斷,正確則接收並處理表單。實現本質就是如此,而反映到具體實現上,就需要用到一種叫 session的東西。關於session的解析,參見wiki
簡單的理解就是session也是一種令 牌的概念,所以你可能會很驚奇,“什麼我已經使用了令牌?!”,是的,但是我們要實現的不僅僅是session而是在其基礎上附加一些資料來實現我們想要的表單令牌。So let's do it!
session在php中也是被存放在$_SESSION這個超級全域變數裡面的,啟用起請使用session_start(),關於其他服務端指令碼原理一樣,只是可能調用方法名不一致而已。加入 token後的代碼如下:
| 代碼如下 |
複製代碼 |
//開啟session session_start(); if ( isset( $_POST['submit'] ) && valid_token() ) { //表單 提交處理 } /** * 產生令牌 * @return string MD5加密後的時間戳記 */ function create_token() { //目前時間戳 $timestamp = time(); $_SESSION['token'] = $timestamp; return md5( $timestamp ); } /** * 是否有效令牌 * @return bool */ function valid_token() { if ( isset( $_SESSION['token'] ) && isset( $_POST['token'] ) && $_POST['token'] == md5( $_SESSION['token'] ) ) { //若正確將本次令牌銷毀掉 $_SESSION['token'] = ''; return true; } return false; } ?> 插入成功 返回 |
部分代碼paperen這裡省略,因為並不是重點,其實加的 東西只有3處:
第一,在表單結束前加入了一個input元素,記得type為hidden(隱藏欄位)
第二,增加了兩個函數,create_token與valid_token,前者用來產生令牌 的後者用來驗證令牌的
第三,在if中多加一個條件,valid_token
那就大功告成了,很簡單,而且所有的東西都聚集在新加的兩個函數中。paperen這裡使用的令牌很 簡單就是時間戳記,將請求表單時的時間戳記儲存到$_SESSION['token']中,那麼驗證令牌就明白了,就是檢查用戶端提交過來的$_POST['token']是否與md5後的$_SESSION['token']一致 就行了,當然還要加上存在$_POST['token']與$_SESSION['token']這兩個變數才行。
你可以將這個簡單的令牌模式封裝得更加棒並擴充一下功能,例如加上表單提交逾時驗證 也是個不錯的動手機會。
最後附上之前paperen擴充codeingeter的Form_validation類檔案,主要是擴充上令牌與表單逾時。壓縮包中welcome.php是控制器檔案,請放置到 applicationcontroller中(如不想增加這個控制器可以開啟然後將token方法複製下來放到已有的其他控制器中);MY_Form_validation.php請放到applicationlibraries中。
codeingeter的Form_validation類檔案代碼
| 代碼如下 |
複製代碼 |
class Welcome extends CI_Controller { public function index() { $this->load->view('welcome_message'); }
public function token() { $this->load->helper( array('form') ); $this->load->library('form_validation'); if ( $this->input->post( 'submit' ) && $this->form_validation->valid_token() ) { //nothing //valid_token已經包含令牌逾時與令牌正確的判斷,若要啟用令牌逾時,請將token_validtime設定為非0 echo 'ok'; } //產生表單令牌 $token = $this->form_validation->create_token(); //form example echo form_open(); echo form_input('token', ''); echo $token; echo form_submit('submit', 'submit'); echo form_close(); } } |
form_validation_token
| 代碼如下 |
複製代碼 |
/** * @abstract 繼承CI的Form_validation類在其基礎上增加令牌 */ class MY_Form_validation extends CI_Form_validation { /** * 令牌索引值 * @var string */ var $key = 'token'; /** * 表單令牌有效時間(秒) * @abstract 如果某些表單需要限制輸入時間,則設定該值,為0則不限制 * @var int 秒 */ var $token_validtime = 5; /** * 偵錯模式 * @var bool */ var $debug = false; /** * CI對象 * @var */ private $_CI; public function __construct() { parent::__construct(); $this->_CI =& get_instance(); //如果配置沒有填寫encryption_key $encryption_key = config_item('encryption_key'); if ( empty( $encryption_key ) ) $this->_CI->config->set_item( 'encryption_key', $this->key ); //如果沒有裝載session if ( !isset( $this->_CI->session ) ) $this->_CI->load->library('session'); } /** * 設定令牌有效時間 * @param int $second 秒數 */ public function set_token_validtime( $second ) { $this->token_validtime = intval( $second ); } /** * 擷取表單令牌有效時間 * @return int 秒數 */ public function get_token_validtime() { return $this->token_validtime; } /** * 驗證表單令牌是否合法 * @return bool */ public function valid_token() { if ( $this->debug ) return true; //擷取session中的hash $source_hash = $this->_CI->session->userdata( $this->key ); if ( empty( $source_hash ) ) return false; //判斷是否逾時 if ( $this->is_token_exprie() ) return false; //提交的hash $post_formhash = $this->_CI->input->post( $this->key ); if ( empty( $post_formhash ) ) return false; if ( md5( $source_hash ) == $post_formhash ) { $this->_CI->session->unset_userdata( $this->key ); return true; } return false; } /** * 產生表單令牌(連同input元素) * @return string */ public function create_token( $output = false ) { $code = time() . '|' . $this->get_random( 5 ); $this->_CI->session->set_userdata( $this->key , $code); $result = function_exists('form_hidden') ? form_hidden( $this->key, md5( $code ) ) : ''; if ( $output ) { echo $result; } else { return $result; } } /** * 擷取隨機數(自己可以擴充) * @param int $number 上限 * @return string */ public function get_random( $number ) { return rand( 0, $number ); } /** * 判斷表單令牌是否到期 * @return bool */ public function is_token_exprie() { if ( empty( $this->token_validtime ) ) return false; $token = $this->_CI->session->userdata( $this->key ); if ( empty( $token ) ) return false; $create_time = array_shift( explode('|', $token) ); return ( time() - $create_time > $this->token_validtime ); } } |
http://www.bkjia.com/PHPjc/632826.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/632826.htmlTechArticle以前看過很多各種防止表單重複提交js或jquery程式,但這種只是一個簡單的方法,如果我們不從這個頁面提交表單,直接找到接受資料的頁面...