Header Ads

Seo Services

Hế lô anh em, là mình đây!

Ngày nay trong các ứng dụng hoặc các hệ thống thông tin có một đối tượng mà chúng ta rất hay phải xử lý và làm việc đó chính là files.

Đối với files chúng ta cũng có nhiều thao tác khác nhau như upload, download, mã hóa files... Bài viết tiếp theo này mình sẽ cùng anh em tìm hiểu cách để chúng ta upload/download files trong các ứng dụng sử dụng Spring Boot.

Note: Việc upload file chúng ta có thể upload lên server hoặc lưu trực tiếp vào database. Bài viết này mình cùng anh em thực hiện upload/download file trực tiếp từ database.

Okay, let's gooo!

1. Chúng ta làm gì và chuẩn bị gì?

1.1 - Làm gì?

Thông thường việc upload/download files sẽ được thực hiện ở phía client (web app, mobile app...) thông qua việc gọi các APIs do phía server cung cấp.

Nhưng để đơn giản thì trong bài viết này chúng ta sẽ chỉ xây dựng các APIs và sử dụng Postman để tạo các request thay cho phía client.

Các APIs mình sẽ cùng anh em viết:

Methods                   URLs                                                   Actions

POST                        /files/upload                                         Tải một file lên

POST                        /files/uploadMultipleFiles                  Tải nhiều files lên một lúc

GET                          /files/getSingleFile[fileId]                    Lấy thông tin một file đơn lẻ

GET                          /files/getAllFiles                                    Lấy toàn bộ files

GET                          /files/download                                    Tải file xuống   

1.2 - Chuẩn bị gì?

Nói chuẩn bị nghe hơi to tát thực ra đơn giản chỉ là những công cụ mà chúng ta sử dụng thôi:

+ Java (ver: 11)

+ Spring Boot (ver: 2.7.0)

+ MariaDB (ver: 10.6.7)

+ Maven (ver: 3.8.4)

+ IntelliJ Idea IDE (ver: Ultimate)

+ Postman (ver: 9.19.3)

Note: Anh em có thể sử dụng các công cụ khác miễn sao anh em quen dùng và đơn giản nhất để setup project là được!

Okay, vậy là về cơ bản chắc anh em cũng nắm được chúng ta sẽ làm gì và cần phải chuẩn bị những công cụ gì. Phần tiếp mình sẽ cùng anh em setup database cũng như tạo ứng dụng để có thể bắt đầu triển khai coding.

2. Setup Database và Tạo ứng dụng Spring Boot

2.1 - Setup Database

Để lưu trữ thông tin files mình sẽ tạo một bảng có tên FILE trong cơ sở dữ liệu với các trường thông tin:

ID: Id của file,

NAME: Tên file

TYPE: Loại file (extension)

DATA: Nội dung file  

Anh em có thể sử dụng đoạn script bên dưới để tạo bảng cho nhanh.

