【特別企画】 トラフィックエクスチェンジを作ってみよう! > 4、ポイントを記録できるようにしてみる

4、ポイントを記録できるようにしてみる

トラフィックエクスチェンジと言えばフレームにタイマー、そして……ポイントです。 ポイントを貯め込むために実際のアクセス獲得そっちのけで人々が夢中になる。 そう。トラフィックエクスチェンジが「アクセスアップのサイト」だというのは表向きの顔。 トラフィックエクスチェンジには「ポイントサイト」という魔性の顔があるのでした。

というわけで今回はポイントを記録できるように処理を追加していきましょう。魔性だけに。ぎゃふん。

どういう形式で記録するか?

事前にIDを登録するタイプのものであれば、IDに対してポイントがつく、という形式になりますよね。 サーバー側に「ユーザーのレコード」が作成され、そこにIDとポイント(そしてもちろんURLも)が記録される、という形式です。

今回作るのはID登録しないタイプのものですが考え方は同じです。 IDではなく、URLに対して直接ポイントがつく、という形式になります。 サーバー側に「ユーザーのレコード」が作成され、そこにURLとポイントが記録される。

レコードを作成するタイミング

ID登録するタイプのものであれば、事前に申し込んでレコードを作成しておく、ということになります。 ではID登録しないタイプのものの場合はいつレコードが作成されるのか?

「ログイン」が「新規登録」も兼ねる、ということにしましょう。 ログインの時点でまだそのURLのレコードが存在していなければレコードを作成。すでに存在していればそれを読み込む。

「何に」記録するか?

誰かがサーバーの前に24時間張り付いていてくれるなら、その人に覚えていてもらうとよいですね。 またはその人の手元にあるチラシの裏にでもメモしておいてくれればいい。しかし昨今なかなかそんな人はいません。いや、昔だっていませんけど。 したがいまして、何らかの手段で機械的にデータを記録しておくこととなるでしょう。

こういう場合はDBに記録するのがお約束です。まさにそういう目的のためのものですからね。 しかしDBを使うとなると、それ自体でお勉強が必要だし、セッティングもそこそこ面倒です。

今回はトラフィックエクスチェンジを「お手軽に作ってしまう」ということが主旨ですから、DBは使わないこととします。 代わりにどうするかというと、あなたにサーバーの前に24時間張り付いていてもらいます。 じゃなった。ファイルに記録することとします。

ちなみにDBは「データベース」の略です。発音は秘密です。

ファイルの形式

お手軽さのためにDBではなくファイルを使うと言いました。が、 ファイルの方がDBよりもお手軽かどうか、というのは実際には微妙なところがあります。

詳しくは「排他制御」で調べてもらうといいのですが、 ざっくり言うとファイルに記録するとデータが壊れやすい、ということです。 この排他制御というやつをまともにやろうとすると心が病んできます。どう頑張っても結局はどこかに穴が残りますからね。

だから、まともに運営するつもりならやっぱりDBを使うべきなんじゃないの? とは思うところです。 しかし今回はお手軽さ優先ですからね。あんまり厳密なことは気にしないことにします。 というかそもそもトラフィックエクチェンジ自体「まとも」なものじゃないので問題ナシだ。

というわけで今回は排他制御のことをあんまり気にしないというか、気にしなくてもいいような方式にします。 要は「同じファイルを共有しない」「追記だけにする」ってことにすればいいのです。

具体的にはこうです。
ユーザーごとに(URLごとに)ファイルを作成します。そのファイルにポイントを記録していきます。 ポイントは数字を書き換えるのではなく、獲得したポイントをファイルの末尾に追記していきます。消費の場合は符号をマイナスです。 その数字の合計がその時点での所持ポイントです。

いわゆる排他制御というやつがなぜ必要になるかと言うと、サーバー上にある同一のファイルを 複数のユーザーが同時に書き換えようとするとデータが壊れるからです。 そうしたことが起きないように同一のファイルには一度に一人までしかアクセスできないようにする。「排他」とはそういうことです。 つまりキーボードで入力すると「吐いた」や「歯痛」になりがちであることからも、 意味としては大体同じであるということがおわかりになると思います。おわかりにならねぇよ。

