Bài viết gốc xem ở đây.

Spring là một framework ứng dụng Java và Spring Boot là một sự phát triển của Spring, nó giúp chúng ta dễ dàng và nhanh chóng tạo ra sản phẩm dựa trên các công cụ của Spring.

Các kiểu form mã hoá HTML

Đây là ba kiểu form mã hoá HTML cho một POST request:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

Application/x-www-form-urlencoded là kiểu mã hoá mặc định, tại đây các giá trị được mã hoá trong key-value được phân tách bằng &. Kí tự = được sử dụng giữa key và value. Các ký tự không phải là chữ và số được mã hoá theo phần trăm. Loại mã hoá này không phù hợp với các tệp nhị phân.

Multipart/form-data được sử dụng cho các dữ liệu không phải là ACSII và các file nhị phân. Thuộc tính của phần tử đầu vào được cài đặt trong file.

Text/plain được sử dụng để debug.

Ví dụ tải file lên trong Spring

Trong ví dụ sau, chúng ta có một web form để lựa chọn một file để tải lên server. File này được tải lên trong đường dẫn /var/www/upload/ .

Đường dẫn tải lên

/var/www/ là một đường dẫn tiêu chuẩn cho web trong Debian Linux.

$ ls -ld /var/www/upload/
drwxrwxr-x 2 www-data www-data 4096 Dec  3 14:29 /var/www/upload/

Chúng ta sẽ tải file vào đường dẫn /var/www/upload/ . Đường dẫn của file có thể được thay đổi bởi người sử dụng, người ở trong danh sách trong www-data. Bởi vậy người chạy web server bắt buộc phải ở trong danh sách này.

Ứng dụng

Sau đây là mã nguồn của ứng dụng web Spring Boot.
 

pom.xml
src
├───main
│   ├───java
│   │   └───com
│   │       └───zetcode
│   │           │   Application.java
│   │           ├───controller
│   │           │       MyController.java
│   │           ├───exception
│   │           │       StorageException.java
│   │           └───service
│   │                   StorageService.java
│   └───resources
│       │   application.properties
│       └───static
│               failure.html
│               index.html
│               success.html
└───test
    └───java

Đây là cấu trúc của dự án ứng dụng Spring.

 

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>
    <groupId>com.zetcode</groupId>
    <artifactId>springbootuploadfile</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>13</maven.compiler.source>
        <maven.compiler.target>13</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

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

Đây là file Maven pom.xml.

 

com/zetcode/controller/MyController.java

package com.zetcode.controller;

import com.zetcode.exception.StorageException;
import com.zetcode.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.multipart.MultipartFile;

@Controller
public class MyController {

    @Autowired
    private StorageService storageService;

    @RequestMapping(value = "/doUpload", method = RequestMethod.POST,
            consumes = {"multipart/form-data"})
    public String upload(@RequestParam MultipartFile file) {

        storageService.uploadFile(file);

        return "redirect:/success.html";
    }

    @ExceptionHandler(StorageException.class)
    public String handleStorageFileNotFound(StorageException e) {

        return "redirect:/failure.html";
    }
}

MyController đọc file từ request và lưu nó vào đường dẫn đã chọn từ trước.

 

@Autowired
private StorageService storageService;

StorageService lưu trữ file trong ổ cứng.

 

@RequestMapping(value = “/doUpload”, method = RequestMethod.POST,
consumes = {“multipart/form-data”})
public String upload(@RequestParam MultipartFile file) {

Phương thức upload()  được ánh xạ vào doUpload URL pattern. Vì chúng ta đang gửi dữ liệu lên server, chúng ta sử dụng POST request. Tham số request có kiểu MultipartFile.

 

return “redirect:/successs.html”;

Chúng ta sẽ hiển thị thông báo đã tải file lên thành công.

 

@ExceptionHandler(StorageException e) {
	return “redirect:/failure.html”;
}

Chúng ta sẽ xử lý StorageException như sau.

 

com/zetcode/exception/StorageException.java

package com.zetcode.exception;

public class StorageException extends RuntimeException {
	
    public StorageException (String message){
	   super(message);
    }

    public StorageException(String message, Throwable cause){
	   super(message, cause);
    }
}

Đây là file custom StorageException của chúng ta. Nó sẽ được ném ra khi file không thể lưu trữ trong hệ thống.

 

com/zetcode/service/StorageService.java

package com.zetcode.service;

import com.zetcode.exception.StorageException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

@Service
public class StorageService {

    @Value("${upload.path}")
    private String path;

    public void uploadFile(MultipartFile file) {

        if (file.isEmpty()) {

            throw new StorageException("Failed to store empty file");
        }

        try {
            var fileName = file.getOriginalFilename();
            var is = file.getInputStream();

            Files.copy(is, Paths.get(path + fileName),
                    StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {

            var msg = String.format("Failed to store file %f", file.getName());

            throw new StorageException(msg, e);
        }
    }
}

StorageService sao chép dữ liệu từ inputstream và lưu nó vào ổ cứng.
 

@Value(“${upload.path}”)
private String path;

Chúng ta đọc đường dẫn từ file application.properties sử dụng annotation @Value.

 

if (file.isEmpty()) {
	throw new StorageException(“Failed to store empty file”);
}

Chúng ta đảm bảo rằng một file đã được chọn với phương thức isEmpty().

 

var fileName = file.getOriginalFilename();

Chúng ta lấy được tên file với phương thức getOriginalFilename().

 

var is = file.getInputStream();

Chúng ta lấy đầu vào với phương thức getInputStream(). 

 

Files.copy(is, Paths.get(path + fileName),
	StandardCopyOption.REPLACE_EXISTING);

File được sao chép vào thư mục đích từ input với Files.copy() .

 

resources/application.properties

upload.path=/var/www/upload/

Trong application.properties, chúng ta có một giá trị upload.path thể hiện đường dẫn của file tải lên.

 

resources/static/index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Uploading file</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <h1>Uploading file</h1>

        <form action="/doUpload" method="post" enctype="multipart/form-data">
            <label>Enter file</label>
            <input type="file" name="file">
            <button type="submit">Upload</button>
        </form>
    </body>
</html>

Đây là trang chủ. Nó là file tĩnh ở trong đường dẫn src/main/resources/static. Nó gồm một form để chọn file và gửi nó lên ứng dụng Spring.

 

<form action=”/doUpload” method=”post” enctype=”multipart/form-data”>

Chúng ta lựa chọn pattern doUpload URL. Một request được tạo bởi form này sẽ được xử lý bởi một controller Spring. Thuộc tính enctype chỉ định kiểu mã hoá dữ liệu multipart/form-data, đây là điều kiện để có thể tải một file lên bằng HTML form.

 

<input type = “file” name =”file”>

Thuộc tính type của thẻ input dùng để cho người dùng chọn một file.

 

<button type = “submit”>Upload</button>

Cuối cùng là nút Submit.

 

resources/static/success.html

<!DOCTYPE html>
<html>
    <head>
        <title>Success</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <p>File successfully uploaded</p>
    </body>
</html>

success.html sẽ được hiện lên khi file được tải thành công lên server.

 

resources/static/failure.html

<!DOCTYPE html>
<html>
    <head>
        <title>Failure</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <p>Failed to upload file</p>
    </body>
</html>

failure.html sẽ được hiện ra khi file tải lên bị lỗi.

 

com/zetcode/Application.java

package com.zetcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application  {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Đây là đoạn code để cài đặt ứng dụng Spring Boot.

 

Trong hướng dẫn này, chúng ta đã học được cách để tải một file lên trong một ứng dụng Spring.