WEBアプリ開発記

~備忘録としてね~

Java8のStream覚えるためにネタとしてロト6の過去データから次回当選予想+Linebotで通知やってみた

データを関数型言語(Scala)でごにょごにょする系のことを今仕事で携わらせてもらってるので、Java8のStreamではどうやって書くんだろ?と、ふと興味を持ちました。
ただコピペするだけじゃつまらんということで、何かやる気が出るネタはないかなーと思っていたところ、ありました。宝くじです。

しょっちゅう買ってるんですが、なかなか当たらないので、過去のデータを使って予想とかできないかな?と思い始めたのです!

ここには詳しく書くつもりはないのでひとまず、作ってみたソースを公開してみました!
github.com

ざっくりと流れを書くと、

loto-dataプロジェクト

  • みずほ銀行さんのロト6の結果ページをスクレイピングする
  • そのデータを軽量のDBであるH2に溜め込んでいく(みずほさんのwebサイトは1年以上前の詳細データがどんどん消えてってしまうため)
  • そのデータをjava内で簡単に扱えるようにRepositoryオブジェクトというMapを作る

loto-data-apiプロジェクト

  • LineBotからのリクエストやブラウザからのリクエストを受け取れるようにする(こちらのページline-bot-sdk-java で chat bot - Qiitaをとても参考にさせていただきました。)
  • リクエストに応じて上記loto-dataライブラリのRepositoryオブジェクトを使って、データを絞ったり加工したりする
  • Stream操作を体に染み付くまで嫌という程に使いまくって!予想を行う!
  • その結果をLineやブラウザに返す
  • これをAWSにデプロイする(Lineがhttpsじゃないとダメなので、Let's Encryptなどを使ってhttps化するのがミソ)

とまぁこんな感じです。

そんなこんなで完成したLinebotの「ロングボ君」でございます!
(※やべーかなーやべーかなー写真とか勝手に使うの)
f:id:jazzmaster0601:20170309004838j:plain:w450

↓↓↓もしよければ以下のQRコードをLINEで友達追加してみてくだされ↓↓↓

会話で過去結果とか次回予想とかしますよ。

でね、結論としてはね、まじで当たらない笑
良い子はこんなくだらないことに時間を使っちゃダメよ。

引用:https://www.mizuhobank.co.jp/takarakuji/loto/loto6/index.html

JavaScriptで高階関数とかカリー化とか

今まで気にも留めずに書いていたけれど、ふと調べたら、なんとなく面白かったと思い、なんとなく久々に書いてみようと思いました!

高階関数

関数を引数に取る関数。コールバックを引数に取る関数と言った方がわかりやすいか。
以下のフィルター関数みたいに引数に関数をもらって中で処理する奴が高階関数

function filter(orgList, callback) {
    var newList = [];
    for(var i = 0; i < orgList.length; ++i) {
        if (callback(orgList[i])) {
            newList.push(orgList[i]);
        }
    }
    return newList;
};

これを呼び出すと、

var list1 = filter([1, 5, 8, 6, 2, 4, 7], function(num) {
    return num > 5;
});
console.log(list1);// 8, 6, 7

var list2 = filter([1, 5, 8, 6, 2, 4, 7], function(num) {
    return num > 3;
});
console.log(list2);// 5, 8, 6, 4, 7

こんな感じになる。

なぜ便利なのかというと、配列の中のどれを弾くかという条件に当たる部分が、filter関数から切り離されているため、
・フィルタリング条件を変えるだけで返ってくる新配列を変更できる
・条件部分以外を使い回しできるため、共通化(ソースコードの削減)ができる

ロジックの共有と個別ロジックの注入をOOPではポリモーフィズムで表現したりするが、関数渡しができる言語だと、親子関係がなくてもロジックを注入できる。

カリー化

上記の高階関数のように、手続きの大部分を共通化し、一部を外出しする方法があるが、今度は渡す側の関数もちょっと共通化してソース減らしてみようということになる。

この際、パラメータに応じた関数を返す共通関数を作っておいておけば良さげ。

function over(condition) {
    return function(num) {
        return num > condition;
    };
};

こんな関数を作って、

var overFive = over(5);

としてやると、もちろんoverFiveという変数は関数を格納してる。
なので、

console.log(overFive(3)); // false
console.log(overFive(6)); // true

だし、以下と等価になるわけだ。

console.log(over(5)(3)); // false
console.log(over(5)(6)); // true

動的に関数が作れるので、こんな風に3以上フィルタもすぐ作れる。

var overThree = over(3);
console.log(overThree(5)); // true
console.log(overThree(2)); // false

このように動的に関数を生み出す関数のこと(関数を作る行為)をカリー化という。

これを使って上記のフィルター関数呼び出しを書き換えると、

var list = filter([1, 5, 8, 6, 2, 4, 7], over(5));
console.log(list); // 8, 6, 7

うおおすげー減ったし、ちょっとソースが文章っぽくなってきた!

おまけ Arrayオブジェクトのprototypeにfilter関数を実装しちゃう

上記のfilter関数をjsのArrayオブジェクトに拡張して実装するともう少し幸せになれそうなので勢いで書いておく。
上記のfilter関数を以下のように書き換える。

Array.prototype.filter = function(callback) {
    var newList = [];
    for(var i = 0; i < this.length; ++i) {
        if (callback(this[i])) {
            newList.push(this[i]);
        }
    }
    return newList;
};

すると呼び出し部分は、

var list = [1, 5, 8, 6, 2, 4, 7].filter(over(5));// この行注目!
console.log(list); // 8, 6, 7

もうこれで完全に文章でしょ処理が。
「配列を、フィルタしてくれ、条件は5以上の数値だけ」と読める。
この辺の技術を体得してソースに表現できるように設計できると神。

最後に全ソース

Array.prototype.filter = function(callback) {
    var newList = [];
    for(var i = 0; i < this.length; ++i) {
        if (callback(this[i])) {
            newList.push(this[i]);
        }
    }
    return newList;
};

function over(condition) {
    return function(num) {
        return num > condition;
    };
};

var list1 = [1, 5, 8, 6, 2, 4, 7].filter(over(5));
console.log(list1); // 8, 6, 7

var list2 = [1, 5, 8, 6, 2, 4, 7].filter(over(3));
console.log(list2); // 5, 8, 6, 4, 7

アプリ紹介 : 簡単シェア文字~複数端末間での楽々テキスト共有アプリ~

前回書いた記事のChannelAPIの機能を使ってアプリを作ってみました。

「 簡単シェア文字~複数端末間での楽々テキスト共有アプリ~」ってアプリです。
webサービスとして、ブラウザからも利用できます!

簡単シェア文字~複数端末間での楽々テキスト共有アプリ~ - Google Play の Android アプリ

パソコンで調べていた気になるお店のURL、スマホに送るときにいちいちメールを立ち上げて。。って面倒だったことはありませんか!?
このアプリは1回設定しておけば自分の持っている端末同士のテキスト情報のやり取りをいつでも簡単に行うことができるアプリです!

f:id:jazzmaster0601:20150613001949p:plain

[スムーズな起動~簡単シェア]
いつも使う端末にアプリをインストールしておいたり、ブラウザのお気に入りやデスクトップにショートカットを置いていけば、いつでもスピーディに起動~テキスト送信が簡単に行えます!

f:id:jazzmaster0601:20150613001956p:plain

[データを保存しないから安全かつスピーディ]
送信されたデータはサーバーに保存されません!端末でアプリを落とせばすぐ消去されます!
そのためスピーディな送受信を可能としています!


[シェアされた文字は簡単にコピー]
シェアした文字は他のアプリなどで使うことを想定して、簡単にコピーできる機能を用意!
URLの場合は自動でリンクになるのでタップするだけでブラウザが起動します!


[接続端末の一覧を表示]
現在あなたが接続しているシェア環境(部屋という)に接続している端末の一覧が表示可能!

f:id:jazzmaster0601:20150613001959p:plain

[ブラウザからもどうぞ!↓↓↓]
アプリでなくてもブラウザ同士でもシェア可能ですよ!
(以下紹介ページにリンクがあります)
http://cosbroths.com/textbroadcaster/cosbroths.com


このアプリでいつでも簡単に文字を共有しましょう!

是非使ってみてくださいm(__)m

簡単シェア文字~複数端末間での楽々テキスト共有アプリ~ - Google Play の Android アプリ

GAEjでChannelAPI利用時の端末~サーバー間接続をしっかり管理しよう - その2

さて、前回の記事にて、詳しく紹介することにした「ChannelAPIの接続管理」の詳細を書き始めます。
順を追って行きましょう!

動作サンプルはコチラ
スマホとブラウザだったり、PCブラウザで2つタブを開いたり何でもいいですが、同じ部屋IDを入力して試してみてください!
また、別の部屋に入っている場合はメッセージが届かないことまで確認してもらえると良いです。

※前回も書きましたが、面倒な人は直接ソースを見てもらった方がいいと思います!github.com
ちなみに、サーバーサイドは例によってSpringMVCを使って楽しています。

ではいきます。

A.HTMLファイル(1枚だけ!)
特に難しい記載はないので、特徴的なとこだけ抜粋。

<!-- 1.jQueryは便利だから読み込む -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<!-- 2.このURLでChannelAPIに必要な接続スクリプトを読み込む -->
<script src="/_ah/channel/jsapi"></script>
<script src="common.js"></script>
<!-- 3.部屋に入るための自作接続クラス -->
<script src="room-connector.js"></script>
<!-- 4.自作接続クラスを扱うスクリプト -->
<script src="index.js"></script>

1.jQuery
特筆すべきもんでもないですが、いろいろ楽になるので読み込みます。

2.jsapi
GAEでサーバーを起動すると"/_ah/channel/jsapi"というURLでChanneAPIへの接続に必要なスクリプトを取得できますので、このように読み込みます。

3.自作接続クラス
特に説明はしませんが、これを使ってサーバー上の部屋との通信を行います。
中身も大したことないのですが、見たい方はソース見てみてくださいね!(prototype使ってないのはすいません汗)

4.それを扱うindex.jsスクリプト
以下に説明します。

cssはクソほども書いていないので省略!


B.index.jsファイル

$(document).ready(function() {
	// 1.まずは簡単接続用のクラスを生成!
	var roomConnector = new RoomConnector();

	// 2.端末IDを画面上に表示
	$('#device-id').text(roomConnector.getDeviceId());

	// 3.サーバーからのプッシュメッセージを受けたときに何をするかをここに記述
	roomConnector.setOnMessage(function(data) {
		if (data.substring(0, 21) === 'joinRoomAndDeviceIds:') {
			// 4.部屋に接続している端末一覧が送られてきたらそれを画面上に表示
			var joinRoomAndDeviceIds = data.substring(21).replaceAll([ '[', ']', ' ' ], '').split(',');
			$('#join-devices').empty();
			joinRoomAndDeviceIds.forEach(function(roomAndDeviceId) {
				var deviceId = roomAndDeviceId.replaceAll(roomConnector.getRoomId() + ':', '');
				$('#join-devices').append('<div>・' + deviceId + '</div>');
			});
		} else if (data.substring(0, 8) === 'message:') {
			// 5.メッセージが送られてきたらそれを画面上に表示
			var message = data.substring(8);
			var html = '<div>' + message + ':' + new Date().toLocaleString() + '</div>';
			$('#receive').append(html);
		}
	});

	// 6.部屋に入るボタンを押したとき
	$('#btn-enter-room').on('click', function() {
		var roomId = $('#enter-room-id').val();
		if (!roomId) {
			alert('部屋IDが空です');
			return;
		}
		// 7.セミコロンが部屋IDに指定されているとバグるのでそれを回避
		if (roomId.indexOf(':') > -1) {
			alert('部屋IDにコロン(:)は利用できません');
			return;
		}
		roomConnector.open(roomId);
		$('#enter-room').hide();
		$('#current-room-id').text(roomId);
		$('#current-room').show();
	});

	// 8.送信ボタンを押したとき
	$('#btn-send').on('click', function() {
		var message = $('#send').val();
		roomConnector.send(message + ':' + roomConnector.getDeviceId());
		$('#send').val('');
	});

});

1.先ほど読み込んだ自作接続クラスをインスタンス化します。

3.サーバーからメッセージが届いたときの処理を自作接続クラスにセットします。

4.joinRoomAndDeviceIdsという接頭文字で始まるメッセージを受け取ったら、参加者一覧を更新する

5.messageという接頭文字で始まるメッセージを受け取ったら、受信欄にメッセージを追記する

6.部屋に入るボタンを押して初めてメッセージのやり取りが可能になる

8.送信ボタンを押したら自作接続クラスのsendメソッドを呼ぶ


クライアント側のソースは大体こんな感じです!
続いて、サーバーサイド行きます。


サーバーサイドは大きく4つの役割で構成されています。

・Room - 部屋自体を表すオブジェクト
・RoomManager - サーバー上に存在するすべての部屋を把握している奴
・RoomConnectController - 部屋への参加、メッセージの送信を受け付ける奴
・ChannelController - チャンネルの接続やブラウザの切断を検知して適切に部屋参加者を操作する奴(ここ大事!)

てな感じです


C.RoomConnectController.java

package sample;

import java.util.Map;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
*
* @author jazzmaster0601
*
*/
@Controller
@RequestMapping("/room-connect")
public class RoomConnectController {

	private String getRoomId(Map<String, String> param) {
		return param.get("roomId");
	}

	/**
	 * 部屋へ参加させます
	 * @param param
	 * @return
	 */
	@RequestMapping(value = "open", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
	@ResponseBody
	public String open(@RequestBody Map<String, String> param) {
		String roomId = getRoomId(param);
		String roomAndDeviceId = param.get("roomAndDeviceId");
		return RoomManager.getInstance().tryToOpenRoom(roomId).join(roomAndDeviceId);
	}

	/**
	 * 部屋へのメッセージを受け取り参加者に配信します
	 * @param param
	 * @return
	 */
	@RequestMapping(value = "send", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
	@ResponseBody
	public String send(@RequestBody Map<String, String> param) {
		String roomId = getRoomId(param);
		String message = param.get("message");
		RoomManager.getInstance().getRoom(roomId).sendMessage(message);
		return "success";
	}

}


1.部屋への参加
リクエストで飛んできた部屋IDに該当する部屋をなければ作ってそこに「roomAndDeviceId」の端末を参加させます。

2.部屋へのメッセージ配信
リクエストで飛んできた部屋IDに該当する部屋の参加者に、リクエストで飛んできたメッセージをプッシュします。


※roomAndDeviceIdってなんやねん?
→次のChannelControllerの説明で詳しく言います。


D.ChannelController.java

package sample;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.google.appengine.api.channel.ChannelServiceFactory;

/**
*
* @author jazzmaster0601
*
*/
@Controller
@RequestMapping("/_ah/channel")
public class ChannelController {

	private String getRoomAndDeviceId(HttpServletRequest req) throws IOException {
		return ChannelServiceFactory.getChannelService().parsePresence(req).clientId();
	}

	private String getRoomId(String deviceId) {
		return deviceId.split(":")[0];
	}

	/**
	 * 部屋への接続が完了したときに呼ばれます
	 * @param req
	 * @return
	 * @throws IOException
	 */
	@RequestMapping(value = "connected", method = RequestMethod.POST)
	@ResponseBody
	public String connect(HttpServletRequest req) throws IOException {
		String roomAndDeviceId = getRoomAndDeviceId(req);
		String roomId = getRoomId(roomAndDeviceId);
		Room room = RoomManager.getInstance().getRoom(roomId);
		room.sendJoinRoomAndDeviceIds();
		return "success";
	}

	/**
	 * ブラウザが閉じられたときに呼ばれます
	 * @param req
	 * @return
	 * @throws IOException
	 */
	@RequestMapping(value = "disconnected", method = RequestMethod.POST)
	@ResponseBody
	public String disconnect(HttpServletRequest req) throws IOException {
		String roomAndDeviceId = getRoomAndDeviceId(req);
		String roomId = getRoomId(roomAndDeviceId);
		RoomManager manager = RoomManager.getInstance();
		Room room = manager.getRoom(roomId);
		room.exit(roomAndDeviceId);
		manager.tryToCloseRoom(roomId);
		room = manager.getRoom(roomId);
		if (room != null) {
			room.sendJoinRoomAndDeviceIds();
		}
		return "success";
	}

}


まず大事なのが、GAEのChannelAPIは接続が確立したとき、"/_ah/channel/connected"というURLに対して、POSTリクエストが飛ぶような設定が可能です。
その設定方法は、WEB-INF以下のappengine-web.xml

<inbound-services>
    <service>channel_presence</service>
</inbound-services>

という記載をすることで動作するようになります。


1.部屋への接続が完了
この際に部屋への参加者が増加したはずなので、参加者全員にそのメッセージを送っています。

2.ブラウザが閉じられたとき
上記XMLの設定をしていると、Channelの接続が切断された際に"/_ah/channel/disconnected"というURLに対して、POSTリクエストが飛ぶようになっています。これを検知して、部屋から適切に参加者を退出させるのです。
そして、参加者が減少したはずなのでその旨を部屋に残っている参加者にメッセージングするのです。


※roomAndDeviceIdにそろそろ答えんかい!

ChannelControllerで処理をする際に、「どの部屋に」送るかという情報が必要なのは分かると思います。
しかし、このリクエストの中では、clientIdという端末のIDしか知ることができません。
そこでセッションを使う方法を考えたのですが、このChannelControllerに対するリクエストはRoomConnectControllerに対するリクエストと別のセッションを作成してしまうようなのです。
さて困ったということで、javascript側で生成できる端末固有のIDに部屋IDも一定のルールで付けてしまえ!という考えの元、roomAndDeviceIdというものにしたわけですね。
これは、

「部屋ID:端末ID」

というただの文字列です。


以上が大体の流れです!

しつこく再度載せておきますね。

動作サンプルはコチラ

ソースはコチラgithub.com


追記:それを踏まえて作ったアプリを紹介します。jazzmaster0601.hatenablog.com

今時いろいろなアプリやサービスがあるので、上記のようなアプリでできることは楽勝なのでしょうが、逆にそれしかできないアプリを超簡素化して作りました。
是非♪

GAEjでChannelAPI利用時の端末~サーバー間接続をしっかり管理しよう - その1

まず、GoogleAppEngine には、いわゆる昨今のWebSocketのようにサーバーからプッシュし、ネット閲覧中の画面を他動的に変更する方法が用意されています。それをChannelAPIといいます。

難しく言いましたが、つまり、ブラウザを立ち上げてそのままほったらかしておいても、サーバー側から何か情報を与えて画面上の情報を変更できる機能を作ることができるのです。

例えば、チャットだったら、ブラウザを立ち上げておけば誰かがメッセージを送信した際に、勝手に画面にそのメッセージが表示されたり、今何か買いたいものを探している人に効率的にマッチした広告を見せてあげたりといったことが可能になるのです。

一番のポイントとしては、サーバーという機械が勝手に何か情報をスマホなどの端末に与えるのではなく、リアルタイムにどこか別の場所にいる何者か人間が情報を与えることができることではないかと思います。

リアルタイムかつインタラクティブにゴージャスな情報を与え合うことができる技術として個人的にはかなり今後イクんじゃないか?と期待しております。(与える側のインセンティブをどうするとかビジネス的な観点はあまり得意でなく考えてないのですが笑)


前置きは長くなりましたが、このWebSocketやChannelAPIなどざっくりいうと、


端末⇔サーバー⇔端末


のように、今どの端末が接続されているかをサーバー自身が管理することで、適切な端末に対して適切なメッセージを送ることができるわけです。

この「サーバーで管理する」という部分、用途には依りますが、個人で実装しなければならないのがとても面倒です!

例えばチャットアプリを作った場合の必要条件は、

・同じ部屋に入った人だけにメッセージが送信されること
 →同じ部屋に入っている人をサーバーで管理する必要

・ブラウザを切った場合に部屋から退場されること
 →ブラウザがOFFになったという通知をサーバーが適切に受け取ることができること

辺りになると思うのですが、案外これがかったるい!


自分がアプリを作っていたところ、この辺に面倒くささを感じたので、ちょっとサンプルアプリとソースを公開してみようと思ったので、ここにそれを紹介しようと思いました。

次回に順を追って詳しく記載しようと思うのですが、とりあえずソースだけ読みたい場合は、

github.com

直でここを見てもらえればと思います!


続きは以下jazzmaster0601.hatenablog.com

WEBサービス紹介 : みんなでランキング~あなたの順位付けは普通?異常?~

くだらないWEBサービス作ってみましたので、紹介します!

みんなでランキングというWEBサービスです!

くだらないので、飲んでる時とかに会話に困ったり、暇になった時にチラッと使ってもらえれば幸いでございます笑

みんなでランキングを開く

■みんなでランキングとは?■
各お題をタップするとランキングお題が表示されます。

f:id:jazzmaster0601:20150501180057p:plain


各お題には並べ替えられる要素があるので好きな順番に並べ替えてチェックボタンを押しましょう。
あなたの回答がみんなの回答とどれだけズレているかがわかります。

f:id:jazzmaster0601:20150501180107p:plain


くだらない診断も一応出ます。 目指せ!マイノリティ!マジョリティ!

f:id:jazzmaster0601:20150501180111p:plain


■あなたもランキングお題を作ってみませんか!■
あなたもみんなに聞きたいランキングお題を作ってみませんか!
サインインすればあなたもランキングお題が作れるようになります!
あなたが作ったランキングお題に対するみんなの回答の集計結果も見れるようになります。
ランキングお題は非公開にして友達にだけURLを共有することで友達同士だけで楽しむこともできます。
TwitterのID、FacebookのIDを利用してのサインイン、また通常のアカウントを作成してのサインインもできます。

f:id:jazzmaster0601:20150501180405p:plain

f:id:jazzmaster0601:20150501180411p:plain


一応Androidのアプリバージョンも作っときやした。
https://play.google.com/store/apps/details?id=jp.co.sbro.yourankit

WEBで表示する場合は以下です。↓↓↓
みんなでランキングを開く

宜しくお願いしますm(__)m

SNSのリンクシェアボタンを簡単に作成できるjavascript作ったっす。

その名もsns-btn-maker!
まぁ名前のセンスは気にしないでほしい。

SNSのシェアボタンって提供されているボタンよりもっとかっこよくしたいと思う方も多いと思うのですが、こいつはそれを簡単に作ってくれるライブラリなのです。
このライブラリで、TwitterFacebookGoogle+、LINEへのシェアボタンが簡単に作れるのです。

出来上がったボタンを見て「かっこよくない!」と思った方でも、ソースとか見つつ、参考にしてくれればと思います。


ダウンロードはこちら→sns-btn-makerのダウンロード


さて、使い方です。

1.まず、jQueryが必要なんですね。読み込みます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>sample</title>
<script src="jquery/jquery-2.1.3.min.js"></script>
</head>


2.次に、sns-btn-maker-1.0.jsとsns-btn-maker-1.0.cssを読み込みます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>sample</title>
<script src="jquery/jquery-2.1.3.min.js"></script>
<script src="lib/sns-btn-maker-1.0.js"></script>
<link rel="stylesheet" href="lib/sns-btn-maker-1.0.css" />
</head>


3.ボタンのキャプションとなる部分をHTML上に書いておきます。

<span id="twitter-1">Twitter</span>

これでボタンのキャプションはTwitterになるわけですね。


4.続いて、このキャプションを包んでボタン化します。

sbro.toTweetShare('#twitter-1', 'やふーのぺーじだよ', 'http://yahoo.co.jp', 'ハッシュタグ1, ハッシュタグ2,');


するとあら簡単!下のようなボタンが出来上がります。


f:id:jazzmaster0601:20150428195549p:plain


他にもサイズ変えたりいろいろできます。
詳しくは、サンプルソースを参照してみてね♪
ダウンロードはこちら→sns-btn-makerのダウンロード