Spring Bootでファイルアップロードを行う

# ■環境
Spring Boot 1.2.5.Release
Java 8
Maven 3.3.1

# ■概要
Spring Bootで画面からアップロードしたファイルをサーバー側に置くまでの手順を解説します。
**※クライアントのWebブラウザはIE9以下では動きません**

# ■pom.xml

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
<!-- ■Spring Boot本体 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

<!-- ■Spring Boot関連 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!-- ■フロントエンドフレームワーク -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.3.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
```

# ■詳細

まずはHTMLから。

```html:data_upload_view.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>一括アップロード</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link rel="stylesheet" href="./webjars/bootstrap/3.3.1/css/bootstrap.min.css" type="text/css"></link>
<!--[if lt IE 9]>
	<script type="text/javascript" src="./javascript/ie8/html5shiv.js"></script>
	<script type="text/javascript" src="./javascript/ie8/respond.js"></script>
	<script type="text/javascript" src="./javascript/ie8/jquery-1.11.3.min.js"></script>
<![endif]-->
<!--[if gte IE 9]><!-->
	<script type="text/javascript" src="./webjars/jquery/2.1.4/jquery.min.js"></script>
<!--<![endif]-->
	<script type="text/javascript" src="./webjars/bootstrap/3.3.1/js/bootstrap.min.js"></script>
	<script type="text/javascript" src="./javascript/data_upload_view.js"></script>
	<script type="text/javascript" src="./javascript/common.js"></script>
	<script type="text/javascript" src="./javascript/datetimepicker/jquery.datetimepicker.js"></script>
</head>
<body>
	<div class="container">
<!-- タイトル部 -->
		<div class="row">
			<p><h1 id="title">一括アップロード</h1></p>
		</div>
<!-- ヘッダー部 -->
		<div class="row">
<!-- パンくずリスト -->
			<div class="col-md-4 col-lg-4">
			    <ul class="breadcrumb">
			        <li><a href="menu">Menu</a></li>
			        <li class="active">一括アップロード</li>
			    </ul>
			</div>
			<div class="col-md-4 col-lg-4"></div>
			<div class="col-md-4 col-lg-4"></div>
		</div>
<!-- ファイル情報部 -->
		<div class="row">
 			<div class="col-md-2 col-lg-2"></div>
			<div class="col-md-8 col-lg-8">
				<div class="well well-sm">
					<div>アップロードするファイルを選択して、アップロードボタンを押下してください。</div>
					<br />
 					<form id="data_upload_form" enctype="multipart/form-data" method="post">
						<div class="form-group">
							<label>■ファイル種類：</label>
							<select id="select_file_type" name="select_file_type" required="">
								<option value="login-user">ログインユーザー</option>
								<!-- アップロードするファイルを定義していく -->
							</select>
						</div>
						<div class="form-group">
							<label>■ファイルパス：</label>
							<input type="file" id="upload_file" name="upload_file" required="" />
						</div>
						<div class="form-group">
							<input id="data_upload_button" type="submit" value="アップロード" />
						</div>
					</form>
				</div>
			</div>
 			<div class="col-md-2 col-lg-2"></div>
		</div>
	</div>
</body>
</html>
```

特別なことはしておりませんが、あえてポイントを挙げるとすれば以下でしょうか。

```html
<form id="data_upload_form" enctype="multipart/form-data" method="post">
```

**enctype="multipart/form-data"**
というのがポイントでこれでアップロードされたファイルをサーバー側に送信することができます。

サーバーへの送信はJavaScriptでajax送信してます。

```js:data_upload_view.js
// 主処理部
$(function(){

	// アップロードボタンを押下した
	$("#data_upload_form").submit(function(event){
		// 要素規定の動作をキャンセルする
		event.preventDefault();

		var ajaxUrl = "file/upload?";
		// ファイル種類
		ajaxUrl += "filetype=" + $("#select_file_type option:selected").val();

		if(window.FormData){
			var formData = new FormData($(this)[0]);
			
			$.ajax({
				type : "POST", 					// HTTP通信の種類
				url  : ajaxUrl,					// リクエストを送信する先のURL
				dataType : "text",				// サーバーから返されるデータの型
				data : formData, 				// サーバーに送信するデータ
				processData : false,
		        contentType: false,
			}).done(function(data) {		// Ajax通信が成功した時の処理
				alert("アップロードが完了しました。");
			}).fail(function(XMLHttpRequest, textStatus, errorThrown) {	// Ajax通信が失敗した時の処理
				alert("アップロードが失敗しました。");
			});
		}else{
			alert("アップロードに対応できていないブラウザです。");
		}
	});
});
}
```

ポイントは2つです。

```js
		// 要素規定の動作をキャンセルする
		event.preventDefault();
