Node.jsとPHP APIを使用して複合機が受信するFAXをアプリで見れるようにしてみた。


#はじめに
・複合機でFAXを受信したときに、そのデータをスマホアプリに転送してほしいとお願いされた。Node.jsとPHP APIを使用して機能の実現を試みました。

#開発環境
・サーバー構成
　WindowsServr2012R2
　Apache + SQLServer2012 + PHP
　Node.js（シェルスクリプト）

・複合機機種
　Canon iR-ADV C5235F

#実装コード
<b>・Node.jsからPHP APIの呼び出し</b>

　参考にしたページ：[https://qiita.com/zaburo/items/0280807fe5e59026e41b](https://qiita.com/zaburo/items/0280807fe5e59026e41b)

```JS:fax_tenso.js
var kyotenHand001EmailAdress = "【手動転送用のメールアドレス】";
var kyotenAuto001EmailAdress = "【自動転送用のメールアドレス】";
var request = require('request');
// HTTP header
var headers = {
  'Content-Type': 'application/json',
  'Content-type': 'application/x-www-form-urlencoded'
};
//require
var chokidar = require("chokidar");
funcWatcher(
  "【会社番号】/【拠点番号】/hand/",
  "【会社ごとの転送用メールアドレス】",
  kyotenHand001EmailAdress
);
//-----------------------------------
// 2018/05/26
// shinichi yamazaki
// node.jsでファイルの変更を検出して何かする
// ファイル監視のための処理を関数化しました
// 引数１：監視するディレクトリパス
// 引数２：会社を特定するためのメールアドレス（メールアドレスである必要はあまりない）
// 引数３：拠点とFAXモードを特定するためのメールアドレス（全社共通）
//-----------------------------------
function funcWatcher(
    pass, 
    companyEmailAdress, 
    kyotenEmailAdress)
{
  // chokidarの初期化
  var watcher = chokidar.watch(
      "【サーバーの監視先フォルダパス】" + pass, {
      ignored:/[\/\\]\./,
      persistent:true
  });
  // イベント定義
  watcher.on('ready',function(){
      // 準備完了
      console.log("ready watching...");
      // ファイルの追加
      watcher.on('add',function(path){
        var options = {
          url : '【FAX転送用のPHP API】',
          method: 'POST',
          headers: headers,
          form: {
            'companyEmailAdress': companyEmailAdress,
            'kyotenEmailAdress': kyotenEmailAdress,
            'mailTitle': "hello"
          }  
        };
        // FAX転送用のPHP APIの呼び出し
        request.post(options, function (error, response, body) {
          if (body) {
            var options = {
              url : '【通知用のPHP API】',
              method: 'POST',
              headers: headers,
              form: {
                  'idm': "",
                  'registerID': "-1",
                  'companyID': "",
                  'staffID': "",
                  'msg': "新しいFAXを受信しました",
                  'mode': "mode=6",
                  'groupID': "",
                  'smartPhoneName': body
              }  
            };
            // 通知用のPHP APIの呼び出し
            request.post(options, function (error, response, body) {
              if (body) {
              }
              if (error) {
              }
            });
            console.log(body);
          }
          if (error) {
            console.log(error);
          }
        });
        console.log(path + " added.");
      });
  });
}
```

<b>サーバー側</b>
　FAX転送用のPHP API

```PHP:fax_tenso.php
<?php
  date_default_timezone_set('Asia/Tokyo');
  // 現在日時をUNIXタイムスタンプを秒単位で取得する
  $now = time();
  // 現在日時を YYYY/MM/DD hh:mm:ss の書式の文字列で取得する
  $now = date('Y/m/d H:i:s');
  $companyEmailAdress = $_POST["companyEmailAdress"];
  $kyotenEmailAdress  = $_POST["kyotenEmailAdress"];
  $subject  = $_POST["mailTitle"];
  $bodystructure  = $_POST["bodystructure"];
  // 変数宣言
  $companyID = "";
  $faxKyoten = "";
  $faxMode   = "";
  $sendFlag  = "";
  $maxFaxID  = "";
  // FAX受信者リスト
  $faxPushList = NULL;
  $pushJsonArray = array();
  require('common.php');
  //-----------------------------------------
  // マッピング
  // 会社番号の取得
  // 2018/05/03 yamazaki
  //-----------------------------------------
  $companyData = databaseGet(
    "SELECT".
    "    companyID".
    " FROM MK_company".
    " WHERE".
    "    faxEmail=N'".$companyEmailAdress."'"
  );
  // 存在していないときの処理
  if ($companyData == NULL) {
    echo "会社番号の取得に失敗しました。";
    die('MSSQL Serer Connect Error');
  }
  else {
    $companyID = $companyData[0]["companyID"];
  }
  //-----------------------------------------
  // マッピング
  // 拠点情報の取得
  // 2018/05/03 yamazaki
  //-----------------------------------------
  $kyotenData = databaseGet(
    "SELECT".
    "    faxKyoten,".
    "    sendFlag".
    " FROM MK_kyoten".
    " WHERE".
    "    kyotenEmail=N'".$kyotenEmailAdress."'"
  );
  // 存在していないときの処理
  if ($kyotenData == NULL) {
    echo "拠点情報の取得に失敗しました。";
    die('MSSQL Serer Connect Error');
  }
  else {
    $faxKyoten = $kyotenData[0]["faxKyoten"];
    $sendFlag  = $kyotenData[0]["sendFlag"];
  }
  if ($sendFlag == "0") {
    $faxMode = "auto";
  } else {
    $faxMode = "hand";
  }
  //-----------------------------------------
  // マッピング
  // FaxID最大値の取得
  // 2018/05/06 yamazaki
  //-----------------------------------------
  $faxIDData = databaseGet(
    "SELECT".
    "    MAX(faxID)+1 AS maxFaxID".
    " FROM MK_faxSend".
    " WHERE".
    "    companyID=N'".$companyID."'"
  );
  // 存在していないときの処理
  if ($faxIDData == NULL) {
    echo "FaxID最大値の取得に失敗しました。";
    die('MSSQL Serer Connect Error');
  }
  else {
    $maxFaxID = $faxIDData[0]["maxFaxID"];
  }
  //-----------------------------------------
  // マッピング
  // FAX受信者リストの取得
  // 2018/05/06 yamazaki
  //-----------------------------------------
  $faxPushDate = databaseGet(
    "SELECT".
    "    staffID".
    " FROM MK_staff".
    " WHERE".
    "    companyID = N'". $companyID. "' AND".
    "    faxRec = N'000'"
  );
  // 存在していないときの処理
  if ($faxPushDate == NULL) {
    echo "FAX受信者リストの取得に失敗しました。";
    die('MSSQL Serer Connect Error');
  }
  else {
    $faxPushList = $faxPushDate;
  }
  // 保存先のFAX受信リストを取得する。
  $files = glob("../../../fax_data/get/dev/".$companyID."/".$faxKyoten."/".$faxMode."/*");
  $list = array();
  foreach ($files as $file) {
    if (is_file($file)) {
      $fileNamePath = explode("/", $file);
      $fileName = $fileNamePath[9];
      $fileExte = explode(".", $fileName);
      error_log("ファイルの拡張子：".$fileExte[1], 0);
      // 拡張子がTIFの時にJPEGに変換する処理
      if ($fileExte[1] == "tif" || $fileExte[1] == "tiff") {
        $dest = "./dst/";
        $type = "jpg";
        $image = new Imagick();
        $image->readImage("【サーバーのFAX受信フォルダ　←　ここに複合機からFAXが転送されるようにしてください】".$companyID."/".$faxKyoten."/".$faxMode."/".$fileName);
        $image->setImageFormat('jpg');
        $image->writeImage("【サーバーのFAX転送先フォルダ　←　Webアプリはここに参照しに行く】".$companyID."/".$faxKyoten."/".$fileExte[0].".jpg");
        // 後処理
        $image->clear();
        $image->destroy();
        // 変換前のファイルを削除する
        unlink("【サーバーのFAX受信フォルダ】".$companyID."/".$faxKyoten."/".$faxMode."/".$fileName);
      }
      else {
        //-----------------------------------------
        // 該当ファイルの移動
        // 1つずつ
        //-----------------------------------------
        if (FALSE == rename(
          "../../../fax_data/get/dev/".$companyID."/".$faxKyoten."/".$faxMode."/".$fileName,
          "../../../fax_data/send/dev/".$companyID."/".$faxKyoten."/".$fileName
        )) {
          die('File Move Error');
        }
      }
      //-----------------------------------------
      // マッピング
      // FAX送信情報の登録
      // 2018/05/06 yamazaki
      //-----------------------------------------
      databaseSet(
        "INSERT INTO".
        " MK_faxSend(".
        "    faxDateTime,".
        "    companyID,".
        "    faxFlag,".
        "    subject,".
        "    imagePass,".
        "    faxID,".
        "    faxKyoten".
        " ) VALUES(".
        "    '".$now."',".
        "    N'".$companyID.  "',".
        "    '" .$sendFlag.   "',".
        "    N'".$subject.    "',".
        "    N'".$fileExte[0].".jpg".   "',".
        "    '" .$maxFaxID.   "',".
        "    N'".$faxKyoten.  "'".
        ")"
      );
      foreach ($faxPushList as $faxPush) {
        //-----------------------------------------
        // マッピング
        // FAX送信情報履歴の登録
        // 2018/05/06 yamazaki
        //-----------------------------------------
        databaseSet(
          "INSERT INTO".
          " MK_historyOption(".
          "    companyID,".
          "    category,".
          "    ymdTime,".
          "    staffID,".
          "    flag,".
          "    externNo,".
          "    okiniFlag".
          " )".
          " VALUES(".
          "    N'".$companyID.  "',".
          "    '6',".
          "    '".$now."',".
          "    N'".$faxPush["staffID"]."',".
          "    '0',".
          "    '" .$maxFaxID.   "',".
          "    '" .$sendFlag.   "'".
          " )"
        );
        //-----------------------------------------
        // マッピング
        // スタッフIDから通知先の情報の取得
        // 2018/05/07 yamazaki
        //-----------------------------------------
        $pushData = databaseGet(
          "SELECT".
          "    registerID,".
          "    name".
          " FROM MK_smartPhone".
          " WHERE".
          "    staffID = N'".$faxPush["staffID"]."' AND".
          "    companyID = N'".$companyID."' AND".
          "    name <> N'card'"
        );
        //-----------------------------------------
        // 2018/05/15 yamazaki
        // 通知先の情報が取得できないときは何もしない
        //-----------------------------------------
        foreach ($pushData as $pushInfo) {
          $pushJsonArray[] = array(
            'registerID' =>$pushInfo["registerID"],
            'name' =>$pushInfo["name"]
          );
        }
      }
      $maxFaxID++;
    }
  }
  // commonPushFuncメソッドで、対象者全員に通知を送る。
  //（※FAXデータが複数存在するときに複数回通知が飛ぶこともありえる）
  header('Content-type: application/json');
  // 指定されたデータタイプに応じたヘッダーを出力する
  print(json_encode($pushJsonArray));
  exit();
?>
```

<b>クライアント側</b>
 アプリ（iPhone, Android ガワネイティブ）

#運用時の問題点
①機能自体は実装できている。ただ、運用面でサーバーを再起動するとそのたびにシェルスクリプトを実行する必要がある。
②Node.jsのソースバージョン管理が出来ていない。

#対策案
①ひとまずは、Windows Serverのタスクスケジューラにログオン時にShell Scriptを自動起動で実行出来るようになればいいかな。他にもっと良い方法がありそうだが...
②Gitを使ってバージョン管理ができるといいな。

#まとめ
・Node.jsでフォルダの更新を監視し、トリガーからPHP APIの呼び出しができた。
・現状、実行ファイルsをPowerShellから手動でしか呼び出せていないので、なんとか他のやり方でスマートにやりたい。
・将来的には他の会社、複数の拠点で運用出来る様にしたい。（今後拡張が出来るように設計段階で盛り込み済み）