今回は、バックエンド側の実装編です。環境構築については、こちらからご覧ください。
1.パッケージとクラスの作成
作成したFileUploadDownloadプロジェクトに画像のようにパッケージとクラスを作成します。ファイル名やパッケージの分け方は任意です。
2.クラスの内容
FilesController クラス
アップロードとダウンロードを処理しているクラス。このクラスの処理の理解が重要。
package com.nexflame.fupdl.controller;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import com.nexflame.fupdl.message.ResponseMessage;
import com.nexflame.fupdl.model.FileInfo;
import com.nexflame.fupdl.service.FilesStorageService;
@Controller
@CrossOrigin(origins = "*", maxAge = 3600)
public class FilesController {
@Autowired
FilesStorageService storageService;
@PostMapping("/upload")
public ResponseEntity<ResponseMessage> uploadFile(@RequestParam("file") MultipartFile file) {
String message = "";
try {
storageService.save(file);
message = "ファイルのアップロードに成功しました。: " + file.getOriginalFilename();
return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message));
} catch (Exception e) {
message = "ファイルをアップロードできませんでした。: " + file.getOriginalFilename() + "!";
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage(message));
}
}
@GetMapping("/files")
public ResponseEntity<List<FileInfo>> getListFiles() {
List<FileInfo> fileInfos = storageService.loadAll().map(path -> {
String filename = path.getFileName().toString();
String url = MvcUriComponentsBuilder
.fromMethodName(FilesController.class, "getFile", path.getFileName().toString()).build().toString();
return new FileInfo(filename, url);
}).collect(Collectors.toList());
return ResponseEntity.status(HttpStatus.OK).body(fileInfos);
}
@GetMapping("/files/{filename:.+}")
@ResponseBody
public ResponseEntity<Resource> getFile(@PathVariable String filename) {
Resource file = storageService.load(filename);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file);
}
}
TestController クラス
VueとSpringがうまく連携できているかを確認するために作った処理。今回のアップロード、ダウンロードとは直接関係がないので、これは作らなくても良い。
package com.nexflame.fupdl.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
/**
* テスト表示画面
* @return タイトルと内容のMap
*/
@RequestMapping(value = "/entries/latest", method = RequestMethod.GET)
public Entry getLatestEntry() {
Entry entryData = new Entry();
// TODO DBからデータを取得
entryData.setTitle("VueRouterを使ってみた");
entryData.setContent("VueRouterで簡単にページルーティングができました。なんとなくイメージはつかめた。");
return entryData;
}
/**
* データのクラス
*/
private class Entry{
private String title;
private String content;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
}
ResponseMessage クラス
処理のメッセージを保持しておくクラス
package com.nexflame.fupdl.message;
public class ResponseMessage {
private String message;
public ResponseMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
FileInfo クラス
ファイルの情報を保持しておく入れ物の定義。
package com.nexflame.fupdl.model;
public class FileInfo {
private String name;
private String url;
public FileInfo(String name, String url) {
this.name = name;
this.url = url;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
}
FilesStorageService インターフェース
package com.nexflame.fupdl.service;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
public interface FilesStorageService {
public void init();
public void save(MultipartFile file);
public Resource load(String filename);
public void deleteAll();
public Stream<Path> loadAll();
}
FilesStorageServiceImpl クラス(継承)
package com.nexflame.fupdl.service;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;
@Service
public class FilesStorageServiceImpl implements FilesStorageService {
private final Path root = Paths.get("uploads");
@Override
public void init() {
try {
Files.createDirectory(root);
} catch (IOException e) {
throw new RuntimeException("アップロード用のフォルダを初期化できません。");
}
}
@Override
public void save(MultipartFile file) {
try {
Files.copy(file.getInputStream(), this.root.resolve(file.getOriginalFilename()));
} catch (Exception e) {
throw new RuntimeException("ファイルを保存できません。 Error: " + e.getMessage());
}
}
@Override
public Resource load(String filename) {
try {
Path file = root.resolve(filename);
Resource resource = new UrlResource(file.toUri());
if (resource.exists() || resource.isReadable()) {
return resource;
} else {
throw new RuntimeException("ファイルを読み込めません。");
}
} catch (MalformedURLException e) {
throw new RuntimeException("Error: " + e.getMessage());
}
}
@Override
public void deleteAll() {
FileSystemUtils.deleteRecursively(root.toFile());
}
@Override
public Stream<Path> loadAll() {
try {
return Files.walk(this.root, 1).filter(path -> !path.equals(this.root)).map(this.root::relativize);
} catch (IOException e) {
throw new RuntimeException("ファイルを読み込めません。");
}
}
}
StorageService インターフェース
package com.nexflame.fupdl.storage;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
public interface StorageService {
void init();
void store(MultipartFile file);
Stream<Path> loadAll();
Path load(String filename);
Resource loadAsResource(String filename);
void deleteAll();
}
FileUploadDownloadApplication クラス
SpringBoot の自動生成で作成されるクラス。今回は何も修正していない。
package com.nexflame.fupdl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FileUploadDownloadApplication {
public static void main(String[] args) {
SpringApplication.run(FileUploadDownloadApplication.class, args);
}
}
ServletInitializer クラス
プロジェクト生成時に自動生成されなかったので今回は手動で作成した。本来は自動生成される?
package com.nexflame.fupdl;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(FileUploadDownloadApplication.class);
}
}
3.その他の設定
ファイルのアップロードとダウンロードのファイルサイズの上限を決めるために設定ファイルを修正します。とりあえず、1ファイル2MBまで。
application.properties の設定変更
spring.servlet.multipart.max-file-size=2MB
spring.servlet.multipart.max-request-size=10MB
アップロード用フォルダの作成
FilesStorageServiceImpl クラスでファイルを格納するのは、「uploads」フォルダになるのでプロジェクト直下に「uploads」フォルダを空で新規作成しておく。
ここまででサーバー側の処理は問題ないと思うので、あとは、フロント側でファイルの処理を記述すればアップロードとダウンロードができるはず。次回は、フロント側をVueとVuetifyを使って記述していこうと思う。
4.参考リンク
ほぼ下記のサイトのコードをそのまま使わせてもらいました。
コメント