```

①submit規定の動作をキャンセルさせることでJavascript内で独自の動作を定義します。

```js
			var formData = new FormData($(this)[0]);
```

②formの中身をそのままFormDataオブジェクトにぶち込みます。

FormDataはIE9以下では動きません。

最後にサーバー側の処理です。

```java:FileUploadRestController.java
package com.sample.fileupload.controller;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

import com.sample.login.service.data.LoginUser;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * 一括アップロードのコントローラー
 */
@RestController
@RequestMapping("file/upload")
public class FileUploadRestController {
	/**
	 * 一括アップロードを行う
	 *
	 * @param multipartFile
	 * @param fileType
	 * @param registrationType
	 * @param dailyAttendanceYyyymm
	 * @param loginUser
	 * @return
	 */
	@RequestMapping(method = RequestMethod.POST)
	public Object post(
			@RequestParam("upload_file") MultipartFile multipartFile,
			@RequestParam("filetype") String fileType,	// ファイル種類
			@AuthenticationPrincipal LoginUser loginUser){	// 認証ユーザー情報

		// ファイルが空の場合は異常終了
		if(multipartFile.isEmpty()){
			// 異常終了時の処理
		}

		// ファイル種類から決まる値をセットする
		StringBuffer filePath = new StringBuffer("/uploadfile")
										.append(File.separator).append(fileType);	//ファイルパス

		// アップロードファイルを格納するディレクトリを作成する
		File uploadDir = mkdirs(filePath);

		try {
			// アップロードファイルを置く
			File uploadFile =
					new File(uploadDir.getPath() + "/" + fileType);
			byte[] bytes = multipartFile.getBytes();
			BufferedOutputStream uploadFileStream =
					new BufferedOutputStream(new FileOutputStream(uploadFile));
			uploadFileStream.write(bytes);
			uploadFileStream.close();

			return "You successfully uploaded.";
		} catch (Exception e) {
			// 異常終了時の処理
		} catch (Throwable t) {
			// 異常終了時の処理
		}
	}

	/**
	 * アップロードファイルを格納するディレクトリを作成する
	 *
	 * @param filePath
	 * @return
	 */
	private File mkdirs(StringBuffer filePath){
		Date now = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
		File uploadDir = new File(filePath.toString(), sdf.format(now));
		// 既に存在する場合はプレフィックスをつける
		int prefix = 0;
		while(uploadDir.exists()){
			prefix++;
			uploadDir =
					new File(filePath.toString() + sdf.format(now) + "-" + String.valueOf(prefix));
		}

		// フォルダ作成
		uploadDir.mkdirs();

		return uploadDir;
	}
}
```

アップロードされたファイルは「org.springframework.web.multipart.MultipartFile」で受け取ります。

```java
			@RequestParam("upload_file") MultipartFile multipartFile,
```

受け取ったファイルはStream系のクラスで読み込んでサーバーに置きます。

```java
			File uploadFile =
					new File(uploadDir.getPath() + "/" + fileType);
			byte[] bytes = multipartFile.getBytes();
			BufferedOutputStream uploadFileStream =
					new BufferedOutputStream(new FileOutputStream(uploadFile));
			uploadFileStream.write(bytes);
			uploadFileStream.close();
```





# ■まとめ
・クライアント側からFormでファイルをアップロードする
・enctype="multipart/form-data"とすることでファイルとしてサーバー側に送れる
・Formの中身をそのままFormDataオブジェクトにぶち込みajax送信
・サーバー側ではMultipartFileで受け取る

# ■参考資料
[Spring Bootでファイルをアップロードする](http://blog.okazuki.jp/entry/2015/07/17/202941)
[enctype='multipart/form-data'ってなんだ？](http://mugenup-tech.hatenadiary.com/entry/2014/08/28/100910)