その点、上記の方式ならばユーザーごとに別々のファイルですから同じファイルを別々のクライアントが同時に更新しようとしてデータが壊れる、 という心配はかなり少なくなります。 しかも追記だけです。怖いのは「一部を書き換える瞬間」ですからね。書き換えではなく追記だけならそんなに神経質になる必要はありません。

ただ、これだとファイルが肥大化する一方です。 そこで、ポイントの合計がゼロになったらレコード自体を削除、ということにします。 これだって同時に起きるとデータが消えてしまう可能性はあるわけですが、 どの道、ポイントがゼロに近かったのですから、被害は大したことはありません。ん? 蚊でも刺したかな? ってなもんです。

よーし、なんだかうまくいきそうな感じですよね? よね?  細かいことを気にし出すとキリがありません。それを言い出すとDBを使ったって同じってもんよ。なぁ兄弟? ガハハ。

実装

前フリのゴタクな長くなりましたが、いよいよ実際にコードを書いていきます。 上の説明を読んでなんだかよくわからなかったお友達も、実際にコードを書いてみれば実感が沸くハズ。

さて、前回まででサーフ画面のサーバー処理(PHP部分)は以下のようになっていたのでした。

サーフ画面 surf.php
<?php
    
    // ログインフォームからの入力を受け取る
    $url = $_POST['url'];
    
    // セッション開始
    session_start();
    
    // セッションにログイン情報(っていうかURLだけ)を保存
    $_SESSION['url'] = $url;
    
?>

ここに、

・レコードがなければ(新規なら)作成
・レコードを読み込んで現在の所持ポイントを取得

という処理を追加します。

順番に行きましょう。まずは「レコードがなければ(新規なら)作成」です。

サーフ画面 surf.php
<?php
    
    // ログインフォームからの入力を受け取る
    $url = $_POST['url'];
    
    // セッション開始
    session_start();
    
    // セッションにログイン情報(っていうかURLだけ)を保存
    $_SESSION['url'] = $url;

    // 登録されてないなら登録する
    RegisterIfNecessary ( $url );
    
?>

RegisterIfNecessary というのが「そのURLのレコードが未登録ならレコードを作成する」という機能です。 PHPにはこんな機能までついてるなんて実にありがたいですね〜。ほのぼの。 って、そんな機能ついてるワケないんじゃ! ふえーん、ごめんなさいですぅ〜。

ないものは作りましょう。
functions.php とでも言う名前でPHPファイルを作って、そこに書いていくこととします。

functions.php
<?php

//=========================================================
// 未登録なら新規登録
//=========================================================
function RegisterIfNecessary ( $url ) {

    $userDataDir = "userdata/";
    $urlMd5 = md5($url);

    // ファイルが存在しないなら作る
    if ( !file_exists( $userDataDir . $urlMd5  ) ) {
    
        // ファイルを作る
        $f = fopen( $userDataDir . $urlMd5, "w");
    
        // パーミッションを適当に設定しておく
        chmod ( $userDataDir . $urlMd5 , 0666 );

        // URLを書き込む
        fwrite($f, $url . "\n");

        // 初期登録ポイント
        fwrite($f, "10" . "\n");
    
        // 閉じる
        fclose($f);
    }
}

?>

まず冒頭の2行について。

$userDataDir = "userdata/";
$urlMd5 = md5($url);

$userDataDir は見ての通りフォルダ名です。見ての通りは言い過ぎか? それはさておき、このフォルダの中にレコードを(ファイルを)作っていくことにします。 なので事前にフォルダを作っておいてね。

ところでここで問題が1つ。レコードのファイル名は何にしましょうか?  URLごとに1レコードなのだからURLをファイル名にしたらいいんじゃないの?  はい、そういうふうにできればシンプルでわかりやすいのですけど、それができない事情があるのでした。
それは何か?
ファイル名として使用できる文字には制限があるのです。

URLってさ、ほら、スラッシュとか入ってるじゃないですか。ファイル名でスラッシュって使えないわけよ。 ファイル名として使えない文字は他にもいろいろとあって、URLをそのまま使うことはできません。 文字列を変換する必要がある。

