Upload và download các file là những tác vụ rất phổ biến mà các lập trình viên thường xuyên phải triển khai trong quá trình phát triển ứng dụng.
Trong bài viết này, chúng ta sẽ tìm hiểu về cách quản lý file cơ bản trong ứng dụng SpringBoot REST API. Những file này sau khi được upload sẽ được lưu trữ trong hệ quản trị cơ sở dữ liệu MySQL.
Trước tiên, chúng ta sẽ xây dựng API REST để có thể upload và download file, sau đó kiểm tra các API đã viết bằng Postman. Chúng ta cũng có thể làm một giao diện frontend cơ bản, áp dụng javascript để có thể có thể upload file.
OK, bắt đầu nào!!! 😀😁
Tạo ứng dụng
Chúng ta có thể sử dụng theo các cách sau :
- Sử dụng trang web https://start.spring.io/ (đây là trang web cho phép tạo ứng dụng SpringBoot), sau khi điền các thông tin cơ bản, chọn các dependence cần thiết, chúng ta có thể download về và sử dụng.
- Sử dụng các IDE có hỗ trợ tạo ứng dụng SpringBoot : VSCode, Intellij, …
Sau đây là cấu trúc ứng dụng hoàn chỉnh mà các bạn có thể tham khảo.
Danh sách các dependence được sử dụng
Trong ứng dụng này chúng ta sẽ lưu trữ các file trong cơ sở dữ liệu MySQL, nên sẽ cần các dependence JPA, MySQL, Spring Web, Lombok
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Cấu hình Database và Multipart File
Tiếp theo, chúng ta cần định cấu hình url, username và password của cơ sở dữ liệu MySQL. Bạn có thể cấu hình nó trong src/main/resources/application.properties
## Cấu hình DATASOURCE
spring.datasource.url=jdbc:mysql://localhost:3306/db-image?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=123
## Cấu hình Hibernate
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
## Cấu hình MULTIPART
# Max file size
spring.servlet.multipart.max-file-size=100MB
# Max Request Size
spring.servlet.multipart.max-request-size=100MB
Các bạn có thể thay đổi các thông tin trong tệp cấu hình trên dựa trên các thông số trong ứng dụng của các bạn
Image Entity
Đầu tiên chúng ta sẽ tạo Image Entity, class này sẽ ánh xạ với bảng image trong database
package vn.techmaster.image.entity;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity
@Table(name = "image")
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "name")
private String name;
@Column(name = "type")
private String type;
@Lob // Large Object
@Column(name = "data", columnDefinition = "LONGBLOB")
private byte[] data;
@Column(name = "created_at")
private LocalDateTime createdAt;
@PrePersist
public void prePersist() {
createdAt = LocalDateTime.now();
}
public Image(String name, String type, byte[] data) {
this.name = name;
this.type = type;
this.data = data;
}
}
Image.java
Trong Entity Image, chúng ta có 1 số thuộc tính sau
- id : sử dụng
@GeneratedValue(strategy = GenerationType.AUTO)
để generate id khi lưu vào database - name : tên của file
- type : loại file (ví dụ : image/png, image/jpg, …)
- data : Thông tin của file, lưu dưới dạng byte[]
- createdAt : thời gian upload
Anotation @Lob
chỉ định cơ sở dữ liệu sẽ lưu trữ thuộc tính này dưới dạng Large Object.
BLOB
– Binary Large Object thường dùng để lưu trữ dữ liệu ở dạng binary như image, audio hoặc video
Anotation @PrePersist
là một trong các life cycle của entity, được thực hiện trước khi entity được lưu vào database. Trong trường này chúng ta sử dụng @PrePersist
để lưu giá trị createdAt (thời gian upload) của image
Image Repository
Tiếp theo chúng ta tạo repository để có thể tương tác với database
package vn.techmaster.image.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import vn.techmaster.image.entity.Image;
import java.util.List;
public interface ImageRepository extends JpaRepository<Image, Integer> {
// Lấy danh sách tất cả image theo thời gian tạo giảm dần
List<Image> findByOrderByCreatedAtDesc();
}
ImageRepository
Image Controller
Bây giờ chúng ta viết các API REST để upload và download các file. Trong file ImageController chúng ta định nghĩa các API thực hiện các công việc sau:
- Lấy danh sách tất cả các image
- Upload image
- Xem image
- Download image
- Xóa image
Chú ý : Các API Xem image, Download image chúng ta cần set giá trị Content-Type trên header của response để có thể xem được ảnh ở phía fontend
Các bạn có thể xem nội dung file ImageController dưới đây để biết thêm chi tiết
package vn.techmaster.image.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import vn.techmaster.image.entity.Image;
import vn.techmaster.image.service.ImageService;
@RestController
@RequestMapping("api/v1/images")
@RequiredArgsConstructor
public class ImageController {
private final ImageService imageService;
// Lấy danh sách ảnh
@GetMapping("")
public ResponseEntity<?> getAllImage() {
return ResponseEntity.ok(imageService.getAllImage());
}
// Xem ảnh
@GetMapping("{id}")
public ResponseEntity<?> readImage(@PathVariable Integer id) {
Image image = imageService.getImage(id);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(image.getType()))
.body(image.getData());
}
// Upload ảnh
@PostMapping("")
public ResponseEntity<?> uploadImage(@ModelAttribute("file") MultipartFile file) {
return new ResponseEntity<>(imageService.uploadImage(file), HttpStatus.CREATED);
}
// Download ảnh
@GetMapping("/download/{id}")
public ResponseEntity<?> downloadImage(@PathVariable Integer id) {
Image image = imageService.getImage(id);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(image.getType()))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + image.getName() + "\"")
.body(new ByteArrayResource(image.getData()));
}
// Xóa ảnh
@DeleteMapping("{id}")
public ResponseEntity<?> deleteImage(@PathVariable Integer id) {
imageService.deleteImage(id);
return ResponseEntity.noContent().build(); // 204
}
}
ImageController
Image Service
Mỗi API được định nghĩa trong ImageController
chúng ta sẽ định nghĩa các method tương ứng trong ImageService
để xử lý. Trong này chúng ta định nghĩa các method xử lý các công việc sau
- Lấy danh sách tất cả file
- Lấy chi tiết file
- Upload file
- Xóa file
Trong ImageService
cần tương tác với database vì vậy cần inject bean imageRepository
vào để có thể sử dụng
Ngoài ra trong này có inject thêm bean FileUtils
, tác dụng của bean này chứa các method hữu ích để thao tác với file
Các bạn có thể xem nội dung file ImageService dưới đây để biết thêm chi tiết
package vn.techmaster.image.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import vn.techmaster.image.entity.Image;
import vn.techmaster.image.exception.NotFoundException;
import vn.techmaster.image.repository.ImageRepository;
import vn.techmaster.image.response.ImageResponse;
import vn.techmaster.image.utils.FileUtils;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ImageService {
private final ImageRepository imageRepository;
private final FileUtils fileUtils;
public List<Image> getAllImage() {
return imageRepository.findByOrderByCreatedAtDesc();
}
public Image getImage(Integer id) {
return imageRepository.findById(id).orElseThrow(() -> {
throw new NotFoundException("Not found image with id = " + id);
});
}
public Image uploadImage(MultipartFile file) {
fileUtils.validateFile(file);
try {
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
Image image = new Image(fileName, file.getContentType(), file.getBytes());
return imageRepository.save(image);
} catch (Exception e) {
throw new RuntimeException("Upload image error");
}
}
public void deleteImage(Integer id) {
Image image = imageRepository.findById(id).orElseThrow(() -> {
throw new NotFoundException("Not found image with id = " + id);
});
imageRepository.delete(image);
}
}
ImageService
Phần định nghĩa và xử lý exception, các bạn có thể tham khảo code trong repo mình đặt ở phía dưới
FileUtils
FileUtils
chứa các method hữu ích khi thao tác với file, chủ yếu trong phần này chúng ta sẽ có các method để validate file trước khi lưu vào trong database
Ví dụ một số trường hợp chúng ta cần validate:
- Kiểm tra tên file?
- Kiểm tra type file có hợp lệ hay không?
- Kiểm tra size file có trong khoảng cho phép hay không?
package vn.techmaster.image.utils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import vn.techmaster.image.exception.BadRequestException;
import java.util.ArrayList;
import java.util.List;
@Component
public class FileUtils {
// Validate file
public void validateFile( MultipartFile file) {
// Kiểm tra tên file
String fileName = file.getOriginalFilename();
if(fileName == null || fileName.isEmpty()) {
throw new BadRequestException("tên file không được để trống");
}
// image.png -> png
// avatar.jpg -> jpg
// Kiểm tra đuôi file (jpg, png, jpeg)
String fileExtension = getFileExtension(fileName);
if(!checkFileExtension(fileExtension)) {
throw new BadRequestException("file không đúng định dạng");
}
// Kiểm tra dung lượng file (<= 2MB)
double fileSize = (double) (file.getSize() / 1_048_576);
if( fileSize > 2) {
throw new BadRequestException("file không được vượt quá 2MB");
}
}
// Lấy extension của file (ví dụ png, jpg, ...)
public String getFileExtension(String fileName) {
int lastIndexOf = fileName.lastIndexOf(".");
return fileName.substring(lastIndexOf + 1);
}
// Kiểm tra extension của file có được phép hay không
public boolean checkFileExtension(String fileExtension) {
List<String> extensions = new ArrayList<>(List.of("png", "jpg", "jpeg", "pdf"));
return extensions.contains(fileExtension.toLowerCase());
}
}
FileUtils
Test API
Phần này, chúng ta sẽ test API dựa trên công cụ PostMan
1. Lấy danh sách ảnh
2. Upload ảnh
3. Xem ảnh
4. Download ảnh
5. Xóa ảnh
Vậy là chúng ta đã hoàn thành ứng dụng “File Management” với các chức năng cơ bản. Hi vọng các bạn thấy bài viết này hữu ích và thú vị 😁😁😁
Trong phần tiếp theo, chúng ta sẽ làm ứng dụng FrontEnd cơ bản kết nối với ứng dụng SpringBoot để có thể thao tác với file ở trên UI
Phần sources code của bài viết này, các bạn có thể tham khảo tại đây: https://github.com/buihien0109/image-management/tree/main/image-management-backend
Các bạn có thể tham khảo thêm khóa học này nhé:
Bình luận
Chúc mừng thầy lên 1 bài đầu tiên.
Hãy là một con rùa, mỗi ngày tiến lên một bước cũng tốt.