CREATE TABLE `FILE` (
  `ID` varchar(36) NOT NULL COMMENT 'ID',
  `NAME` varchar(255) DEFAULT NULL COMMENT 'Tên file',
  `TYPE` varchar(100) DEFAULT NULL COMMENT 'Loại files',
  `DATA` mediumblob DEFAULT NULL COMMENT 'Dữ liêu',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Vậy là chúng ta đã tạo được bảng để lưu trữ thông tin files. Bây giờ mình sẽ cùng anh em tạo một ứng dụng Spring Boot.

2.2 - Tạo ứng dụng Spring Boot

Có rất nhiều cách để khởi tạo một ứng dụng Spring Boot và cách đơn giản nhất là anh em có thể truy cập vào trang https://start.spring.io/ rồi chọn các thư viện cần thiết.

Bài viết này mình sử dụng maven nên yêu cầu một số thư viện sau:

<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-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.mariadb.jdbc</groupId>
		<artifactId>mariadb-java-client</artifactId>
		<scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-configuration-processor</artifactId>
		<optional>true</optional>
	</dependency>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.12</version>
		<scope>provided</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

3. Coding

■ Cấu hình kết nối đến cơ sở dữ liệu:

Khi làm việc với cơ sở dữ liệu chúng ta phải cấu hình để kết nối đến cơ sở dữ liệu đó. May mắn là đối với Spring Boot thì việc cấu hình này tương đối đơn giản.

Anh em nào sử dụng file application.properties thì cấu hình như sau:

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/<tên database>
spring.datasource.username=<username>
spring.datasource.password=<password>
spring.datasource.driver-class-name =com.mysql.jdbc.Driver

■ Tạo Entity

Đầu tiên chúng ta phải tạo một entity class để mapping với bảng FILE trong cơ sở dữ liệu.

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Table(name = "FILE")
@Entity
public class File {
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    @Column(name = "ID")
    private String id;

    @Column(name = "NAME")
    private String name;

    @Column(name = "TYPE")
    private String type;

    @Lob
    @Column(name = "DATA")
    private byte[] data;
}

Ở đây ID sẽ tự sinh theo giá trị uuid bằng cách cấu hình thông qua hai annotation:

@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")

Ngoài ra, việc lưu trữ files vào cơ sở dữ liệu sẽ được lưu dưới dạng các bytes nên trường DATA mình phải sử dụng annotation @Lob để đánh dấu trường này lưu thông tin dạng nhị phân.

■ Tầng Repository

@Repository
public interface FileRepository extends JpaRepository<File, String> {
}

Vẫn là "tận dụng" sức mạnh của Spring Data JPA nên tầng repository mình chỉ cần kế thừa interface JpaRepository là được!

■ Tầng Service

Như thường lệ để đạt được tính đa hình mình sử dụng interface FileService để định nghĩa toàn bộ các phương thức mình sẽ viết.

public interface FileService {
    Boolean upload(MultipartFile file) throws IOException;

    Boolean uploadMultipleFiles(List<MultipartFile> files);

    FileSdo getSingleFile(String fileId) throws Exception;

    List<FileSdo> getAllFiles();

    byte[] download(String fileId);
}

Sau đó sử dụng class FileServicImpl để implements FileService và triển khai các phương thức. Tại đây cũng là nơi mình xử lý hầu hết logic của các phương thức.

@Service
public class FileServiceImpl implements FileService {
    @Autowired
    FileRepository fileRepo;

    @Override
    public Boolean upload(MultipartFile file) throws IOException {
        if (Objects.nonNull(file)) {
            String fileName = StringUtils.cleanPath(file.getOriginalFilename());

            File uploadedFile = File.builder()
                    .name(fileName)
                    .type(file.getContentType())
                    .data(file.getBytes())
                    .build();
            fileRepo.save(uploadedFile);
            return true;
        }
        return false;
    }

    @Override
    public Boolean uploadMultipleFiles(List<MultipartFile> files) {
        if(Objects.nonNull(files)) {{
            List<File> uploadedFiles = files.stream()
                    .map(file -> {
                        try {
                            return File.builder()
                                    .name(StringUtils.cleanPath(file.getOriginalFilename()))
                                    .type(file.getContentType())
                                    .data(file.getBytes())
                                    .build();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return null;
                    }).collect(Collectors.toList());
            fileRepo.saveAll(uploadedFiles);
            return true;
        }}
        return false;
    }

    @Override
    public FileSdo getSingleFile(String fileId) throws Exception {
        return fileRepo.findById(fileId)
                .map(file -> FileSdo.builder()
                        .id(file.getId())
                        .name(file.getName())
                        .data(file.getData())
                        .url(ServletUriComponentsBuilder
                                .fromCurrentContextPath()
                                .path("/files/")
                                .path(file.getId())
                                .toUriString())
                        .type(file.getType())
                        .size(file.getData().length)
                        .build())
                .orElseThrow(() -> new Exception(""));
    }

    @Override
    public List<FileSdo> getAllFiles() {
        return fileRepo.findAll()
                .stream()
                .map(file -> FileSdo.builder()
                        .id(file.getId())
                        .name(file.getName())
                        .url(ServletUriComponentsBuilder
                                .fromCurrentContextPath()
                                .path("/files/")
                                .path(file.getId())
                                .toUriString())
                        .type(file.getType())
                        .size(file.getData().length)
                        .build())
                .collect(Collectors.toList());
    }

    @Override
    public byte[] download(String fileId) {
        Optional<File> optFile = fileRepo.findById(fileId);
        return optFile.map(File::getData).orElse(null);
    }
}

■ Tầng Controller

Cuối cùng là tầng controller mình sẽ định nghĩa những phương thức tương ứng với tầng service như sau:

@RestController
@RequestMapping("/files")
public class FileController {
    @Autowired
    FileService fileService;

    @PostMapping("/upload")
    public ResponseEntity<ResponseMessage> uploadFile(@RequestBody MultipartFile file) {
        String message;
        try {
            fileService.upload(file);
            message = "Upload file thành công!";
            return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message));
        } catch (Exception e) {
            message = "Upload file không thành công!";
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage(message));
        }
    }

    @PostMapping("/uploadMultipleFiles")
    public ResponseEntity<ResponseMessage> uploadMultipleFiles(
            @RequestBody List<MultipartFile> files
    ) {
        String message;
        try {
            fileService.uploadMultipleFiles(files);
            message = "Upload file thành công!";
            return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message));
        } catch (Exception e) {
            message = "Upload file không thành công!";
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage(message));
        }
    }

    @GetMapping("/getSingleFile")
    public ResponseEntity<FileSdo> getSingleFile(@RequestParam String fileId) throws Exception {
        FileSdo fileSdo = fileService.getSingleFile(fileId);
        return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileSdo.getName() + "\"").body(fileSdo);
    }

    @GetMapping("/getAllFiles")
    public ResponseEntity<List<FileSdo>> getAllFiles() {
        List<FileSdo> files = fileService.getAllFiles();
        return ResponseEntity.ok().body(files);
    }

    @GetMapping("/download")
    public ResponseEntity<byte[]> download(@RequestParam String fileId) {
        byte[] data = fileService.download(fileId);
        return ResponseEntity.ok().body(data);
    }
}

