ニフクラmBaaSお役立ちブログ

スマホアプリ開発にニフクラmBaaS。アプリ開発に役立つ情報をおとどけ!

mBaaSを使ってブログを作る(その4)「ファイルアップロード機能を実装する」

mBaaSをモバイルではなくバックエンドのデータベースとして活用する記事になります。前回で編集機能まで作りましたので、今回はファイルストアへのアップロード機能を作ります。実際にできあがると次のようになります。Qiitaやはてなブックマークのように画像をアップロードすると自動的にコンテンツとして埋め込まれます。

Expressでバイナリファイルを扱う

Expressではバイナリファイルを扱う際に以下のような設定を行います。これは app.js に追加します。これは app.use(bodyParser.json()); より前に書く必要があります。通常のHTMLやAjaxアクセスは content-type を指定して弾いています。この処理はNode.js - get raw request body using Express - Stack Overflowを参考にしています。

app.use(function(req, res, next) {
  var contentType = req.headers['content-type'] || ''
    , mime = contentType.split(';')[0];
  if (mime != 'text/plain') {
    return next();
  }
  req.rawBody = '';
  req.on('data', function(chunk) {
    req.rawBody += chunk;
  });

  req.on('end', function() {
    req.rawBody = new Buffer(req.rawBody.toString('binary'),'binary');
    next();
  });
});

ファイルのドラッグ&ドロップを実装する

ファイルのドラッグ&ドロップはHTML5の新しいAPIになります。そのためレガシーなブラウザではサポートされませんので注意してください。 public/javascripts/app.js に追加する実装は次のようになります。

ファイルをドラッグしてテキストエリアに移動した際にテキストエリアの枠を太字で表示します。それらはスタイルシートで設定しますので、addClass/removeClassを使って指定します。ドロップされたファイルは e.originalEvent.dataTransfer.files[0] で取得できますので、それを HTML5のFileReaderで読み込み、 POST /posts/file へアップロードします。

var textarea = $("form #body");

// ドロップ完了時のイベント
var cancelDrop = function(e) {
  e.preventDefault();
  e.stopPropagation();
  textarea.removeClass('drop');
  return false;
}

// ドロップ開始時のイベント
var startDrop = function(e) {
  textarea.addClass('drop');
  e.preventDefault();
  e.stopPropagation();
  return false;
}
// イベント設定
textarea.bind("dragenter", startDrop);
textarea.bind("dragover",  startDrop);

// ドロップした際のイベント
textarea.bind("drop", function(e) {
  // ドロップされたファイルを取得
  var file = e.originalEvent.dataTransfer.files[0];
  // HTML5のFileReaderを用意
  var fileReader = new FileReader();
  // ファイルを読み込んだ際のコールバック
  fileReader.onload = function(e) {
    // Ajaxでファイルアップロード
    $.ajax({
      url: '/posts/files',
      type: 'POST',
      data: e.target.result,
      contentType: false,
      processData: false
    })
    .then(function(result) {
      // 受け取ったデータをテキストエリアのカーソルがある部分に挿入
      var cursorPos = textarea.prop('selectionStart');
      var v = textarea.val();
      var textBefore = v.substring(0,  cursorPos);
      var textAfter  = v.substring(cursorPos, v.length);
      textarea.val(textBefore + '![](' + result.url + ')' + textAfter);
    }, function(err) {
      console.log(error);
    })
  }
  // ファイル読み込み実行
  fileReader.readAsBinaryString(file);
  
  // ドロップキャンセル時のイベントを実行
  cancelDrop(e);
});

今回は画像のURLをMarkdownフォーマットで挿入しています。パーサーを入れればブログ記事に画像が表示できるようになります。

テキストエリアを変化させるスタイルシートは public/stylesheets/style.css に作成し、次のように記述します。

textarea.drop {
  border: dashed blue 5px !important;
}

最後に shared/header.ejs を開いてスタイルシートを読み込むようにします。

<link rel='stylesheet' href='/stylesheets/style.css' />

ファイルアップロード処理を記述する

次に routes/posts.js にファイルアップロード処理を記述します。先ほど、バイナリファイルは req.rawBody で取得できるようにしましたので、それを使ってファイルアップロードを行います。

ドラッグ&ドロップによるファイルアップロードの場合、ファイル名が取得できません。そのため、バイナリファイルの内容から拡張子を取得します。それが common.detectFileType になります。ファイル名が取得できませんので、代わりにユニークなIDを生成してファイル名としています。それが common.guiid() です。どちらも後ほど紹介します。

実際のアップロードは ncmb.File.upload(name, req.rawBody) と記述するだけです。これだけで mBaaS へのファイルアップロード処理が完了します。とても簡単ではないでしょうか。

router.post('/files', function(req, res, next) {
  common.setSessionToken(req, ncmb);
  extension = common.detectFileType(req.rawBody);
  name = `${common.guid()}.${extension}`
  ncmb.File.upload(name, req.rawBody)
    .then(function(data){
      // アップロード後処理
      var json = {
        url: `https://mb.api.cloud.nifty.com/2013-09-01/applications/${config.application_id}/publicFiles/${name}`
      }
      res.status(200).json(json);
     })
    .catch(function(err){
      // エラー処理
      res.status(400).json(err);
    });
});

アップロードが完了したら、そのファイルをHTTPSで公開します。これはまず mBaaS の管理画面で HTTPS によるファイル公開を有効にしなければなりません。

さらに、管理画面のURLが次のようになっていた場合、aaaaa の部分を application_id として config.js に追加する必要があります。これはSDKやWeb APIから取得できませんので注意してください。

libs/common.js は次のように修正します。 detectFileType については JavaScriptで拡張子のない画像のファイルの種類を判別する方法(IE9以上) | スターフィールド株式会社 、UUIDの生成は Create GUID / UUID in JavaScript? - Stack Overflow を参考にさせてもらいました。

module.exports = {
  :
  // 拡張子を特定する処理
  detectFileType: function(bytes) {
    if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[bytes.length-2] === 0xff && bytes[bytes.length-1] === 0xd9) {
      return "jpg";
    } else if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
      return "png";
    } else if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38) {
      return "gif";
    }
    return false;
  },
  guid: function() {
    function s4() {
      return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
    }
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
      s4() + '-' + s4() + s4() + s4();
  }
}

config.js は次のようになります。

const config = {
  application_key: "d4..ca",
  client_key: "89..a0",
  application_id: "mc..Xn" // 追加
}
module.exports = config;

WebアプリケーションのバックエンドとしてmBaaSを使うと、ファイルをアップロードする処理がとても簡単に作れるようになります。サーバを用意したり、APIを作成したりする手間もありません。

今回のコードは NCMBMania/ncmb_blog_nodejs にアップロードしてあります。不明点があれば参考にしてください。