Bài viết này là phần tiếp theo của bài viết trước, tại đó tôi đã hướng dẫn cho bạn làm như thế nào để upload files và lưu trữ chúng tại filesystem nội bộ.
Chúng ta sẽ sử dùng lại hầu hết các mã và khái niệm được mô tả trong bài viết trước. Vì vậy, tôi khuyên bạn nên tìm hiểu bài viết trước khi đọc cái này.
Sau đây là cấu trúc thư mục hoàn chỉnh của ứng dụng để bạn tham khảo
1. JPA và MySQL dependencies
Vì chúng ta sẽ lưu trữ các tệp trong cơ sở dữ liệu MySQL, nên chúng ta sẽ cần các dependencies JPA và MySQL cùng với dependency Web
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Nếu bạn đang xây dựng dự án từ đầu, bạn có thể tạo bộ khung project từ Spring Initialzr website.
2. Cấu hình Database và Multipart File properties
Tiếp theo, chúng ta cần configure các thông tin để kết nối đến database MySQL như url, username, và password. Chúng ta sẽ configure nó tại file src/main/resources/application.properties
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url= jdbc:mysql://localhost:3306/file_demo?useSSL=false
spring.datasource.username= root
spring.datasource.password= 123456
## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update
## Hibernate Logging
logging.level.org.hibernate.SQL= DEBUG
## MULTIPART (MultipartProperties)
# Enable multipart uploads
spring.servlet.multipart.enabled=true
# Threshold after which files are written to disk.
spring.servlet.multipart.file-size-threshold=2KB
# Max file size.
spring.servlet.multipart.max-file-size=200MB
# Max Request Size
spring.servlet.multipart.max-request-size=215MB
File properties trên có chứa các thông tin thuộc tính của file Multipart. Bạn có thể thay đổi các thuộc tính đó sao cho đúng với requirements của bạn.
3. DBFile model
Hãy tạo một file entity để mô hình hóa các thuộc tính sẽ được lưu trữ vào database
package com.mpt.filedemo.model;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@Entity
@Table(name = "files")
public class DBFile {
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
private String fileName;
private String fileType;
@Lob
private byte[] data;
public DBFile() {
}
public DBFile(String fileName, String fileType, byte[] data) {
this.fileName = fileName;
this.fileType = fileType;
this.data = data;
}
// Getters and Setters (Omitted for brevity)
}
Lưu ý, nội dung của file sẽ được lưu trữ dưới dạng một mảng byte trong cơ sở dữ liệu.
4. DBFileRepository
Kế tiếp, chúng ta cần tạo một file repository để thực hiện lưu files xuống database và lấy chúng ra.
package com.mpt.filedemo.repository;
import com.mpt.filedemo.model.DBFile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DBFileRepository extends JpaRepository<DBFile, String> {
}
5. DBFileStorageService
Dưới đây là thông tin class DBFileStorageService chứa các methods để lưu trữ và truy xuất files to/from database
package com.mpt.filedemo.service;
import com.mpt.filedemo.exception.FileStorageException;
import com.mpt.filedemo.exception.MyFileNotFoundException;
import com.mpt.filedemo.model.DBFile;
import com.mpt.filedemo.repository.DBFileRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@Service
public class DBFileStorageService {
@Autowired
private DBFileRepository dbFileRepository;
public DBFile storeFile(MultipartFile file) {
// Normalize file name
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
// Check if the file's name contains invalid characters
if(fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
DBFile dbFile = new DBFile(fileName, file.getContentType(), file.getBytes());
return dbFileRepository.save(dbFile);
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
public DBFile getFile(String fileId) {
return dbFileRepository.findById(fileId)
.orElseThrow(() -> new MyFileNotFoundException("File not found with id " + fileId));
}
}
6. FileController (File upload/download REST APIs)
Cuối cùng, dưới đây là thông tin các Rest APIs để upload và download files
package com.mpt.filedemo.controller;
import com.mpt.filedemo.model.DBFile;
import com.mpt.filedemo.payload.UploadFileResponse;
import com.mpt.filedemo.service.DBFileStorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RestController
public class FileController {
private static final Logger logger = LoggerFactory.getLogger(FileController.class);
@Autowired
private DBFileStorageService DBFileStorageService;
@PostMapping("/uploadFile")
public UploadFileResponse uploadFile(@RequestParam("file") MultipartFile file) {
DBFile dbFile = DBFileStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(dbFile.getId())
.toUriString();
return new UploadFileResponse(dbFile.getFileName(), fileDownloadUri,
file.getContentType(), file.getSize());
}
@PostMapping("/uploadMultipleFiles")
public List<UploadFileResponse> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
return Arrays.asList(files)
.stream()
.map(file -> uploadFile(file))
.collect(Collectors.toList());
}
@GetMapping("/downloadFile/{fileId}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileId) {
// Load file from database
DBFile dbFile = DBFileStorageService.getFile(fileId);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(dbFile.getFileType()))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + dbFile.getFileName() + "\"")
.body(new ByteArrayResource(dbFile.getData()));
}
}
7. Kết quả
Tôi sẽ sử dụng lại code cho phần front-end đã hoàn chỉnh mà tôi đã trình bày trong bài viết trước. Bạn nên xem bài viết đó để tìm hiểu thêm về code front-end.
Khi bạn đã có code giao diện người dùng, hạy chạy ứng dụng và bắt đầu test.
OK, đó là tất cả những gì tôi muốn trình bày. Cảm ơn vì đã đọc!

Hình như anh copy source của người khác :D
ReplyDeletelàm theo hướng dẫn mà vẫn lỗi nhiều quá, anh có thể cho tôi xin project mẫu này được không?
ReplyDelete