【もくもく勉強会】きゅんきゅんボタンを支える技術の勉強会 in サインカフェ【もぐもぐ会】

小平あたりでCivicTechでそもそもやりたかったこと。。

プログラマーや、ちょっとやってみたい方とか、こんなことしたいな~って人が集まってわいわい言いながら、いろんなことを実現させたり、実装したり、それを社会にも実装するってことがやりたかったことなんです。でも。。なかなか、できなくて(笑)

実現には、

課題 × 興味を持ってくれる人 × タイミング

ってことが大事で。。

これまで勉強会自体は、何度かいろいろやっています。。

やっとそれっぽいことができました(笑)

課題は、キュンキュンボタン!

ラジオの番組で、恋愛相談中に、キュンキュンしたら押してもらって、何キュンキュンだったかを、リアルタイムで、カウントするというもの。

まずは、音だけバージョンを作りました。

【WoT】キュンキュンボタン♪を作ろう!Two Who Nightの恋愛相談用のツール【その1】
WoT 小平あたりでCivicTechの辞書によると。。定義が以下のように書かれています WoT: IoTに続く新しいバズワードにな...
【WoT】なみなみ と よっぴ の キュンキュンボタン♪ 段差でダンサーズ 【Two Who Night】
WoT シリーズ 前回は、とりあえず自分の声で作成しましたが。。 ここは、流石の段差でダンサーズ 自分から言い出しているので...

それに、なんと、ナカさんが、サーバーサイドを作ってくれて、カウンター付きにしてくれました。

現在のバージョンは、こちらです。

これ、とってもくだらないWoT:Waste of Technology つまり、技術の無駄遣いです(笑)

でも。。Webアプリの基本がわかってとても素晴らしい くだらなさだと思うんです。

今回、ナカさんに教えてもらった、とっても無駄遣いな技術をはじめに紹介してみようかとおもいます!

こういうことに、少しでも興味もってもらえて、一緒にやろう!って人が出て来てくれることを願ってます!(笑)

その前にもぐもぐ会からでしたけど(笑)

やってる”てい”で写真を撮りつつ。。その前に。。ごはんがあるという展開(笑)

それでは。。WoTの紹介を。。

使っている技術 その1概要

クライアントサイド(これは、スマホとかPCのブラウザ)で動くプログラムは、HTML5CSSJavaScript でできています。

ボタンを配置したり、グラフを書いたり、キュンキュンと声を鳴らしたりしています。

ボタンを押した時に、画面全体を読み込まなくてもいいように、AjaxDOMという技術も使われています。

サーバーサイド(これはサーバー)で動くプログラムです。今回は、ボタンが押された時に、カウント数を足し算して保存したり、何回押されているのかをクライアントサイドに教えるプログラムですね。

今回は、PHPというプログラム言語で書かれて、データベースは使わずに、XMLというファイル形式(正確にはJSONとして扱うのですが)でカウンタが保存されています。

まず。。ここで。。嫌になっちゃいますかね?(笑)

図で書くと、こういうことです。 スマホなどの中で動くプログラムと、サーバで動くプログラムがあるってことで、スマホでボタンが押されたら、キュンキュン と音を出しながら、サーバーに、ボタンが押されたよ!っていうことを伝えて、サーバはそれをカウントし、クライアントは何回押されたか?を、サーバーに教えてもらって、表示しています。

無駄にハードルを上げる仕様(笑)

単純に、カウントを見せるだけなら、実はクライアントサイドだけでもできますが。。。合計をとることができません。。 そして、このキュンキュンをよりリアルタイムに見せたいということで、メーターとグラフで出そう!っていう仕様にしています(笑)

で。。このカウンターの作りに工夫がされていました!!

使っている技術 その2 サーバーサイドのPHP

今回、Google のFirebaseを使おうかという話もあったのですが。。まだまだ現役で早く動作するPHPを使ってくれました(このWordpressもPHPで書かれています)

Countup.php

ボタンが押された時に、カウンターを1つ増やすことがメインのプログラムです。が。。いろんな工夫がされています。一つは排他制御で、他とぶつからないようにファイルロックをかけたりしています。最大の工夫ポイントは、カウンターが1秒ごとに何回押されたのか?を収納されるようになっているところです。

<?php

// XML保存するデータ数
$FrameCount = (60); // 60秒 60個のフレームを作るようにしています
$nowTime = time(); // 現在のサーバータイム(秒数)を取得します

$TimeIndex = $nowTime % $FrameCount; // どこのフレームに書き込むかを計算

