Upload Image using Cloudinary + Spring Boot + MySQL

 Tải hình ảnh lên với Cloudinary + Spring Boot + MySQL

Bài viết này, mình sẽ hướng dẫn các bạn tạo API từ Spring Boot để có thể upload, xoá, cập nhật ảnh lưu thông tin vào MySQL và sử dụng Cloudinary làm nơi chứa ảnh.

Để xây dựng được hệ thống như trên ta cần:
- Tài khoản Cloudinary (có thời hạn)
- Tool Intellij IDEA (có thể sử dụng sts, netbeans, eclipse,...)
- Database là MySQL để hỗ trợ lưu thông tin (có thể sử dụng database khác)
- Tool Postman để test API

Giao diện Cloudinary nơi chứa ảnh là ở menu Media Library:

Cấu trúc source Spring Boot:

MainController.java: nhận request, xử lý trả về dữ liệu
Message.java: hỗ trợ tuỳ biến thông điệp trả về
Image.java: là một entity mapping với database (ở đây mình dùng code first)
ImageRepository,java: là một repository kế thừ JpaRepository để xử lý
ImageService.java: khởi tạo là một service implement ImageRepository
CloudinaryService.java: là một service cấu hình và xử lý upload, delete trên Cloudinary
application.properties: cấu hình hệ thống chính
pom.xml: quản lý các dependences (hỗ trợ tải các thư viện)

Nội dung các file trên:
MainController.java
package com.tutorial.cloudinaryrest.controller;

import com.tutorial.cloudinaryrest.dto.Message;
import com.tutorial.cloudinaryrest.entity.Image;
import com.tutorial.cloudinaryrest.service.CloudinaryService;
import com.tutorial.cloudinaryrest.service.ImageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/cloudinary")
@CrossOrigin
public class MainController {

@Autowired
private CloudinaryService cloudinaryService;

@Autowired
private ImageService imageService;

@GetMapping("/files")
public ResponseEntity<List<Image>> files() {
List<Image> images = imageService.findAllByOrderById();
return new ResponseEntity(images, HttpStatus.OK);
}

@PostMapping("/upload")
public ResponseEntity<?> upload(@RequestParam("multipartFile") MultipartFile multipartFile) throws IOException {

BufferedImage bufferedImage = ImageIO.read(multipartFile.getInputStream());
if(bufferedImage == null){
return new ResponseEntity(new Message("hình nh không hp l"), HttpStatus.BAD_REQUEST);
}

Map result = cloudinaryService.upload(multipartFile);
Image image = new Image();
image.setNameDatabase(result.get("original_filename").toString());
image.setUrl(result.get("url").toString());
image.setNameCloundinary(result.get("public_id").toString());

imageService.save(image);

return new ResponseEntity(new Message("hình nh đã ti lên"), HttpStatus.OK);
}

@DeleteMapping("/delete/{id}")
public ResponseEntity<?> delete(@PathVariable("id") int id) throws IOException {

if(!imageService.existsById(id)) {
return new ResponseEntity(new Message("không tn ti"), HttpStatus.NOT_FOUND);
}

Image image = imageService.findById(id).get();
cloudinaryService.delete(image.getNameCloundinary());

imageService.deleteById(id);
return new ResponseEntity(new Message("hình nh đã xóa"), HttpStatus.OK);
}

@PutMapping("/update/{id}")
public ResponseEntity<?> update(@PathVariable("id") int id, @RequestParam("multipartFile") MultipartFile multipartFile) throws IOException {
BufferedImage bufferedImage = ImageIO.read(multipartFile.getInputStream());
if(bufferedImage == null){
return new ResponseEntity(new Message("hình nh không hp l"), HttpStatus.BAD_REQUEST);
}

Map result = cloudinaryService.upload(multipartFile);
Image image = imageService.findById(id).get();

cloudinaryService.delete(image.getNameCloundinary());

image.setNameDatabase(result.get("original_filename").toString());
image.setUrl(result.get("url").toString());
image.setNameCloundinary(result.get("public_id").toString());

imageService.save(image);

return new ResponseEntity(new Message("hình nh đã cp nht"), HttpStatus.OK);
}
}

Message.java
package com.tutorial.cloudinaryrest.dto;