Ở đây mình có sử dụng đối tượng FileSdo để đóng gói các thông tin của file mà mình muốn trả ra:

@Builder
@Data
public class FileSdo {
    private String id;
    private String name;
    private byte[] data;
    private String url;
    private String type;
    private long size;
}

Ngoài ra thì còn có đối tượng ResponseMessage để trả về các thông báo cho việc upload file thành công hay không.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseMessage {
    private String message;
}

■ Cấu trúc toàn bộ chương trình:

4. Testing

Đầu tiên anh em chạy ứng dụng lên sau đó sử dụng Postman để test. 

Note: Nếu anh em không cấu hình port cho ứng dụng thì mặc định ứng dụng Spring Boot sẽ chạy ở port 8080. Nhưng trong một số trường hợp có thể anh em đã sử dụng port 8080 rồi thì anh em có thể cấu hình lại trong file application.properties bằng thuộc tính server.port=<port>

Ví dụ port 8080 của mình đã dùng rồi thì mình có thể sử dụng port 8089 bằng cách cấu hình: server.port=8089

■ API upload: http://localhost:8089/files/upload

■ API upload multiple files: http://localhost:8089/files/uploadMultipleFiles

■ API lấy thông tin file theo ID: http://localhost:8089/files/getSingleFile?fileId=<fileId>

■ API lấy toàn bộ files: http://localhost:8089/files/getAllFiles

■ API download file: http://localhost:8089/files/download?fileId=<fileId>

■ Kiểm tra thông tin trong database:

5. Lời kết

Vậy là trong bài viết này mình đã cùng anh em tìm hiểu cách để chúng ta thực hiện upload/download files thông qua database cũng nhưng viết các APIs tương tứng trong ứng dụng Spring Boot.

Có thể anh em không để ý nhưng ở đây mình sử dụng đối tượng MultipartFile để làm việc với file. Đây cũng là đối tượng mà chúng ta sẽ gặp rất nhiều khi làm việc với file trong Java nói chung và Spring Boot nói riêng.

Tất nhiên trong thực tế chúng ta không chỉ upload hay download mỗi đối tượng file mà còn rất nhiều thông tin khác. Khi đó cách làm của chúng ta cũng phải khác đi và mình sẽ cùng anh em tìm hiểu trong các bài viết tiếp theo. 

Anh em có thể tải source code của bài viết hôm nay TẠI ĐÂY

Hẹn gặp lại anh em trong các bài viết tiếp theo nhé. Bye! Thanks All ❤️❤️❤️

Không có nhận xét nào:

Được tạo bởi Blogger.