そこで md5 です。
詳しい説明は省きますが、どんな文字列もアルファベットと数字に変換してくれるという任せて安心な頼もしいお友達です。 たとえば「http://lovehappy.click/」をMD5化すると、アラ不思議、 「68e9ee035367f199302434f59f613644」という文字列に変身しちゃうのでした。これならファイル名に使えるぜ!  というわけでソースの次の行。URLをMD5化しています。あ、これはPHPの機能なので自分で作らなくてもいいですよ。

あとは何をしているかというと、大体コメント文の通りです。 同名のファイルがなければ作る(=新規登録)。そしてファイルに登録情報を書き込んでいます。 書き込む登録情報は2つ。「URL」と「初期ポイント」です。

URLはファイル名を変換して元に戻せばいいのだからここに記録してなくってもいい…… と言いたいところですが、悲しいことにMD5化した文字列を元に戻すことは不可能なのでした。いや別に悲しむ必要はないか。

あとはパーミッションですね。
この行が必要か? と言われれば別になくてもいいような気がしてきました。 管理者が手作業でファイルを開いて中身を見れると楽しいので、 その程度のパーミッションにしておくといいのかな、とは思います。 それよりも気にするべきは userdataフォルダのパーミッションですね。 PHPがファイルを生成できるようにユルめのパーミッションにしておいてね(実際に作るなら)。
え? パーミッションって何かって? もちろんパーのミッションに決まってるじゃないか! 言わせるな!
(777が最ユルなので動作テストだけならそれにしちゃえ)

ところで何の疑問もなく初期ポイントを付与しちゃってますが、 あんまりふよふよと付与しない方がいいかもしれません。 だってID登録不要でレコードを量産できますからね。 同じページにリダイレクトするURLを一人で大量に登録すればサーフしなくてもポイントがっぽりだぜ! 俺様ってアタマイイ〜。 いや、でもそこは流石のトラフィックエクスチェンジ。 どうせオートサーフなんですから、登録を何度も繰り返すよりも放置した方が楽だったりする。ぎゃふん。

では試してみましょう。

あ、その前に、サーフ画面の方から functions.php を読み込む処理を書いておく必要があるのでした。 冒頭に以下のように書き加えておいてください。

surf.php のPHP部分
<?php
    // 外部のファイルを読み込む
    require_once ("functions.php");
    
    // ログインフォームからの入力を受け取る
    $url = $_POST['url'];
    
    // セッション開始
    session_start();
    
    // セッションにログイン情報(っていうかURLだけ)を保存
    $_SESSION['url'] = $url;

    // 登録されてないなら登録する
    RegisterIfNecessary ( $url );
    
?>

では気を取り直して。ブラウザでアクセス。
ログイン画面でURLを入力して……

サーフ画面へログイン。

さて、userdataフォルダを見てみましょう。

おっ、MD5っぽい名前のファイルが生成されてますね!
拡張子がないですが中身はテキストファイル(のハズ)です。テキストエディタで開いてみましょう。

URLと初期ポイントが書き込まれてます。完璧!

レコードを読み込んで現在の所持ポイントを取得

レコードが記録されるようになりました。 ではそのレコードを読み込む処理を追加しようではありませんか。

サーフ画面 surf.php
<?php
    // 外部のファイルを読み込む
    require_once ("functions.php");
    
    // ログインフォームからの入力を受け取る
    $url = $_POST['url'];
    
    // セッション開始
    session_start();
    
    // セッションにログイン情報(っていうかURLだけ)を保存
    $_SESSION['url'] = $url;
    
    // 登録されてないなら登録する
    RegisterIfNecessary ( $url );
    
    // 自分の情報を取得
    $info = ReadUserDataFile ( md5($url) );
    $myPoint = $info['point'];
        
?>

登録されているレコードを ReadUserDataFile で取得します。
おお、PHPにはそんな便利な機能が……じゃなくて自分で作るんじゃ! ふえ〜ん、ごめんなさいですぅ〜。

仕様としてはこういう感じにします。

・パラメータとしてファイル名(MD5化したURL)を入れると登録情報が得られる。
・登録情報は「URL」と「ポイント」の2つ。

URLは実際にはログイン情報としてすでに持っているのでここでは取得しなくてもいいかもですね。 ここに書いたサンプルソースの中ではポイントだけ取り出しています。 取り出したポイントはタイマー側のフレームにでも表示することにしましょうか。

