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: