• ベストアンサー

php 投票システム

データベースと連動した投票システムを考えております。 phpとMySQLを使用したもので、対応するボタンを押すと1票投じられるというものです。 この仕組み自体は実現できたのですが、 ブラウザの更新ボタンを押すと、『情報を再送信する必要があります』と表示され、『はい』を押すと、自動的に1票投じられてしまいます。 スクリプトは以下のようになるのですが、上記のような不具合を修正するためにはどうすればよいのでしょうか。 アドバイス、具体的なソース、また、他の部分における修正すべき箇所など、ご指摘いただきたいと思います。 よろしくお願いいたします。 <?php echo "<table border=0>"; echo "<tr><td colspan=4 align=left><img src=logo-touhyou.gif></td></tr>"; echo "<tr><td><form method=post action="; echo $_SERVER["PHP_SELF"]; echo "><input type=hidden value=1 name=posi><input type=image src=logo-posi-s.gif border=0></form></td>"; echo "<td><form method=post action="; echo $_SERVER["PHP_SELF"]; echo "><input type=hidden value=1 name=deep><input type=image src=logo-deep-s.gif border=0></form></td></tr></table>"; $posi=$_POST["posi"]; $deep=$_POST["deep"]; if ($posi==1){ $server="mysql..jp"; $dbname="LA"; $user="LA"; $pass=""; $conn = mysql_connect($server,$user,$pass); $conndb = mysql_select_db($dbname); $sql="SELECT posi FROM test WHERE id =1 LIMIT 1"; $res=mysql_query($sql); while($row = mysql_fetch_assoc($res)) { $row_con=mb_convert_encoding($row["posi"], "shift_jis", "auto"); echo $row_con; echo "<br>"; $number1=1; $after=$row_con+$number1; echo $after; echo "<br>"; mysql_query("LOCK TABLES test WRITE"); $sql_1="UPDATE test SET posi='$after3' WHERE id =1 LIMIT 1"; $ins=mysql_query($sql); mysql_query("UNLOCK TABLES"); if ($ins_1){ echo "ポジティブ投票完了"; } $sql_2="SELECT posi FROM test WHERE id =1 LIMIT 1"; $res_2=mysql_query($sql_2); while($row_2 = mysql_fetch_assoc($res_2)) { $row_con_2=mb_convert_encoding($row_2["posi"], "shift_jis", "auto"); echo $row_con_2; } } mysql_close($conn); } if ($deep==1){ $server="mysql..jp"; $dbname="LA"; $user="LA"; $pass=""; $conn = mysql_connect($server,$user,$pass); $conndb = mysql_select_db($dbname); $sql2="SELECT deep FROM test WHERE id =2 LIMIT 1"; $res2=mysql_query($sql2); while($row2 = mysql_fetch_assoc($res2)) { $row_con2=mb_convert_encoding($row2["deep"], "shift_jis", "auto"); echo $row_con2; echo "<br>"; $number1=1; $after2=$row_con2+$number1; echo $after2; echo "<br>"; mysql_query("LOCK TABLES test WRITE"); $sql2_2="UPDATE test SET deep='$after2' WHERE id =2 LIMIT 1"; $ins2=mysql_query($sql2_2); mysql_query("UNLOCK TABLES"); if ($ins2){ echo "ディープ投票完了"; } $sql2_2="SELECT deep FROM test WHERE id =4 LIMIT 1"; $res2_2=mysql_query($sql2_2); while($row2_2 = mysql_fetch_assoc($res2_2)) { $row_con2_2=mb_convert_encoding($row2_2["deep"], "shift_jis", "auto"); echo $row_con2_2; } } mysql_close($conn); } ?>

質問者が選んだベストアンサー

  • ベストアンサー
  • wp_
  • ベストアンサー率54% (132/242)
回答No.2

多重投稿を防ぎたいということでしょうか。 そうであれば仕組みを根本から考え直す必要があるでしょう。 たとえばDBは単純にボタンが押された回数をインクリメントするのではなく、 投稿した人間のIPアドレスなどの情報と一意のキーとしたテーブルを設計する必要があります。 ボタンが押されたとき、投稿者のIPアドレスを既存の入力済みIPアドレスでチェックし 既存であれば「投稿済みです」のメッセージを出すなどの仕組みを作れば良いです。 集計の際はクエリでcountをとるようにします。 この辺の設計思想はデータウェアハウスという単語について調べると良いでしょう 多重投稿OKだけどF5アタックされるのが嫌だ、という場合は  1.有効期限付きのクッキーをクライアントに食わせ、残っている場合はインクリメントしない  2.$_SESSIONで値を保持させ、残っている場合はインクリメントしない  3.投稿フォームにhiddenで有効期限を記述し、有効期限が過ぎた送信はインクリメントしない  4.上記のようなDB設計とし、ユーザごとに「投稿された時間」のカラムを持たせる。   フォームに「投稿された時間」、つまりフォームを表示した時間を持たせる。   ボタンが押されるたびにDBの「最新の投稿された時間」とフォームから送信された「投稿された時間」をチェックし   同一だった場合はレコードを追加しない などと言った手法があります。 ただ、1,2はクッキーが無効にされていると意味がないので 3が一番お手軽でしょう。 4は位置から作り直さねばならんので少し面倒かもしれませんね。