<!-- タイマーが表示される側 -->
<div id="surfNavi">
    
    MyURL : <?php print $url; ?> <br>
    ポイント : <?php echo $myPoint ?><br>
    
    あと<span id="timer"></span>秒
</div>

うむ。
ではこの夢を実現すべく、さきほどと同様、functions.php に ReadUserDataFile を追加していこうではありませんか。

functions.php(さっき作ったファイル。以下の内容を追加)
//=========================================================
// ユーザーデータを取得
//=========================================================
function ReadUserDataFile ( $userFileName ) {
    
    $info = array();

    $point = 0;
    $url   = "";
    
    $userDataDir = "userdata/";

    // ファイルが存在する?
    if ( file_exists( $userDataDir . $userFileName  ) ) {
        
        $f = fopen ( $userDataDir . $userFileName, "r") or die("ファイルを開けませんでした...");
    
        $rowNumber = 0;
        
        // ファイルの内容を1行ずつ見ていく
        while ( !feof($f) ) {

            $row = fgets ( $f );
                
            // 2行目以降がポイント履歴
            if ( $rowNumber > 0 ) {
            
                $row = trim($row);  // trim は末尾の改行コードを削除する目的
                
                if ( is_numeric ( $row ) ) { // 念のため「数値だよね?」と確認している
                    $point += $row;          // 数値をポイントに加算
                }
            
            // 1行目はURL
            } else {
            
                $url = trim($row);
            }
        
            ++$rowNumber;
        }
    
        fclose($f);
        
    // ファイルが存在しない
    } else {
        // 何もしない
    }
    
    // 結果を格納
    $info['url']   = $url;
    $info['point'] = $point;
    
    return $info;
}

妙に分量が多くて目が痛くなりそうですが、 「ファイルが存在する?」のif文の中カッコの中身が処理の本体です。

ファイルの中身を1行ずつ見ていっています。 1行目はURLとして確保。
2行目以降がポイントというかポイントの履歴ですね。それを加算して所持ポイントを求めています。

そして最終的に $info の中に URLとポイントが格納されて戻り値となります。

では試してみましょう。
まずはログイン画面からアクセス。

URLを入力してサーフ画面にいくと……

おお、所持ポイントが表示されている! やったぜ!

試しにレコードファイルを開いてポイントを書き込んでみてください。
末尾に1行ずつ数字を追加です。合計された数値がこの「ポイント」欄に表示されるはずです。 本来はサーフ完了後に自動的に書き込まれていく予定、というわけですね。次回以降、それをやっていきます。

では恒例のソース全文です。
ログイン画面は変更なしなのでサーフ画面(surf.php)と functions.php の2枚です。

サーフ画面 surf.php
<?php
    // 外部のファイルを読み込む
    require_once ("functions.php");
    
    // ログインフォームからの入力を受け取る
    $url = $_POST['url'];
    
    // セッション開始
    session_start();
    
    // セッションにログイン情報(っていうかURLだけ)を保存
    $_SESSION['url'] = $url;
    
    // 登録されてないなら登録する
    RegisterIfNecessary ( $url );
    
    // 自分の情報を取得
    $info = ReadUserDataFile ( md5($url) );
    $myPoint = $info['point'];
        