// 排他スタート
$lockfilename = 'lock'; // 排他用のファイル名
//**ロック用ファイルのオープン**
$lockfp = fopen($lockfilename,'w');
//**ロック用ファイルのロック**
flock($lockfp, LOCK_EX);

// CounterXMLの読み込み
$xml = simplexml_load_file('./Counter.xml');
$TimeCount = $xml->xpath('/Counter/Sec/TimeCount[@Index="'.($TimeIndex).'"]');
if(count($TimeCount) > 0 )
{
// Indexが合っていてもTimeが違う場合は、過去のデータなので0扱いにする。
if((int)$TimeCount[0]["Time"] == (int)$nowTime)
{
// すでに同じ時間のエレメントが存在する場合は、カウントアップ
$TimeCount[0]["Value"] = (int)$TimeCount[0]["Value"] + 1;
}else {
// エレメントは存在するが、時間が異なる場合は、時間を更新してカウントを1とする。
$TimeCount[0]["Time"] = $nowTime;
$TimeCount[0]["Value"] = 1;
}
}else 
{
// エレメントが存在しない場合は追加する、
$SecRootItem = $xml->xpath('/Counter/Sec');
$result = $SecRootItem[0]->addChild("TimeCount");
$result[0]['Index'] = $TimeIndex;
$result[0]['Time'] = $nowTime;
$result[0]['Value'] = 1;
}

$DayIndex = date('Ymd') ; // 本日付 例:20200220

// デイリーのカウンター
$DayCount = $xml->xpath('/Counter/Day/DayCount[@Index="'.($DayIndex).'"]');
if(count($DayCount) > 0 )
{
// 同一日付のカウンターが存在する場合は、カウントアップ
$DayCount[0]["Value"] = (int)$DayCount[0]["Value"] + 1;
}else 
{
// 同一日付のカウンターが存在しない場合は、タグを作成してカウントを1とする。
$SecRootItem = $xml->xpath('/Counter/Day');
$result = $SecRootItem[0]->addChild("DayCount");
$result[0]['Index'] = $DayIndex;
$result[0]['Value'] = 1;
}

// XMLの更新
$xml->saveXML('./Counter.xml');

//**ロック用ファイルのロックの開放**
flock($lockfp, LOCK_UN);
//**ロック用ファイルのクローズ**
fclose($lockfp);
// 排他終了

?>

このcountup.phpが動くとCounter.xml の 中身に、下のように書き込みがされます。<Day>という部分には、1日分のカウントが Valueというところに入り、<Sec>という部分には、秒ごとに分けられたValueに入ることで、あとで、グラフが書けるようなデータ構造になっているってことです。

