画像をアップロードしてプロジェクト内のstaticフォルダに格納し、格納した画像をサムネイルとして画面表示する流れを実装したかった。
※spring Frameworkの時はwebapp直下にuploadsフォルダを作成してしまえば画像の格納が容易にできたのでその感覚で実装できるだろうと思っていたらハマりました。
結論
staticフォルダに格納することはできました。
がしかし、HTMLに画像を表示させるため、静的リソースへアクセスパスを受け渡しても画像が表示されず。。。いろいろ探っていくとclasspath:で取得したパス情報は、コンパイル後フォルダを参照しているようでした。つまり、static直下に格納したものは、あくまでもstaticな扱いとなるため、サーバーrun以降にアップロードされた画像は classpath: の対象には入らないことがわかりました。表示させるためにはサーバー再起動が必要だと。。。
spring securityを挟んでいたため、サーバー再起動してしまうとsessionが切れてしまう、、、ナンセンス!
苦渋の選択として、staticフォルダに画像をuploadすることはできたので、画像をbase64でバイナリファイルに変換させてレスポンスさせるという遠回りに遠回りを重ねた方法をとりました。
画像の受け渡しだけで1週間ほどかかってしまったので、力尽きて調べるのを断念。また、やる気がでたらうまくできる方法を模索したいと思っています。
こちらの記事では、苦肉の策である画像をstaticに保存してバイナリーでViewに受け渡すコードを残します。
開発環境
eclipse java8
MySQL
spring boot2.2.1
Maven
Thymeleaf
処理の概要
やりたいこと:
①アカウント事にサムネイルが表示される。②サムネイルはユーザーにて変更可能。
③サムネイルが変更されたら、リアルタイムにフロント側も反映させる
画像の読み込み方:
①DBに「アカウント名.jpg」の文字列が格納されている
②ログインが行われたら、DBの文字列をfindByIdで拾って、uploadフォルダ内にある同じ名前の画像にアクセス
③バイナリに変換して、HTMLへレスポンス
④thymeleafで画像表示
ユーザー側でサムネイルが変更された場合:
①アップロード画像の名前を「アカウント名.jpg」にリネーム
②画像を圧縮してファイルサイズを縮小
③uploadフォルダに格納
※同じ名前の画像が既にある場合、上書き保存となるため、余分なファイルが増える心配がない。
uploadフォルダの作成
static直下にuploadフォルダを作成
src/main/resources/static/upload
filterの認証対象からuploadフォルダを除外する
認証にはspring securityを使用しておりあすので、WebSecurityConfigurerAdapter内で認証から除外する必要があります。これを行わないと、フォルダにアクセスができません。
@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/favicon.ico", "/css/**", "/js/**", "/images/**", "/fonts/**","/upload/**"); }
HTMLを用意
HTMLには、
①画像表示
②画像を変更するためのform
を書いていきます。
<!-- サムネイル表示用 --> <img alt="" th:src="${userPhoto}" class="icon"> <!-- サムネイル変更用 --> <form th:action="@{/photoupload}" method="post" enctype="multipart/form-data"> <p>サムネイルの変更</p> <input type="file" name="multipartFile"> <input type="submit" value="サムネイル変更" class="photchange"> </form>
画像を送信する際は、formタグの中に enctype="multipart/form-data"
を必ず明記してください。
formのname=””をControllerの受け取り型&変数名と同じ名前にしておけば、自動的にマッピングしてくれるので@RequestParamを省略することができます。
画像を受け取るController
@PostMapping("/photoupload") public String photoupload(EmployeeEntity employeeEntity, @AuthenticationPrincipal LoginAccount account, MultipartFile multipartFile, Model model) throws Exception { if (!multipartFile.isEmpty()) { try { // ファイル名を社員番号にリネイム File oldFileName = new File(multipartFile.getOriginalFilename()); File newFileName = new File(account.getUsername() + ".jpg"); oldFileName.renameTo(newFileName); // 保存先を定義 String uploadPath = "src/main/resources/static/upload/"; byte[] bytes = multipartFile.getBytes(); // 指定ファイルへ読み込みファイルを書き込み BufferedOutputStream stream = new BufferedOutputStream( new FileOutputStream(new File(uploadPath + newFileName))); stream.write(bytes); stream.close(); // 圧縮 File input = new File(uploadPath + newFileName); BufferedImage image = ImageIO.read(input); OutputStream os = new FileOutputStream(input); Iterator<ImageWriter> writers = ImageIO .getImageWritersByFormatName("jpg"); ImageWriter writer = (ImageWriter) writers.next(); ImageOutputStream ios = ImageIO.createImageOutputStream(os); writer.setOutput(ios); ImageWriteParam param = new JPEGImageWriteParam(null); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(0.30f); writer.write(null, new IIOImage(image, null, null), param); os.close(); ios.close(); writer.dispose(); // DBに写真の名前を格納する service.updatePhoto(account.getUsername(), newFileName.toString()); } catch (Exception e) { System.out.println(e); } } return "forward:/top"; }
全然オブジェクト指向ではないですね・・・外だしして綺麗にしたいところですが、格納までの工程が階段式で分かりやすいので、ずらーーーっと書いてしまいました。
メソッドの引数で設定している@AuthenticationPrincipal LoginAccount accountは、securityで設定した権限を表示するためのものになります。これを利用して、現在ログインしている人のアカウント名を拾ってファイル名に適用させます。
そして、強制的に拡張子をjpgファイルにしてしまっています。次工程の圧縮もjpg縛りの圧縮方法を採用しているため、このようにしました。もちろん他のファイル圧縮も設定すれば可能です。PNGのみ、圧縮方法が異なるためサポートされていない可能性があります。
圧縮のクオリティは34行目
param.setCompressionQuality(0.30f);
を変更すると変わります。
次回は、画面にはきだすためのメソッドを作成します。