?>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>トラフィックエクスチェンジだぜ!</title>
        
        <style>
        
            html,body {
                margin:0;
                padding:0;
                height : 100%;
            }
    
            /*  タイマー側 */
            #surfNavi {
                width : 100%;
                height : 20%;
                background-color : #eeeeff;
            }
    
            /*  宣伝ページ側 */
            #surfFrame {
                width : 100%;
                height : 80%;
                border : 0px;
                position : absolute;
        
            }
        </style>
        
        <script>
    
            // 初期の秒数。
            var time = 10;
    
            // 最初に実行される処理
            function start() {
        
                // 秒数を表示(関数化しました)
                updateTimer();

                // countDownという名前の関数を 1000ミリ秒後に実行
                setTimeout ( countDown, 1000 );
            }
    
            // タイマーの表示を更新
            function updateTimer() {
    
                var timer = document.getElementById("timer");
                timer.innerHTML = time;
            }
    
            // カウントダウンの処理
            function countDown() {
        
                // カウントダウン
                time -= 1;
        
                // そしてゼロ判定
                if ( time <= 0) {
            
                    // ゼロになっていたら「サーフ完了」の処理を実行
                    surfCompleted();

                } else {
            
                    // まだゼロじゃないので、再びタイマーをセット
                    setTimeout(countDown, 1000);
                }
        
                // タイマーの表示を更新
                updateTimer();
        
            }
    
            // サーフ完了の処理
            function surfCompleted() {
        
                /*
                    次回のお楽しみ
                    (あ、これは「複数行のコメント」です)
                */
        
                // 今日のところはタイマーの数字を元に戻して再びカウント続行することにします。
                time = 10;
                updateTimer();
                setTimeout(countDown, 1000);
            }
    

        </script>
        
    </head>
    <body onload="start();">

        <!-- タイマーが表示される側 -->
        <div id="surfNavi">
    
            MyURL : <?php print $url; ?> <br>
            ポイント : <?php echo $myPoint ?><br>
    
            あと<span id="timer"></span>秒
        </div>

        <!-- 宣伝先のページが表示される側 -->
        <iframe id="surfFrame" src="http://xxxxxxxxxxx"></iframe>

    </body>
</html>
functions.php
<?php

//=========================================================
// 未登録なら新規登録
//=========================================================
function RegisterIfNecessary ( $url ) {

    $userDataDir = "userdata/";
    $urlMd5 = md5($url);

    // ファイルが存在しないなら作る
    if ( !file_exists( $userDataDir . $urlMd5  ) ) {
    
        // ファイルを作る
        $f = fopen( $userDataDir . $urlMd5, "w");
    
        // パーミッションを適当に設定しておく
        chmod ( $userDataDir . $urlMd5 , 0666 );

        // URLを書き込む
        fwrite($f, $url . "\n");

        // 初期登録ポイント
        fwrite($f, "10" . "\n");
    
        // 閉じる
        fclose($f);
    }
}

//=========================================================
// ユーザーデータを取得
//=========================================================
function ReadUserDataFile ( $userFileName ) {
    
    $info = array();

    $point = 0;
    $url   = "";
    
    $userDataDir = "userdata/";

    // ファイルが存在する?
    if ( file_exists( $userDataDir . $userFileName  ) ) {
        
        $f = fopen ( $userDataDir . $userFileName, "r") or die("ファイルを開けませんでした...");
    
        $rowNumber = 0;
        
        // ファイルの内容を1行ずつ見ていく
        while ( !feof($f) ) {

            $row = fgets ( $f );
                
            // 2行目以降がポイント履歴
            if ( $rowNumber > 0 ) {
            
                $row = trim($row);  // trim は末尾の改行コードを削除する目的
                
                if ( is_numeric ( $row ) ) { // 念のため「数値だよね?」と確認している
                    $point += $row;          // 数値をポイントに加算
                }
            
            // 1行目はURL
            } else {
            
                $url = trim($row);
            }
        
            ++$rowNumber;
        }
    
        fclose($f);
        
    // ファイルが存在しない
    } else {
        // 何もしない
    }
    
    // 結果を格納
    $info['url']   = $url;
    $info['point'] = $point;
    
    return $info;
}

?>

次回予告

今回はユーザーのレコードを作成し、そこにポイントを記録できるようになりました。
とは言いつつ、何かがおかしい。
サーフしてもポイントが増えないからです。こ、こんなことでトラフィックエクスチェンジと呼べるか!
では次回はサーフすることでポイントが増えていくようにしましょう。
と、言いたいところですが、その前に。
サーフしてポイントを得るには他のユーザーのページが登録されていなければなりません。
登録の仕組み自体は今回作成したのでした。
というわけで次回は登録されている他のユーザーのページを取得してランダムに表示する処理を追加します。 今まではフレームの中にデフォルトページしか表示されてませんでしたが、 そこにユーザーのページを表示していくというわけです。想像するだけでウキウキせざるを得ませんね。お楽しみに〜。

サイトTOPへ戻る
目次へ戻る
前のページ
次のページ