Vue.jsでJsonと画像をmultipartでPlayframeworkにPOSTする

個人開発したアプリの宣伝
目的地が設定できる手帳のような使い心地のTODOアプリを公開しています。
Todo with Location

Todo with Location

  • Yoshiko Ichikawa
  • Productivity
  • Free

スポンサードリンク

WebAPIとして動作しているPlayframeworkにmultipartでJsonとファイルデータを送信します。


Vue側の実装

template

axiosでPOSTするので、formにはmultipartをつけず、prevent指定でページロードしないように制御する。

<template>
<form>
  <input type="text" v-model="shopMame">
  <input
    @change="changeFile"
     type="file"
     name="upfile[]"
     multiple
     accept="image/*" />
     <img v-for="(img, idx) in uploadedImageURL" :key="idx" :src="img.src" />
  <button @click.prevent="postData">登録</button>
</form>
</template>


送信処理

例ではchangeFileイベントで、ブラウザ上に画像プレビューするよう記述してある。

FormDataはappend単位でboundary区切りのデータを積むことができ、axios送信時に Content-Type': 'multipart/form-dataヘッダーを送信すればよい。

<script>
import axios from 'axios'

export default {
  name: 'ShopForm',
  data: function () {
    return {
      shopMame: "",
      uploadedImageURL: [],
      uploadedImage: []
    }
  },
  methods: {
    changeFile (elem) {
      this.uploadedImage = []
      const files = elem.target.files || elem.dataTransfer.files
      for (var i = 0; i < files.length; i++) {
        let file = files[i]
        let url = URL.createObjectURL(file)
        this.uploadedImageURL.push({ src: url })
        this.uploadedImage.push( file )
      }
    },
    postData: function(){
      let formData = new FormData();
      let data = {
        shopName: this.shopMame
      }
      let jsonData = JSON.stringify(data);
/*
      const blob = new Blob([jsonData], {
        type: 'application/json'
      });
*/
      formData.append("document", jsonData);
      this.uploadedImage.forEach(element => {
        formData.append("image", element)
      });
      axios({
        method: 'post',
        url: 'http://localhost:9000/shop/create',
        data: formData,
        header: {
          'Accept': 'application/json',
          'Content-Type': 'multipart/form-data',
        },
      })
    }
  }
}
</script>


尚、上記だと、Jsonのデータにmime指定されないので、mime(application/json)を指定したい場合、Jsonファイルとして送信する必要があるみたい。

let jsonData = JSON.stringify(data);
const blob = new Blob([jsonData], {
  type: 'application/json'
});
formData.append("document", blob);

但し、こうするとPlayframework側で添付ファイルとしてJsonコンテンツを解釈してしまうので、好きなほうで送信すれば良いのかな。と思う(^_^;)


Playframework側で各データの受け取り

Jsonデータの受信

jsonのmimeを指定せず、テキストデータとして送信したデータを解釈する。Action applyに(parse.multipartFormData)を指定する。

def create() = Action(parse.multipartFormData){ implicit request =>
    val document = request.body.dataParts.get("document").map( seq => Json.parse(seq.head) )
    ...

documentにOption[JsValue]が入る。


添付ファイルデータの受信
def create() = Action(parse.multipartFormData){ implicit request =>
  val document = request.body.dataParts.get("document").map( seq => Json.parse(seq.head) )
  request.body.files.map { file =>
    val filename = file.filename
    val contentType = file.contentType
    println(Paths.get(filename).getFileName)
    println(contentType)
    println(file.fileSize)
    //file.ref.moveTo(new File("/tmp", filename))
  }
  ...

のような感じでfile.refと各プロパティを元に適宜処理すれば良い。

尚、Jsonもblobを使用し添付ファイルとして送信した場合は、こちらのfilesを経由してデータアクセスすることになる。

Jsonのバリデートやドメインオブジェクトのmappingに関しては、こちらの記事に記述しています。

Playframework 複雑なJsonデータをオブジェクトにmappingまたはForm bind、validateする - 追憶行