<?xml version="1.0"?>
<Counter>
<Day><DayCount Index="20200208" Value="824"/><DayCount Index="20200223" Value="24"/></Day>
<Sec><TimeCount Index="59" Time="1582469999" Value="2"/><TimeCount Index="0" Time="1582470000" Value="1"/><TimeCount Index="1" Time="1582470001" Value="2"/><TimeCount Index="24" Time="1581174924" Value="1"/><TimeCount Index="25" Time="1581174925" Value="2"/><TimeCount Index="26" Time="1581176546" Value="5"/><TimeCount Index="27" Time="1582474887" Value="1"/><TimeCount Index="28" Time="1581174928" Value="2"/><TimeCount Index="7" Time="1581176527" Value="5"/><TimeCount Index="8" Time="1581176528" Value="5"/><TimeCount Index="21" Time="1581173061" Value="1"/><TimeCount Index="52" Time="1581176632" Value="2"/><TimeCount Index="53" Time="1581170093" Value="10"/><TimeCount Index="4" Time="1581176524" Value="6"/><TimeCount Index="5" Time="1581176525" Value="5"/><TimeCount Index="6" Time="1581176526" Value="5"/><TimeCount Index="9" Time="1582470009" Value="2"/><TimeCount Index="10" Time="1581176530" Value="6"/><TimeCount Index="11" Time="1582470011" Value="1"/><TimeCount Index="12" Time="1582470012" Value="1"/><TimeCount Index="13" Time="1581170053" Value="4"/><TimeCount Index="14" Time="1581171194" Value="1"/><TimeCount Index="15" Time="1582470015" Value="2"/><TimeCount Index="16" Time="1581173656" Value="1"/><TimeCount Index="17" Time="1581176537" Value="5"/><TimeCount Index="18" Time="1581176538" Value="5"/><TimeCount Index="32" Time="1581176252" Value="1"/><TimeCount Index="33" Time="1581173853" Value="1"/><TimeCount Index="34" Time="1581176254" Value="1"/><TimeCount Index="35" Time="1581176675" Value="1"/><TimeCount Index="36" Time="1581176676" Value="3"/><TimeCount Index="37" Time="1581176677" Value="5"/><TimeCount Index="38" Time="1581176678" Value="5"/><TimeCount Index="42" Time="1582471062" Value="1"/><TimeCount Index="43" Time="1582471063" Value="3"/><TimeCount Index="41" Time="1582471061" Value="2"/><TimeCount Index="44" Time="1581176684" Value="1"/><TimeCount Index="2" Time="1582470002" Value="3"/><TimeCount Index="3" Time="1581176523" Value="5"/><TimeCount Index="57" Time="1582469997" Value="1"/><TimeCount Index="49" Time="1581176629" Value="5"/><TimeCount Index="30" Time="1581175710" Value="1"/><TimeCount Index="55" Time="1581174955" Value="1"/><TimeCount Index="31" Time="1581172891" Value="1"/><TimeCount Index="39" Time="1581176679" Value="5"/><TimeCount Index="54" Time="1581174774" Value="1"/><TimeCount Index="56" Time="1582469996" Value="1"/><TimeCount Index="58" Time="1582469998" Value="1"/><TimeCount Index="40" Time="1581176680" Value="1"/><TimeCount Index="45" Time="1581176685" Value="4"/><TimeCount Index="46" Time="1581176686" Value="4"/><TimeCount Index="50" Time="1581176630" Value="6"/><TimeCount Index="51" Time="1581176631" Value="5"/><TimeCount Index="47" Time="1581176687" Value="4"/><TimeCount Index="48" Time="1581176628" Value="5"/><TimeCount Index="19" Time="1581176539" Value="2"/><TimeCount Index="20" Time="1581173060" Value="1"/><TimeCount Index="22" Time="1581174922" Value="1"/><TimeCount Index="23" Time="1581174923" Value="1"/><TimeCount Index="29" Time="1581174929" Value="1"/></Sec>
</Counter>

load.php

今度は、グラフを書くために、Counter.xmlの内容を読み込んでくるプログラムです。

Countで工夫しているため、いろいろな技を駆使して読み込んできています(笑)。最終的に、読み込んだファイルを、JSON形式にして、クライアントサイドに返事をしてあげています。

<?php

// XML保存するデータ数
$FrameCount = (60); // 60秒

// 現在のサーバー時間秒
$nowTime = time(); // 現在の秒数
$CurrentTime = $nowTime - $FrameCount; // 読み込み時間秒 (初期値を現在日時-フレーム数することで全てのフレームを読み込むようにする)

if( isset($_GET["LastTime"]) )
{
// URLパラメータにLastTimeが指定される場合
if($_GET["LastTime"] > $CurrentTime)
{
// LastTimeが フレームの最初の時間よりも新しければ、読み込み開始位置をパラメータの時間に変更する。
$CurrentTime = (int)$_GET["LastTime"];
}
}

// CounterXMLの読み込み
$xml = simplexml_load_file('./Counter.xml');

$retValues = array(); //秒ごとのカウンタ値を入れる配列

// 読み出し位置が今の時間の1秒前まで実行
while ($CurrentTime < $nowTime)
{
$Cnt=0;
// カレント時間秒数をフレーム数で割った余りをインデックスとする
$TimeCount = $xml->xpath('/Counter/Sec/TimeCount[@Index="'.($CurrentTime % $FrameCount).'"]');
if(count($TimeCount) > 0 )
{
// 対象のタグが存在した場合
// Indexが合っていてもTimeが違う場合は、過去のデータなので0扱いにする。
if((int)$TimeCount[0]["Time"] == $CurrentTime)
{
// カウンタ値を読み込む
$Cnt = (int)$TimeCount[0]["Value"];
}
}else {
// 対象のタグが存在しない場合
$Cnt = 0;
}
// レスポンス用の構造体に追加する
$retValues[] = array('time'=>$CurrentTime, 'y'=>$Cnt);

// カレント時間を1秒進める
$CurrentTime = $CurrentTime + 1;
}

// 現在のカウンタは過去5秒分の合計
$NowCnt=0; // 現在のカウント数
$NowCurrentTime = $nowTime - 5; // カレントの初期値を5秒前に再設定

