LINE WORKS(と色々)用いて文化祭用のシステムを作った話。

今回したことが学校のHPに掲載されていました↓

https://www.anan-nct.ac.jp/notifications/10973/
## 前置き(文化祭2週間前の部活)
先輩「LINE WORKSの人と協力して文化祭の落とし物管理システムを作るやで」
開発メンバー「ほう」
先輩「LINE WORKSの人来てくれるから色々教わってな」
開発メンバー「それはいいですね」
先輩「先輩ら他で忙しいから開発メンバーは3年主体でやってな」
開発メンバー「ん？？」
先輩「製作期間文化祭までな」
開発メンバー「？？？」
というやり取りがあり、このプロジェクトが始まった。

## 開発メンバー
* Microsoft Flow担当
    * Naoki Takahashi <br>
    Twitter→[@NaokiPadT480](https://twitter.com/NaokiPadT480)
* サーバー担当フレンズ
    * ﾌﾟﾛ(ｸﾞﾗﾐﾝｸﾞ見習い中のアラ)イさん.php <br>
    Twitter→[@raspberry_craft](https://twitter.com/raspberry_craft)
* 掲示板ページUI担当
    * K-Mano <br>
    Twitter→[@ClockOjisan](https://twitter.com/ClockOjisan)<br>
    Github→[K-Mano](https://github.com/K-Mano)

>メンターとしてワークスモバイルジャパンの方がついてくれた。

## したこと
1. LINE WORKSのBot作成
2. MS FlowのBot APIを使用したフローの作成
    →Botに投げられた文をサーバーにPOST
    →Botに投げられた写真のIDをサーバーにPOST
    →使い方ガイドの送信
3. サーバー上にPOSTされた文字列の処理
    →PHPを用いて文字列と写真IDを取得しデータベースに登録
	→写真は即座にダウンロード
4. DBの情報をPHPを埋め込んだHTMLファイルで表示

## なぜMS Flowが挟まれているか
実は運用に使うサーバーが自己署名証明書を使っており、LINE WORKSの仕様では使えないことが判明。
納期が短く、正常に動作させるために時間を使いたいことからサーバー側は特にいじらず間にMS Flowを挟むことで解決した。
MS Flowの構成は以下のようになった。

![LINE-Works-Bot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415384/06234842-666f-39f2-e619-1935c3016f8f.jpeg)

## LINE WORKSのBotを作る
といってもこれは特別なことは特にせず、Botが受け取ったメッセージをサーバーにMS Flow経由で送るようにした
(トークンの期限切れに気づかず、2日ほど潰れたが...)

## サーバー処理方法について考える
サーバー担当は過去に作ったシステムを流用して開発しようとしたが

* SQLインジェクション対策がされていない
* PHPを初めて触った頃の設計で無駄が多すぎる
* なにより汚すぎる

といった理由で新規開発となった。

今回のサーバーでは以下の点を重視した

* 動作ログを残す
* 処理をモジュール化する

ある意味当たり前のことだが、これは過去に出来ていなかったため苦労したことを反映した結果となる。

## 落とし物情報の判別方法
時間がなかったために、送信するメッセージの先頭に番号をつけて

* 落とし物の名前
* 落とし物を見つけた場所
* 落とし物の特徴

を判別した。
また、画像はJSONの情報から判別し、取得した

# そして出来たのがこれ
開発にはPHP 7.3が使われた。選んだ理由は一番慣れている言語かつ、既にサーバーに実行環境が整っていたというものである。
<details><summary>メッセージ受信用プログラム</summary><div>

```php:メッセージ受信用プログラム
<?php
require_once("/var/www/lib/Tools.php");
require_once("/var/www/Config/SQL_login.php");
require_once("/var/www/lib/SQL.php");
require_once("/var/www/Config/Server_config.php");
require_once("/var/www/Config/Server_Proxy_Config.php");
$json=json_decode((file_get_contents(('php://input'),true)));
$type=$json->content->type;														//メッセージイベントのタイプ判別
error_log("type:$type");
switch($type){
	case "text" :
		$cmd=mb_substr(($json->content->text),0,1);										//コマンド文字の切り出し
		error_log("cmd:".$cmd);
		switch($cmd) {
			case "1":
				line_message("name",$json,$cmd,$dbh);
				break;
			case "2":
				line_message("place",$json,$cmd,$dbh);
				break;
			case "3":
				line_message("feature",$json,$cmd,$dbh);
			default :
				echo "Invalid command";
		}
		break;
    	case "image" :
		$fileName=str_replace('/','',sha1($resourceID));
		$count=0;
		$res=false;
		$accountID=$json->source->accountId;
		$time=$json->createdTime;
		$resourceID=$json->content->resourceId;
		$header_json=array(
			'http'=>array(
					'method' => 'GET',
					'header' =>  "consumerKey:******\r\n"
								. "authorization: ******\r\n"
								. "x-works-apiid: *****\r\n"
								.  "x-works-resource-id:$resourceID\r\n"
			)
		);
		$context = stream_context_create($header_json);
		do{
			if($proxy_set){
				$res=file_get_contents($line_image_url, false, stream_context_create([
					'http' => [
						'method' => 'GET',
						'request_fulluri' => true, // プロキシ通す場合は必須
						'proxy'  => $proxy_url,
					]
				]));
				error_log(print_r($http_response_header));
			}else{
				$res=file_get_contents($line_image_url, false, $context);
				error_log(print_r($http_response_header));
			}
			$count++;
		}while(($res==false&& $count>3));

		if($res!=false){
			if($proxy_set){
				LogSQL(000,"Success ($resourceID) ,Line Picture Get Proxy Enable","Line_get","0",$dbh);
			}else{
				LogSQL(000,"Success ($resourceID) ,Line Picture Get Proxy Disable","Line_get","0",$dbh);
			}
			if(file_put_contents("/var/www/html/image/$fileName.png",$res)){
				LogSQL(000,"Success ($resourceID) ,Picture Write","Line_get","0",$dbh);
				$line_get_sql="UPDATE Lost_Article SET `picture`=:Data WHERE `RoomID`=:RoomID ORDER BY id DESC limit 1";
				$line_get_prepare=$dbh->prepare($line_get_sql);
				$line_get_prepare->bindValue(':Data',$fileName,PDO::PARAM_STR);
				$line_get_prepare->bindValue(':RoomID',$accountID,PDO::PARAM_STR);
				if($line_get_prepare->execute()){
					LogSQL(000,"Success Picture Data Update","line_get",0,$dbh);
				}else{
					LogSQL(005,"Failed Picutre Data Update","line_get",2,$dbh);
				}
			}else{
				LogSQL(001,"Failed ($resourceID) ,Picture Write","Line_get","2",$dbh);
			}
		}else{
			if($proxy_set){
				LogSQL(001,"Failed ($resourceID) ,Line Picture Get Proxy Enable","Line_get","2",$dbh);
			}else{
				LogSQL(001,"Failed ($resourceID) ,Line Picture Get Proxy Disable","Line_get","2",$dbh);
			}
		}
	}
```
</div></details>

Webページに表示されるのはこんな感じ(ページのリソース:50MB(ほぼフォント))(1分毎に再読み込み有り)
![WebPage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415384/1492e2f5-4cf2-cbc6-cf2c-660323bce9b7.png)

## 感想

Naoki Takahashi ↓
納期が短く死にかけたが、見える部分の見た目は良くなったので良かった。
APIを初めて触ったが、仕組みを理解できた。

ﾌﾟﾛｲさん ↓
やはり慣れたPHPは手っ取り早く開発するには使いやすいと改めて実感すると同時に、サーバーへの負荷を考えてほしいと半分キレそうになった

K-Mano ↓
普通のWebページを作るだけだったがその分見た目に凝る時間もできた。