nori1969
質問者

補足

お礼が遅くなり、申し訳ございません。 丁寧な説明に感謝します。 データウェアハウスという概念も参考になりました。 現時点での課題は「更新時に自動的に投票されることを防ぐ」ことなのですが(つまりご指摘のF5アタック)、多重投稿に関しても同時にNGとしたいので引き続き、ご指摘の各点に関して検討します。 セッションについてはまだ勉強しておらず(早急の課題ではあるのですが)、1・2に関しては現時点では不可能です。 ですので、3あるいは4の方向で考えています。 ここで質問なのですが、 3は、ページを開いた時点の時間を元に一定の有効期限を設定するということでしょうか。 4は、会員制を前提としたものでしょうか。 (ユーザーテーブルをDBに作成し、管理するということでしょうか) ご回答いただけると幸いです。

その他の回答 (3)

  • wp_
  • ベストアンサー率54% (132/242)
回答No.4

理解の助けになればと思いテーブルのサンプルを。 実際には投稿されたものなどもここに入ることになります。 ip : IPアドレス,というかREMOTE_ADDR send_time : time()で取得されたフォームの表示時間(epoch) +---+------+ | ip | send_time | +-------+------+ | hoge.co.jp | 1193896700 | ←hoge.co.jpさんの一回目投票 | moge.co.jp | 1193896716 | ←moge.co.jpさんの一回目投票 | hoge.co.jp | 1193897016 | ←hoge.co.jpさんの二回目の投票(5分後) | hoge.co.jp | 1193898016 | ←hoge.co.jpさんの三回目の投票(もっとあと) +-------+------+ select max(send_time) from TABLE where ip="送信者IP" group by ip これで最新投稿の時刻を取得できますな。

nori1969
質問者

お礼

再度詳細な説明をありがとうございます。 全くもって頭の下がる思いです。 ユーザー管理に関しては、仰る通りテーブルを別途作成し、運用してゆくことにしました。 多重投稿防止に関しては、頂いたアドバイスを実行したく思いましたが、現在の私のスキル、理解度からすると、現段階では実現できないようです。 ただ、将来的にはこういった方向で進めてゆきたいと思っております。 単に多重投稿を防止するといっても、様々な方式があり、一筋縄ではいかないこと、そして、有効期限設定でコントロールできること、色々勉強になりました。 重ね重ね感謝いたします。 ありがとうございました。 ただ今、検索機能に挑戦しておりますので、また質問しました際にはご協力いただけますよう。

  • wp_
  • ベストアンサー率54% (132/242)
回答No.3

>3は、ページを開いた時点の時間を元に一定の有効期限を設定するということでしょうか。 そのとおりです。 ただ、ユーザさんがソースを見ると「あぁこれは有効期限だな」と一発でバレる恐れがあるので 現在時刻(フォームを表示した時刻)を送信したほうが良いと思います。 いずれにせよ、フォーム画面を再読み込みしない限り多重投稿されることはなくなります。 具体的な例を示しますと、 フォーム表示がform.phpで行われるとして、そのなかで <input type="hidden" name="form_time" value="<?= time() ?>" /> と言ったような記述をします。 その情報を受け取るcomplete.phpのなかで define("TIME_LIMIT", 60 * 5); // 秒単位、この場合だと5分 // complete.phpの実行時がフォーム表示時の時間+タイムリミットより大きい(過ぎた)場合 if( time() > $_POST["form_time"] + TIME_LIMIT ) {  // エラー処理 } という処理を頭に追加します。 ですがこの方法にも穴がありまして、 form_timeの値が改ざんされると意味がないということです。 // 999999999とか入れられて連続送信された場合も有効投票になってしまいます。 厳密にやりたい場合はform_timeの値を復号化できる暗号を用いて暗号化したのちに 送信したほうがよいです。 暗号化はcryptが一般的ですが、敷居があると感じる場合はひとまず考えず 制作してもよいと思います。(値の改ざんは知ってないと出来ませんし) >4は、会員制を前提としたものでしょうか。 >(ユーザーテーブルをDBに作成し、管理するということでしょうか) 必ずしも会員制ではなくてもかまいません。が、ユーザの入力を管理するテーブルは必要です。 その際、DBに保持するとき一意なデータを何にするかにより会員制かどうか決めることになります。 IPアドレスを$_SERVER["REMOTE_ADDR"]から引っ張ってきてinsertすればそれが一意のキーとなり、会員情報登録といったロジックは必要なくなります。 一意のキーをユーザが任意に入力できるものにするのであれば、会員制のサイトになりうるでしょう。 不特定多数を対象とした投票システムであればIPアドレスが無難ですね。 同一IP かつ 送信されたform_timeが同一 であれば弾く、このロジックで F5連打は十分に防げると思います。

  • yambejp
  • ベストアンサー率51% (3827/7415)
回答No.1

処理を終わった時点で、header("Location:hoge.php");で 別ページに飛ばしてください。

nori1969
質問者

お礼

ありがとうございます。 参考になりました。

関連するQ&A