// 読み出し位置が今の時間まで実行
while($NowCurrentTime <= $nowTime)
{
$NowTimeCount = $xml->xpath('/Counter/Sec/TimeCount[@Time="'.$NowCurrentTime.'"]');
if(count($NowTimeCount) > 0 )
{ 
// 対象のタグが存在した場合
// Indexが合っていてもTimeが違う場合は、過去のデータなので0扱いにする。
if((int)$NowTimeCount[0]["Time"] == $NowCurrentTime)
{
// 存在した場合、現在のカウント値に追加
$NowCnt = $NowCnt + (int)$NowTimeCount[0]["Value"];
}
}

// カレント時間を1秒進める
$NowCurrentTime = $NowCurrentTime + 1;
}

$DayIndex = date('Ymd') ; // 本日付 例:20200220

// デイリーのカウンターを取得
$DayCount = $xml->xpath('/Counter/Day/DayCount[@Index="'.($DayIndex).'"]');
$DayCnt = 0;
if(count($DayCount) > 0 )
{
// 本日のカウントエレメントが存在していた場合は、値を格納
$DayCnt = (int)$DayCount[0]["Value"];
}

// 配列をjson_encode関数でJSON形式に変換します。
$retstrct = array('ServerTime'=>$nowTime, 'LastTime'=>$CurrentTime, 'NowCount'=>$NowCnt, 'DayCount'=>$DayCnt, 'values'=>$retValues);
// 構造体をJSONに変換
$json = json_encode($retstrct);

// メタ情報を追加
header('Content-Type: application/json');
header("Access-Control-Allow-Origin: *");
echo json_encode($retstrct);
?>

こちらが、JSON形式のCount.xmlのファイルです。

{"ServerTime":1582474796,"LastTime":1582474796,"NowCount":0,"DayCount":23,"values":[{"time":1582474736,"y":0},{"time":1582474737,"y":0},{"time":1582474738,"y":0},{"time":1582474739,"y":0},{"time":1582474740,"y":0},{"time":1582474741,"y":0},{"time":1582474742,"y":0},{"time":1582474743,"y":0},{"time":1582474744,"y":0},{"time":1582474745,"y":0},{"time":1582474746,"y":0},{"time":1582474747,"y":0},{"time":1582474748,"y":0},{"time":1582474749,"y":0},{"time":1582474750,"y":0},{"time":1582474751,"y":0},{"time":1582474752,"y":0},{"time":1582474753,"y":0},{"time":1582474754,"y":0},{"time":1582474755,"y":0},{"time":1582474756,"y":0},{"time":1582474757,"y":0},{"time":1582474758,"y":0},{"time":1582474759,"y":0},{"time":1582474760,"y":0},{"time":1582474761,"y":0},{"time":1582474762,"y":0},{"time":1582474763,"y":0},{"time":1582474764,"y":0},{"time":1582474765,"y":0},{"time":1582474766,"y":0},{"time":1582474767,"y":0},{"time":1582474768,"y":0},{"time":1582474769,"y":0},{"time":1582474770,"y":0},{"time":1582474771,"y":0},{"time":1582474772,"y":0},{"time":1582474773,"y":0},{"time":1582474774,"y":0},{"time":1582474775,"y":0},{"time":1582474776,"y":0},{"time":1582474777,"y":0},{"time":1582474778,"y":0},{"time":1582474779,"y":0},{"time":1582474780,"y":0},{"time":1582474781,"y":0},{"time":1582474782,"y":0},{"time":1582474783,"y":0},{"time":1582474784,"y":0},{"time":1582474785,"y":0},{"time":1582474786,"y":0},{"time":1582474787,"y":0},{"time":1582474788,"y":0},{"time":1582474789,"y":0},{"time":1582474790,"y":0},{"time":1582474791,"y":0},{"time":1582474792,"y":0},{"time":1582474793,"y":0},{"time":1582474794,"y":0},{"time":1582474795,"y":0}]}

サーバーサイドは分かったかな??(笑)

いろんなやり方があるので、これに限りませんが。。

カウントアップしながら、グラフを書いて。。他の人がボタンを押しても、ちゃんと他の人が押したことが1秒以内に分かるってことが実現できます。

だれか、二人で、このキュンキュンボタンページを開けてみて。。

ひとりの人が、キュンキュンボタン(ハート)を押すと、もう一人の人の方にも、グラフやカウンターが表示されていますよね。リアルタイム(1秒ごとに)で同期するようになっています。

あれ?勉強会の時よりバージョンアップしている(笑)

他の人の端末にもキュンキュンって音がするようになってるのね(笑)

このあたりの実装は、HTML+CSS+JSなので、次回。。

Special Thanks

場所提供あんど一緒に議論 サインカフェ:喜多見さん

講師 中野さん

受講生1 樋口さん

ありがとう!!