public class Message {

private String msg;

public Message() {

}

public Message(String msg) {
this.msg = msg;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}
}

Image.java
package com.tutorial.cloudinaryrest.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Image {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String nameDatabase;
private String url;
private String nameCloundinary;

public Image() {
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getNameDatabase() {
return nameDatabase;
}

public void setNameDatabase(String nameDatabase) {
this.nameDatabase = nameDatabase;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getNameCloundinary() {
return nameCloundinary;
}

public void setNameCloundinary(String nameCloundinary) {
this.nameCloundinary = nameCloundinary;
}
}

ImageRepository
package com.tutorial.cloudinaryrest.repository;

import com.tutorial.cloudinaryrest.entity.Image;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ImageRepository extends JpaRepository<Image, Integer> {

List<Image> findAllByOrderById();

}

ImageService.java
package com.tutorial.cloudinaryrest.service;

import com.tutorial.cloudinaryrest.entity.Image;
import com.tutorial.cloudinaryrest.repository.ImageRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class ImageService {

@Autowired
private ImageRepository imageRepository;

public List<Image> findAllByOrderById(){
return imageRepository.findAllByOrderById();
}

public Optional<Image> findById(int id){
return imageRepository.findById(id);
}

public void save(Image image){
imageRepository.save(image);
}

public void deleteById(int id){
imageRepository.deleteById(id);
}

public boolean existsById(int id){
return imageRepository.existsById(id);
}

}

CloudinaryService.java
package com.tutorial.cloudinaryrest.service;

import com.cloudinary.Cloudinary;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Service
public class CloudinaryService {

private Cloudinary cloudinary;

private Map<String, String> valuesMap = new HashMap<>();
private Map<String, String> options = new HashMap<>();

public CloudinaryService() {
valuesMap.put("cloud_name", "<my_cloud_name>");
valuesMap.put("api_key", "<my_api_key>");
valuesMap.put("api_secret", "<my_api_secret>");
cloudinary = new Cloudinary(valuesMap);

options.put("folder", "images");
}

public Map upload(MultipartFile multipartFile) throws IOException {
File file = convert(multipartFile);

Map result = cloudinary.uploader().upload(file, options);
file.delete();
return result;
}

public Map delete(String id) throws IOException {
Map result = cloudinary.uploader().destroy(id, options);
return result;
}

private File convert(MultipartFile multipartFile) throws IOException {
File file = new File(multipartFile.getOriginalFilename());
FileOutputStream fo = new FileOutputStream(file);
fo.write(multipartFile.getBytes());
fo.close();
return file;
}
}

application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/cloudinary?useSSL=false&serverTimezone=UTC&useLegacyDateTimeCode=false
spring.datasource.username=root
spring.datasource.password=
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.tutorial</groupId>
<artifactId>cloudinaryrest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloudinaryrest</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<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>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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/com.cloudinary/cloudinary-http44 -->
<dependency>
<groupId>com.cloudinary</groupId>
<artifactId>cloudinary-http44</artifactId>
<version>1.24.0</version>
</dependency>

</dependencies>

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

</project>

Khi chúng ta chạy Spring Boot, thì MySQL sẽ tự tạo table theo Image entity (tất nhiên là bạn phải tạo database tên theo kết nối trong application.properties)
Thuộc tính update table trong database quan trọng: spring.jpa.hibernate.ddl-auto=update

Test API với Postman:
Upload ảnh:

Trong đó multipartFile là tham số server nhận xử lý

Dữ liệu được lưu vào database:

Dữ liệu được chứa ở Cloudinary:

Cập nhật ảnh:

Dữ liệu ở database Cloudinary:



Lấy dữ liệu ảnh:
- Cloudinary

- Database

- Postman

Xoá ảnh:
- DatabaseCloudinary trước khi xoá ảnh:



- Postman:

- Database và Cloudinary sau khi xoá ảnh:


Cảm ơn các bạn đã ghé thăm blog ^^!

Nhận xét

Bài đăng phổ biến từ blog này

Java EE Web Application (JSP/Servlet, EJB, JPA, SQL Server, Glassfish) Full Tutorial

Build validation using VanillaJS for Form Submit

Java EE Web Application (JavaServer Faces, EJB, JPA, SQL Server, Glassfish) Full Tutorial