From 39332386516a42bd89445258da8b5322cbdee46c Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Mon, 10 Feb 2025 10:43:39 +0900 Subject: [PATCH 01/11] =?UTF-8?q?Feat=20:=20buy-sell=20image=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20-=20=ED=98=84=EC=9E=AC=20415=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EB=B0=9C=EC=83=9D=20-=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B8=A1=20file,=20article=20=EB=91=90=EA=B0=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=82=98=EB=88=84=EC=96=B4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- buy-sell-service/build.gradle | 3 + .../BuySellServiceApplication.java | 5 ++ .../domain/File/service/FileService.java | 20 ++++++ .../domain/File/service/FileServiceImpl.java | 71 +++++++++++++++++++ .../controller/ApiV1ArticleController.java | 15 ++-- .../article/dto/request/ArticleRequest.java | 3 +- .../domain/article/entity/Article.java | 6 +- .../domain/article/entity/State.java | 4 ++ .../article/exception/FileException.java | 10 +++ .../article/service/ArticleService.java | 4 +- .../service/impl/ArticleServiceImpl.java | 4 ++ .../domain/category/entity/Category.java | 16 ----- .../global/config/MinioConfig.java | 28 ++++++++ .../global/exception/ErrorCode.java | 21 ++++++ docker-compose.yml | 21 +++++- 15 files changed, 205 insertions(+), 26 deletions(-) create mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/State.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/article/exception/FileException.java delete mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/category/entity/Category.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/global/config/MinioConfig.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/global/exception/ErrorCode.java diff --git a/buy-sell-service/build.gradle b/buy-sell-service/build.gradle index 91bebd1..35cd6e2 100644 --- a/buy-sell-service/build.gradle +++ b/buy-sell-service/build.gradle @@ -50,6 +50,9 @@ dependencies { annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // MiniO + implementation 'io.minio:minio:8.4.2' + } dependencyManagement { diff --git a/buy-sell-service/src/main/java/project/buysellservice/BuySellServiceApplication.java b/buy-sell-service/src/main/java/project/buysellservice/BuySellServiceApplication.java index de0dabd..122bf93 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/BuySellServiceApplication.java +++ b/buy-sell-service/src/main/java/project/buysellservice/BuySellServiceApplication.java @@ -2,10 +2,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import project.buysellservice.global.config.MinioConfig; @EnableDiscoveryClient @SpringBootApplication +@EnableConfigurationProperties(MinioConfig.class) public class BuySellServiceApplication { public static void main(String[] args) { diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java new file mode 100644 index 0000000..0523054 --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java @@ -0,0 +1,20 @@ +package project.buysellservice.domain.File.service; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; + +public interface FileService { + // 파일 업로드 + String uploadFile(MultipartFile file) throws Exception; + + // 파일 다운로드 + InputStream downloadFile(String fileName) throws Exception; + + // 파일 가져오기 + InputStream getFile(String fileName) throws Exception; + + // 파일 삭제하기 + void deleteFile(String fileName); + +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java new file mode 100644 index 0000000..2cb38fa --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java @@ -0,0 +1,71 @@ +package project.buysellservice.domain.File.service; + +import io.minio.*; +import io.minio.http.Method; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; + +@Service +@RequiredArgsConstructor +public class FileServiceImpl implements FileService { + + private final MinioClient minioClient; + + private String bucketName; + + // 파일 업로드 + @Override + public String uploadFile(MultipartFile file) throws Exception { + String fileName = file.getOriginalFilename(); // 파일이름 가져오고 + InputStream fileStream = file.getInputStream(); // 파일 데이터를 읽고 가져온다. + + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .bucket(fileName) + .object(fileName) + .stream(fileStream, file.getSize(), -1) + .contentType(file.getContentType()) + .build(); // 파일 빌더 + + minioClient.putObject(putObjectArgs); + // 미니오 서버에 반환하는 응답 객체 + + String imageUrl = minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(fileName) + .build() + ); + + return imageUrl; + } + + // 이미지 다운로드 + @Override + public InputStream downloadFile(String fileName) throws Exception { + return minioClient.getObject( + GetObjectArgs.builder() + .bucket(bucketName) + .object(fileName) + .build()); + } + + // 이미지 파일 가져오기 + @Override + public InputStream getFile(String fileName) throws Exception { + return minioClient.getObject( + GetObjectArgs.builder() + .bucket(bucketName) + .object(fileName) + .build()); + } + + @Override + public void deleteFile(String fileName) { + + } +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java index a2dd4b5..f6ea27f 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java @@ -4,15 +4,18 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import project.buysellservice.domain.article.dto.request.ArticleRequest; import project.buysellservice.domain.article.dto.response.ArticleResponse; import project.buysellservice.domain.article.service.ArticleService; +import project.buysellservice.domain.File.service.FileService; @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/article") public class ApiV1ArticleController { private final ArticleService articleService; + private final FileService fileService; // 게시글 가져오기 @GetMapping("/{id}") @@ -22,18 +25,22 @@ public ResponseEntity getArticleById(@PathVariable("id") Long i // 게시글 생성 @PostMapping - public ResponseEntity createArticle(@Valid @RequestBody ArticleRequest articleRequest) { + public ResponseEntity createArticle(@RequestPart("articleRequest") @Valid ArticleRequest articleRequest, + @RequestPart("file") MultipartFile file) throws Exception { + String imageUrl = fileService.uploadFile(file); return ResponseEntity.ok(articleService.createArticle( - articleRequest.title(), articleRequest.content(), articleRequest.imageUrl(), articleRequest.price() + articleRequest.title(), articleRequest.content(), imageUrl, articleRequest.price() )); } // 게시글 수정 @PutMapping("/{id}") public ResponseEntity updateArticle(@Valid @PathVariable("id") Long id, - @RequestBody ArticleRequest articleRequest) { + @RequestBody ArticleRequest articleRequest, + @RequestParam MultipartFile file) throws Exception { + String imageUrl = fileService.uploadFile(file); return ResponseEntity.ok(articleService.updateArticle( - id, articleRequest.title(), articleRequest.content(), articleRequest.imageUrl(), articleRequest.price() + id, articleRequest.title(), articleRequest.content(), imageUrl, articleRequest.price() )); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/request/ArticleRequest.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/request/ArticleRequest.java index 5776b36..840a8e8 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/request/ArticleRequest.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/request/ArticleRequest.java @@ -1,9 +1,10 @@ package project.buysellservice.domain.article.dto.request; +import org.springframework.web.multipart.MultipartFile; + public record ArticleRequest( String title, String content, - String imageUrl, Long price ) { diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java index 0f11f3c..6382934 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java @@ -22,9 +22,11 @@ public class Article { private String content; // 글 내용 - private String imageUrl; // 사진 (임시) 리스트로 받아오기 + private Long price; // 가격 - private Long price; // 상품가격 + private String imageUrl; // 이미지 url + + private String state; // 무슨 상태? public static Article createFrom(String title, String content, String imageUrl, Long price) { return Article.builder() diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/State.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/State.java new file mode 100644 index 0000000..8be46ea --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/State.java @@ -0,0 +1,4 @@ +package project.buysellservice.domain.article.entity; + +public enum State { +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/exception/FileException.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/exception/FileException.java new file mode 100644 index 0000000..fb055ac --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/exception/FileException.java @@ -0,0 +1,10 @@ +package project.buysellservice.domain.article.exception; + + +import lombok.RequiredArgsConstructor; +import project.buysellservice.global.exception.ErrorCode; + +@RequiredArgsConstructor +public class FileException extends RuntimeException { + private final ErrorCode errorCode; +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java index 99125e0..09ad6b4 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java @@ -1,12 +1,12 @@ package project.buysellservice.domain.article.service; -import project.buysellservice.domain.article.dto.request.ArticleRequest; +import org.springframework.web.multipart.MultipartFile; import project.buysellservice.domain.article.dto.response.ArticleResponse; public interface ArticleService { // 게시글 생성 - ArticleResponse createArticle(String name, String content, String imageUrl, Long Price); + ArticleResponse createArticle(String name, String content, String imageUrl, Long Price) throws Exception; // 게시글 읽기 ArticleResponse readArticle(Long id); diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java index 695e188..4e683ff 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java @@ -12,6 +12,7 @@ public class ArticleServiceImpl implements ArticleService { private final ArticleRepository articleRepository; + // 생성 @Override public ArticleResponse createArticle(String name, String content, String imageUrl, Long price) { Article article = Article.createFrom(name, content, imageUrl, price); @@ -28,6 +29,8 @@ public ArticleResponse readArticle(Long id) { @Override public ArticleResponse updateArticle(Long id, String name, String content, String imageUrl, Long price) { Article article = articleRepository.getByIdOrThrow(id); + + Article newArticle = Article.updateFrom(article.getId(), name, content, imageUrl, price); articleRepository.save(newArticle); return ArticleResponse.of(newArticle); @@ -37,4 +40,5 @@ public ArticleResponse updateArticle(Long id, String name, String content, Strin public void deleteArticle(Long id) { articleRepository.deleteById(id); } + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/category/entity/Category.java b/buy-sell-service/src/main/java/project/buysellservice/domain/category/entity/Category.java deleted file mode 100644 index 0cdae30..0000000 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/category/entity/Category.java +++ /dev/null @@ -1,16 +0,0 @@ -package project.buysellservice.domain.category.entity; - -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import lombok.Getter; - -@Entity -@Getter -public class Category { - @Id - @GeneratedValue - private Long id; - - private String name; -} diff --git a/buy-sell-service/src/main/java/project/buysellservice/global/config/MinioConfig.java b/buy-sell-service/src/main/java/project/buysellservice/global/config/MinioConfig.java new file mode 100644 index 0000000..8fbeb52 --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/global/config/MinioConfig.java @@ -0,0 +1,28 @@ +package project.buysellservice.global.config; + +import io.minio.MinioClient; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@ConfigurationProperties +@RequiredArgsConstructor +public class MinioConfig { + + private String minioUrl; + + private String accessKey; + + private String secretKey; + + @Bean + public MinioClient minioClient() { + return MinioClient.builder() + .endpoint("http://localhost:9000") // 실제 Minio 서버의 endpoint를 입력해야 합니다. + .credentials("accessKey", "secretKey") + .build(); + } + +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/global/exception/ErrorCode.java b/buy-sell-service/src/main/java/project/buysellservice/global/exception/ErrorCode.java new file mode 100644 index 0000000..ff435f4 --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/global/exception/ErrorCode.java @@ -0,0 +1,21 @@ +package project.buysellservice.global.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ErrorCode { + // 파일 + NOT_EXIST_BUCKET(00, "버킷이 존재하지 않습니다."), + FAIL_UPLOAD_IMAGE(00, "이미지 업로드에 실패하였습니다."); + + + + + + private final int errorCode; + private final String errorMsg; + + +} diff --git a/docker-compose.yml b/docker-compose.yml index 44e078c..9cf2f95 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -167,10 +167,29 @@ services: networks: - app-network + minio: + image: minio/minio:latest + container_name: minio-service + restart: unless-stopped + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ACCESS_KEY: minio + MINIO_SECRET_KEY: minio123 + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin123 + volumes: + - minio_data:/data + command: server /data --console-address ":9001" + networks: + - app-network + networks: app-network: driver: bridge volumes: Kafka00: - driver: local \ No newline at end of file + driver: local + minio_data: \ No newline at end of file From 898894aeab3cd923d1572a5502c6fd0129ec57cf Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Mon, 10 Feb 2025 16:04:42 +0900 Subject: [PATCH 02/11] =?UTF-8?q?Feat=20:=20buy-sell=20image=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20-=20RequestPart=EB=A1=9C=20Body=EC=99=80?= =?UTF-8?q?=20Param=20=EB=91=98=20=EB=8B=A4=20=EB=B0=9B=EA=B2=8C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20-=20Content-Type=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20Oct?= =?UTF-8?q?etStreamReadMsgConverter=20=EC=99=80=20WebConfig=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/File/service/FileService.java | 2 +- .../domain/File/service/FileServiceImpl.java | 13 ++++--- .../controller/ApiV1ArticleController.java | 3 +- .../domain/article/entity/Article.java | 4 ++- .../global/config/MinioConfig.java | 3 +- .../config/OctetStreamReadMsgConverter.java | 35 +++++++++++++++++++ .../global/config/WebConfig.java | 23 ++++++++++++ .../src/main/resources/application.yml | 10 +++++- 8 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 buy-sell-service/src/main/java/project/buysellservice/global/config/OctetStreamReadMsgConverter.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/global/config/WebConfig.java diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java index 0523054..fcc3938 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java @@ -15,6 +15,6 @@ public interface FileService { InputStream getFile(String fileName) throws Exception; // 파일 삭제하기 - void deleteFile(String fileName); + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java index 2cb38fa..ed397e3 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java @@ -3,18 +3,21 @@ import io.minio.*; import io.minio.http.Method; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import project.buysellservice.global.config.MinioConfig; import java.io.InputStream; @Service @RequiredArgsConstructor +@Slf4j public class FileServiceImpl implements FileService { - private final MinioClient minioClient; + @Value("${minio.server.bucket}") private String bucketName; // 파일 업로드 @@ -23,8 +26,9 @@ public String uploadFile(MultipartFile file) throws Exception { String fileName = file.getOriginalFilename(); // 파일이름 가져오고 InputStream fileStream = file.getInputStream(); // 파일 데이터를 읽고 가져온다. + log.info(fileName); PutObjectArgs putObjectArgs = PutObjectArgs.builder() - .bucket(fileName) + .bucket(bucketName) .object(fileName) .stream(fileStream, file.getSize(), -1) .contentType(file.getContentType()) @@ -63,9 +67,4 @@ public InputStream getFile(String fileName) throws Exception { .object(fileName) .build()); } - - @Override - public void deleteFile(String fileName) { - - } } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java index f6ea27f..c853f11 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java @@ -22,9 +22,8 @@ public class ApiV1ArticleController { public ResponseEntity getArticleById(@PathVariable("id") Long id) { return ResponseEntity.ok(articleService.readArticle(id)); } - // 게시글 생성 - @PostMapping + @PostMapping(consumes = "multipart/form-data") public ResponseEntity createArticle(@RequestPart("articleRequest") @Valid ArticleRequest articleRequest, @RequestPart("file") MultipartFile file) throws Exception { String imageUrl = fileService.uploadFile(file); diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java index 6382934..1360c82 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java @@ -1,5 +1,6 @@ package project.buysellservice.domain.article.entity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -24,9 +25,10 @@ public class Article { private Long price; // 가격 + @Column(length = 2048) private String imageUrl; // 이미지 url - private String state; // 무슨 상태? + private State state; // 상태 (팔린 것과 안 팔린 것) public static Article createFrom(String title, String content, String imageUrl, Long price) { return Article.builder() diff --git a/buy-sell-service/src/main/java/project/buysellservice/global/config/MinioConfig.java b/buy-sell-service/src/main/java/project/buysellservice/global/config/MinioConfig.java index 8fbeb52..8da1f9e 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/global/config/MinioConfig.java +++ b/buy-sell-service/src/main/java/project/buysellservice/global/config/MinioConfig.java @@ -8,7 +8,6 @@ import org.springframework.context.annotation.Configuration; @ConfigurationProperties -@RequiredArgsConstructor public class MinioConfig { private String minioUrl; @@ -21,7 +20,7 @@ public class MinioConfig { public MinioClient minioClient() { return MinioClient.builder() .endpoint("http://localhost:9000") // 실제 Minio 서버의 endpoint를 입력해야 합니다. - .credentials("accessKey", "secretKey") + .credentials("minioadmin", "minioadmin123") .build(); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/global/config/OctetStreamReadMsgConverter.java b/buy-sell-service/src/main/java/project/buysellservice/global/config/OctetStreamReadMsgConverter.java new file mode 100644 index 0000000..02cb0bc --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/global/config/OctetStreamReadMsgConverter.java @@ -0,0 +1,35 @@ +package project.buysellservice.global.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Type; + +@Component +public class OctetStreamReadMsgConverter extends AbstractJackson2HttpMessageConverter { + + @Autowired + public OctetStreamReadMsgConverter(ObjectMapper objectMapper, ObjectMapper objectMapper1) { + super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); + } + + // 기존 application/octet-stream 타입을 쓰기로 다루는 메시지 컨버터가 이미 존재 (ByteArrayHttpMessageConverter) + // 따라서 해당 컨버터는 쓰기 작업에는 이용하면 안됨 + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Type type, Class clazz, MediaType mediaType) { + return false; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; + } +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/global/config/WebConfig.java b/buy-sell-service/src/main/java/project/buysellservice/global/config/WebConfig.java new file mode 100644 index 0000000..032d542 --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/global/config/WebConfig.java @@ -0,0 +1,23 @@ +package project.buysellservice.global.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final OctetStreamReadMsgConverter octetStreamReadMsgConverter; + + @Autowired + public WebConfig(OctetStreamReadMsgConverter octetStreamReadMsgConverter) { + this.octetStreamReadMsgConverter = octetStreamReadMsgConverter; + } + + @Override + public void configureMessageConverters(List> converters) { + converters.add(octetStreamReadMsgConverter); + } +} diff --git a/buy-sell-service/src/main/resources/application.yml b/buy-sell-service/src/main/resources/application.yml index 0ddb8b3..79671f6 100644 --- a/buy-sell-service/src/main/resources/application.yml +++ b/buy-sell-service/src/main/resources/application.yml @@ -2,4 +2,12 @@ spring: application: name: buy-sell-service config: - import: "optional:configserver:${CONFIG_SERVER:http://localhost:8888}" \ No newline at end of file + import: "optional:configserver:${CONFIG_SERVER:http://localhost:8888}" + + # Minio server configuration +minio: + server: + url: http://localhost:9000 + access-key: minioadmin + secret-key: minioadmin123 + bucket: files \ No newline at end of file From 5d1c55ece077cb24f201a3069c2037df9f7878f1 Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Mon, 10 Feb 2025 16:24:03 +0900 Subject: [PATCH 03/11] =?UTF-8?q?fix=20:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20put=20=EC=97=90=EC=84=9C=20patch?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95=20-=20state=20=EB=AF=B8=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/controller/ApiV1ArticleController.java | 9 +++++---- .../buysellservice/domain/article/entity/Article.java | 2 -- .../domain/article/service/impl/ArticleServiceImpl.java | 3 ++- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java index c853f11..c961cc1 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java @@ -5,10 +5,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import project.buysellservice.domain.File.service.FileService; import project.buysellservice.domain.article.dto.request.ArticleRequest; import project.buysellservice.domain.article.dto.response.ArticleResponse; import project.buysellservice.domain.article.service.ArticleService; -import project.buysellservice.domain.File.service.FileService; @RestController @RequiredArgsConstructor @@ -22,6 +22,7 @@ public class ApiV1ArticleController { public ResponseEntity getArticleById(@PathVariable("id") Long id) { return ResponseEntity.ok(articleService.readArticle(id)); } + // 게시글 생성 @PostMapping(consumes = "multipart/form-data") public ResponseEntity createArticle(@RequestPart("articleRequest") @Valid ArticleRequest articleRequest, @@ -33,10 +34,10 @@ public ResponseEntity createArticle(@RequestPart("articleReques } // 게시글 수정 - @PutMapping("/{id}") + @PatchMapping("/{id}") public ResponseEntity updateArticle(@Valid @PathVariable("id") Long id, - @RequestBody ArticleRequest articleRequest, - @RequestParam MultipartFile file) throws Exception { + @RequestPart("articleRequest") ArticleRequest articleRequest, + @RequestPart MultipartFile file) throws Exception { String imageUrl = fileService.uploadFile(file); return ResponseEntity.ok(articleService.updateArticle( id, articleRequest.title(), articleRequest.content(), imageUrl, articleRequest.price() diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java index 1360c82..f4d08aa 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java @@ -28,8 +28,6 @@ public class Article { @Column(length = 2048) private String imageUrl; // 이미지 url - private State state; // 상태 (팔린 것과 안 팔린 것) - public static Article createFrom(String title, String content, String imageUrl, Long price) { return Article.builder() .title(title) diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java index 4e683ff..14dd21c 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java @@ -20,17 +20,18 @@ public ArticleResponse createArticle(String name, String content, String imageUr return ArticleResponse.of(article); } + // 읽기 @Override public ArticleResponse readArticle(Long id) { Article article = articleRepository.getByIdOrThrow(id); return ArticleResponse.of(article); } + // 수정 @Override public ArticleResponse updateArticle(Long id, String name, String content, String imageUrl, Long price) { Article article = articleRepository.getByIdOrThrow(id); - Article newArticle = Article.updateFrom(article.getId(), name, content, imageUrl, price); articleRepository.save(newArticle); return ArticleResponse.of(newArticle); From f97aceef15d10748f037571dd5ae262b7b4ce29f Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Mon, 10 Feb 2025 16:29:40 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat=20:=20=EB=B2=84=ED=82=B7=EC=97=90=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=8B=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20-=20?= =?UTF-8?q?=EB=AF=B8=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/File/service/FileService.java | 2 +- .../domain/File/service/FileServiceImpl.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java index fcc3938..a94e2e4 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java @@ -15,6 +15,6 @@ public interface FileService { InputStream getFile(String fileName) throws Exception; // 파일 삭제하기 - + void deleteFile(String fileName) throws Exception; } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java index ed397e3..f51351a 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java @@ -67,4 +67,15 @@ public InputStream getFile(String fileName) throws Exception { .object(fileName) .build()); } + + // 이미지 파일 삭제하기 + @Override + public void deleteFile(String fileName) throws Exception { + minioClient.removeObject( + RemoveObjectArgs.builder() + .bucket(bucketName) + .object(fileName) + .build() + ); + } } From b8c84a0b250a8399868ed1d8a33b7d6fa24b60af Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Mon, 10 Feb 2025 18:59:34 +0900 Subject: [PATCH 05/11] =?UTF-8?q?refactor=20:=20=EB=8D=94=ED=8B=B0=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/File/service/FileService.java | 17 ++--- .../domain/File/service/FileServiceImpl.java | 66 +++++++++++-------- .../controller/ApiV1ArticleController.java | 11 ++-- .../article/service/ArticleService.java | 5 +- .../service/impl/ArticleServiceImpl.java | 14 +++- 5 files changed, 65 insertions(+), 48 deletions(-) diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java index a94e2e4..e5defcc 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java @@ -2,19 +2,16 @@ import org.springframework.web.multipart.MultipartFile; -import java.io.InputStream; - public interface FileService { // 파일 업로드 - String uploadFile(MultipartFile file) throws Exception; - - // 파일 다운로드 - InputStream downloadFile(String fileName) throws Exception; - - // 파일 가져오기 - InputStream getFile(String fileName) throws Exception; + String uploadFile(MultipartFile file, String bucketName) throws Exception; // 파일 삭제하기 - void deleteFile(String fileName) throws Exception; + void deleteFile(Long id) throws Exception; + + // 버킷 삭제하기 + void deleteBucket(Long id) throws Exception; + // 파일 이름 가져오기 + String getFileName(Long id) throws Exception; } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java index f51351a..0c9722a 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java @@ -3,30 +3,28 @@ import io.minio.*; import io.minio.http.Method; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import project.buysellservice.global.config.MinioConfig; +import project.buysellservice.domain.article.repository.ArticleRepository; import java.io.InputStream; @Service @RequiredArgsConstructor -@Slf4j public class FileServiceImpl implements FileService { private final MinioClient minioClient; - - @Value("${minio.server.bucket}") - private String bucketName; + private final ArticleRepository articleRepository; // 파일 업로드 @Override - public String uploadFile(MultipartFile file) throws Exception { + public String uploadFile(MultipartFile file, String bucketName) throws Exception { String fileName = file.getOriginalFilename(); // 파일이름 가져오고 InputStream fileStream = file.getInputStream(); // 파일 데이터를 읽고 가져온다. - log.info(fileName); + if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + } // 버킷이 없으면 만들어주는 코드 + PutObjectArgs putObjectArgs = PutObjectArgs.builder() .bucket(bucketName) .object(fileName) @@ -37,45 +35,57 @@ public String uploadFile(MultipartFile file) throws Exception { minioClient.putObject(putObjectArgs); // 미니오 서버에 반환하는 응답 객체 - String imageUrl = minioClient.getPresignedObjectUrl( + return minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucketName) .object(fileName) .build() ); - - return imageUrl; } - // 이미지 다운로드 + // 이미지 파일 삭제하기 @Override - public InputStream downloadFile(String fileName) throws Exception { - return minioClient.getObject( - GetObjectArgs.builder() - .bucket(bucketName) - .object(fileName) - .build()); - } + public void deleteFile(Long id) throws Exception { + String bucketName = articleRepository.getByIdOrThrow(id).getTitle(); + String fileName = getFileName(id); - // 이미지 파일 가져오기 - @Override - public InputStream getFile(String fileName) throws Exception { - return minioClient.getObject( - GetObjectArgs.builder() + minioClient.removeObject( + RemoveObjectArgs.builder() .bucket(bucketName) .object(fileName) - .build()); + .build() + ); } - // 이미지 파일 삭제하기 + // 버킷 전체 삭제하기 @Override - public void deleteFile(String fileName) throws Exception { + public void deleteBucket(Long id) throws Exception { + String bucketName = articleRepository.getByIdOrThrow(id).getTitle(); + String fileName = getFileName(id); + minioClient.removeObject( RemoveObjectArgs.builder() .bucket(bucketName) .object(fileName) .build() ); + + minioClient.removeBucket( + RemoveBucketArgs.builder() + .bucket(bucketName) + .build()); } + + // 이미지 url 에서 파일 이름 가져오기 + @Override + public String getFileName(Long id) { + String url = articleRepository.getByIdOrThrow(id).getImageUrl(); + + String fileNameWithParams = url.substring(url.lastIndexOf("/") + 1); + + return fileNameWithParams.split("\\?")[0]; + } + + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java index c961cc1..d6590b0 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java @@ -2,6 +2,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -13,6 +14,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/article") +@Slf4j public class ApiV1ArticleController { private final ArticleService articleService; private final FileService fileService; @@ -27,9 +29,8 @@ public ResponseEntity getArticleById(@PathVariable("id") Long i @PostMapping(consumes = "multipart/form-data") public ResponseEntity createArticle(@RequestPart("articleRequest") @Valid ArticleRequest articleRequest, @RequestPart("file") MultipartFile file) throws Exception { - String imageUrl = fileService.uploadFile(file); return ResponseEntity.ok(articleService.createArticle( - articleRequest.title(), articleRequest.content(), imageUrl, articleRequest.price() + articleRequest.title(), articleRequest.content(), file, articleRequest.price() )); } @@ -38,15 +39,15 @@ public ResponseEntity createArticle(@RequestPart("articleReques public ResponseEntity updateArticle(@Valid @PathVariable("id") Long id, @RequestPart("articleRequest") ArticleRequest articleRequest, @RequestPart MultipartFile file) throws Exception { - String imageUrl = fileService.uploadFile(file); return ResponseEntity.ok(articleService.updateArticle( - id, articleRequest.title(), articleRequest.content(), imageUrl, articleRequest.price() + id, articleRequest.title(), articleRequest.content(), file, articleRequest.price() )); } // 게시글 삭제 @DeleteMapping("/{id}") - public void deleteArticle(@PathVariable("id") Long id) { + public void deleteArticle(@PathVariable("id") Long id) throws Exception { + fileService.deleteBucket(id); articleService.deleteArticle(id); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java index 09ad6b4..240efea 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java @@ -1,18 +1,17 @@ package project.buysellservice.domain.article.service; - import org.springframework.web.multipart.MultipartFile; import project.buysellservice.domain.article.dto.response.ArticleResponse; public interface ArticleService { // 게시글 생성 - ArticleResponse createArticle(String name, String content, String imageUrl, Long Price) throws Exception; + ArticleResponse createArticle(String name, String content, MultipartFile file, Long Price) throws Exception; // 게시글 읽기 ArticleResponse readArticle(Long id); // 게시글 수정 - ArticleResponse updateArticle(Long id, String name, String content, String imageUrl, Long Price); + ArticleResponse updateArticle(Long id, String name, String content, MultipartFile file, Long Price) throws Exception; // 게시글 삭제 void deleteArticle(Long id); diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java index 14dd21c..490446c 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java @@ -2,6 +2,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import project.buysellservice.domain.File.service.FileService; import project.buysellservice.domain.article.dto.response.ArticleResponse; import project.buysellservice.domain.article.entity.Article; import project.buysellservice.domain.article.repository.ArticleRepository; @@ -11,10 +13,13 @@ @RequiredArgsConstructor public class ArticleServiceImpl implements ArticleService { private final ArticleRepository articleRepository; + private final FileService fileService; // 생성 @Override - public ArticleResponse createArticle(String name, String content, String imageUrl, Long price) { + public ArticleResponse createArticle(String name, String content, MultipartFile file, Long price) throws Exception { + String imageUrl = fileService.uploadFile(file, name); + Article article = Article.createFrom(name, content, imageUrl, price); articleRepository.save(article); return ArticleResponse.of(article); @@ -29,7 +34,11 @@ public ArticleResponse readArticle(Long id) { // 수정 @Override - public ArticleResponse updateArticle(Long id, String name, String content, String imageUrl, Long price) { + public ArticleResponse updateArticle(Long id, String name, String content, MultipartFile file, Long price) throws Exception { + fileService.deleteFile(id); + + String imageUrl = fileService.uploadFile(file, name); + Article article = articleRepository.getByIdOrThrow(id); Article newArticle = Article.updateFrom(article.getId(), name, content, imageUrl, price); @@ -37,6 +46,7 @@ public ArticleResponse updateArticle(Long id, String name, String content, Strin return ArticleResponse.of(newArticle); } + // 삭제 @Override public void deleteArticle(Long id) { articleRepository.deleteById(id); From 9ae0f7fb609b109a37104fc712b55327cf14f4ce Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Tue, 11 Feb 2025 17:22:04 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat=20:=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20-?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=EC=8B=9C=EA=B0=84=20desc=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=88=9C=EC=B0=A8=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EA=B2=8C=20=EC=83=9D=EC=84=B1=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BuySellServiceApplication.java | 1 + .../controller/ApiV1ArticleController.java | 11 +++++++ .../domain/article/entity/Article.java | 3 +- .../article/repository/ArticleRepository.java | 2 ++ .../repository/ArticleRepositoryCustom.java | 2 +- .../impl/ArticleRepositoryCustomImpl.java | 17 +++++++++-- .../article/service/ArticleService.java | 6 ++++ .../service/impl/ArticleServiceImpl.java | 12 ++++++++ .../global/Util/BaseEntity.java | 29 +++++++++++++++++++ 9 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 buy-sell-service/src/main/java/project/buysellservice/global/Util/BaseEntity.java diff --git a/buy-sell-service/src/main/java/project/buysellservice/BuySellServiceApplication.java b/buy-sell-service/src/main/java/project/buysellservice/BuySellServiceApplication.java index 122bf93..2641bd3 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/BuySellServiceApplication.java +++ b/buy-sell-service/src/main/java/project/buysellservice/BuySellServiceApplication.java @@ -11,6 +11,7 @@ @EnableDiscoveryClient @SpringBootApplication @EnableConfigurationProperties(MinioConfig.class) +@EnableJpaAuditing public class BuySellServiceApplication { public static void main(String[] args) { diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java index d6590b0..58d89e0 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java @@ -3,6 +3,8 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -11,6 +13,8 @@ import project.buysellservice.domain.article.dto.response.ArticleResponse; import project.buysellservice.domain.article.service.ArticleService; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/article") @@ -19,6 +23,13 @@ public class ApiV1ArticleController { private final ArticleService articleService; private final FileService fileService; + // 게시글 전체 페이징 처리 + @GetMapping + public ResponseEntity> getArticle(@Valid @RequestParam("page") int page, + @Valid @RequestParam("size") int size) { + return ResponseEntity.ok(articleService.readAllArticles(PageRequest.of(page, size))); + } + // 게시글 가져오기 @GetMapping("/{id}") public ResponseEntity getArticleById(@PathVariable("id") Long id) { diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java index f4d08aa..be34c10 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java @@ -8,13 +8,14 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import project.buysellservice.global.Util.BaseEntity; @Entity @Getter @Builder @NoArgsConstructor @AllArgsConstructor -public class Article { +public class Article extends BaseEntity { @Id @GeneratedValue private Long id; // 고유 아이디 diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepository.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepository.java index ef2265d..0499cb5 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepository.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepository.java @@ -1,5 +1,7 @@ package project.buysellservice.domain.article.repository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import project.buysellservice.domain.article.entity.Article; diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepositoryCustom.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepositoryCustom.java index 5edf52a..3877d3f 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepositoryCustom.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepositoryCustom.java @@ -6,5 +6,5 @@ public interface ArticleRepositoryCustom { // 전체 게시글 받아오기 - Page
findAll(Pageable pageable); + Page
findAllByOrderByCreatedAt(Pageable pageable); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/impl/ArticleRepositoryCustomImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/impl/ArticleRepositoryCustomImpl.java index 38f3c1c..49c4202 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/impl/ArticleRepositoryCustomImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/impl/ArticleRepositoryCustomImpl.java @@ -3,16 +3,29 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import project.buysellservice.domain.article.entity.Article; +import project.buysellservice.domain.article.entity.QArticle; import project.buysellservice.domain.article.repository.ArticleRepositoryCustom; +import java.util.List; + @RequiredArgsConstructor public class ArticleRepositoryCustomImpl implements ArticleRepositoryCustom { private final JPAQueryFactory jpaQueryFactory; @Override - public Page
findAll(Pageable pageable) { - return null; + public Page
findAllByOrderByCreatedAt(Pageable pageable) { + QArticle article = QArticle.article; + + List
articles = jpaQueryFactory + .selectFrom(article) + .orderBy(article.createDate.desc()) + .offset(pageable.getOffset()) // 페이지 시작 위치 + .limit(pageable.getPageSize()) // 한 페이지에 표시할 개수 + .fetch(); + + return new PageImpl<>(articles); } } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java index 240efea..58ed10a 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java @@ -1,8 +1,11 @@ package project.buysellservice.domain.article.service; +import org.springframework.data.domain.Pageable; import org.springframework.web.multipart.MultipartFile; import project.buysellservice.domain.article.dto.response.ArticleResponse; +import java.util.List; + public interface ArticleService { // 게시글 생성 ArticleResponse createArticle(String name, String content, MultipartFile file, Long Price) throws Exception; @@ -15,4 +18,7 @@ public interface ArticleService { // 게시글 삭제 void deleteArticle(Long id); + + // 전체 게시글 읽기 + List readAllArticles(Pageable pageable); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java index 490446c..fb9e51f 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java @@ -1,6 +1,8 @@ package project.buysellservice.domain.article.service.impl; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import project.buysellservice.domain.File.service.FileService; @@ -9,6 +11,8 @@ import project.buysellservice.domain.article.repository.ArticleRepository; import project.buysellservice.domain.article.service.ArticleService; +import java.util.List; + @Service @RequiredArgsConstructor public class ArticleServiceImpl implements ArticleService { @@ -52,4 +56,12 @@ public void deleteArticle(Long id) { articleRepository.deleteById(id); } + // 모든 게시글 가져오기 + @Override + public List readAllArticles(Pageable pageable) { + Page
articles = articleRepository.findAllByOrderByCreatedAt(pageable); + List
article = articles.getContent(); + return ArticleResponse.listOf(article); + } + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/global/Util/BaseEntity.java b/buy-sell-service/src/main/java/project/buysellservice/global/Util/BaseEntity.java new file mode 100644 index 0000000..d808585 --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/global/Util/BaseEntity.java @@ -0,0 +1,29 @@ +package project.buysellservice.global.Util; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.*; +import lombok.experimental.SuperBuilder; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +import static lombok.AccessLevel.PROTECTED; + +@Getter +@MappedSuperclass +@NoArgsConstructor(access = PROTECTED) +@AllArgsConstructor(access = PROTECTED) +@EntityListeners(AuditingEntityListener.class) +@ToString +@EqualsAndHashCode +public class BaseEntity { + + @CreatedDate + private LocalDateTime createDate; + + @LastModifiedDate + private LocalDateTime modifyDate; +} From 62aa45a5ba6116508d6cdedce25f068b8df06c65 Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Tue, 11 Feb 2025 22:20:43 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor=20:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=A0=84=EC=B2=B4=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../File/dto/response/FileResponse.java | 20 +++++++ .../domain/File/entity/File.java | 40 +++++++++++++ .../File/repository/FileRepository.java | 9 +++ .../domain/File/service/FileService.java | 5 +- .../service/{ => impl}/FileServiceImpl.java | 59 +++++++++++++------ .../controller/ApiV1ArticleController.java | 4 +- .../article/dto/response/ArticleResponse.java | 12 ++-- .../domain/article/entity/Article.java | 22 +++---- .../article/service/ArticleService.java | 2 +- .../service/impl/ArticleServiceImpl.java | 9 +-- buycision-conf | 2 +- 11 files changed, 142 insertions(+), 42 deletions(-) create mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/File/dto/response/FileResponse.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/File/entity/File.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/File/repository/FileRepository.java rename buy-sell-service/src/main/java/project/buysellservice/domain/File/service/{ => impl}/FileServiceImpl.java (53%) diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/dto/response/FileResponse.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/dto/response/FileResponse.java new file mode 100644 index 0000000..cd93be1 --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/dto/response/FileResponse.java @@ -0,0 +1,20 @@ +package project.buysellservice.domain.File.dto.response; + +import project.buysellservice.domain.File.entity.File; + +import java.util.List; + +public record FileResponse( + Long id, + List imageUrls, + String bucketName + +) { + public static FileResponse of(File file) { + return new FileResponse( + file.getId(), + file.getImageUrls(), + file.getBucketName() + ); + } +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/entity/File.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/entity/File.java new file mode 100644 index 0000000..8b9dd9f --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/entity/File.java @@ -0,0 +1,40 @@ +package project.buysellservice.domain.File.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import project.buysellservice.domain.article.entity.Article; +import project.buysellservice.global.Util.BaseEntity; + +import java.util.List; + +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class File extends BaseEntity { + @Id + @GeneratedValue + private Long id; + + @ElementCollection // List을 JPA에서 사용할 수 있도록 설정 + @CollectionTable(name = "file_images") // 별도의 테이블을 생성 (file_images 테이블) + @Column(name = "image_url") // 테이블의 컬럼명 지정 + private List imageUrls; + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "article_id") + private Article article; + + private String bucketName; + + public static File createFrom(List files, String bucketName) { + return File.builder() + .imageUrls(files) + .bucketName(bucketName) + .build(); + } +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/repository/FileRepository.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/repository/FileRepository.java new file mode 100644 index 0000000..2472d8d --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/repository/FileRepository.java @@ -0,0 +1,9 @@ +package project.buysellservice.domain.File.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import project.buysellservice.domain.File.entity.File; + +@Repository +public interface FileRepository extends JpaRepository { +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java index e5defcc..1a9471e 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java @@ -1,10 +1,13 @@ package project.buysellservice.domain.File.service; import org.springframework.web.multipart.MultipartFile; +import project.buysellservice.domain.File.dto.response.FileResponse; + +import java.util.List; public interface FileService { // 파일 업로드 - String uploadFile(MultipartFile file, String bucketName) throws Exception; + FileResponse uploadFile(List file) throws Exception; // 파일 삭제하기 void deleteFile(Long id) throws Exception; diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java similarity index 53% rename from buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java rename to buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java index 0c9722a..baa9c2d 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java @@ -1,47 +1,68 @@ -package project.buysellservice.domain.File.service; +package project.buysellservice.domain.File.service.impl; import io.minio.*; import io.minio.http.Method; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import project.buysellservice.domain.File.dto.response.FileResponse; +import project.buysellservice.domain.File.entity.File; +import project.buysellservice.domain.File.repository.FileRepository; +import project.buysellservice.domain.File.service.FileService; import project.buysellservice.domain.article.repository.ArticleRepository; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; @Service @RequiredArgsConstructor public class FileServiceImpl implements FileService { private final MinioClient minioClient; private final ArticleRepository articleRepository; + private final FileRepository fileRepository; // 파일 업로드 @Override - public String uploadFile(MultipartFile file, String bucketName) throws Exception { - String fileName = file.getOriginalFilename(); // 파일이름 가져오고 - InputStream fileStream = file.getInputStream(); // 파일 데이터를 읽고 가져온다. + public FileResponse uploadFile(List files) throws Exception { + List uploadedUrls = new ArrayList<>(); + String bucketName = "Post - " + UUID.randomUUID(); // 해당 고유값을 사용하여 게시글과 연관되게 만들고 싶음 if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } // 버킷이 없으면 만들어주는 코드 - PutObjectArgs putObjectArgs = PutObjectArgs.builder() - .bucket(bucketName) - .object(fileName) - .stream(fileStream, file.getSize(), -1) - .contentType(file.getContentType()) - .build(); // 파일 빌더 + for (MultipartFile file : files) { + String fileName = file.getOriginalFilename(); // 파일이름 가져오고 + InputStream fileStream = file.getInputStream(); // 파일 데이터를 읽고 가져온다. - minioClient.putObject(putObjectArgs); - // 미니오 서버에 반환하는 응답 객체 + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .bucket(bucketName) + .object(fileName) + .stream(fileStream, file.getSize(), -1) + .contentType(file.getContentType()) + .build(); // 파일 빌더 - return minioClient.getPresignedObjectUrl( - GetPresignedObjectUrlArgs.builder() - .method(Method.GET) - .bucket(bucketName) - .object(fileName) - .build() - ); + minioClient.putObject(putObjectArgs); + // 미니오 서버에 반환하는 응답 객체 + + String fileUrl = minioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(fileName) + .build() + ); + + uploadedUrls.add(fileUrl); + } + + File file = File.createFrom(uploadedUrls, bucketName); + + fileRepository.save(file); + + return FileResponse.of(file); } // 이미지 파일 삭제하기 diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java index 58d89e0..493a384 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java @@ -39,9 +39,9 @@ public ResponseEntity getArticleById(@PathVariable("id") Long i // 게시글 생성 @PostMapping(consumes = "multipart/form-data") public ResponseEntity createArticle(@RequestPart("articleRequest") @Valid ArticleRequest articleRequest, - @RequestPart("file") MultipartFile file) throws Exception { + @RequestPart("file") List files) throws Exception { return ResponseEntity.ok(articleService.createArticle( - articleRequest.title(), articleRequest.content(), file, articleRequest.price() + articleRequest.title(), articleRequest.content(), files, articleRequest.price() )); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java index 9604ccf..93f3e6c 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java @@ -1,5 +1,7 @@ package project.buysellservice.domain.article.dto.response; +import project.buysellservice.domain.File.dto.response.FileResponse; +import project.buysellservice.domain.File.entity.File; import project.buysellservice.domain.article.entity.Article; import java.util.List; @@ -8,15 +10,17 @@ public record ArticleResponse( String title, String content, - String imageUrl, - Long price + Long price, + FileResponse fileResponse + ) { public static ArticleResponse of(Article article) { return new ArticleResponse( article.getTitle(), article.getContent(), - article.getImageUrl(), - article.getPrice() + article.getPrice(), + FileResponse.of(article.getFile()) + ); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java index be34c10..a604a44 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java @@ -1,15 +1,15 @@ package project.buysellservice.domain.article.entity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import project.buysellservice.domain.File.entity.File; import project.buysellservice.global.Util.BaseEntity; +import java.util.List; + @Entity @Getter @Builder @@ -26,24 +26,26 @@ public class Article extends BaseEntity { private Long price; // 가격 - @Column(length = 2048) - private String imageUrl; // 이미지 url + @ElementCollection // List을 JPA에서 사용할 수 있도록 설정 + @CollectionTable(name = "file_images") // 별도의 테이블을 생성 (file_images 테이블) + @Column(name = "image_url") // 테이블의 컬럼명 지정 + private List imageUrls; - public static Article createFrom(String title, String content, String imageUrl, Long price) { + public static Article createFrom(String title, String content, File file, Long price) { return Article.builder() .title(title) .content(content) - .imageUrl(imageUrl) + .file(file) .price(price) .build(); } - public static Article updateFrom(Long id, String title, String content, String imageUrl, Long price) { + public static Article updateFrom(Long id, String title, String content, File file, Long price) { return Article.builder() .id(id) .title(title) .content(content) - .imageUrl(imageUrl) + .file(file) .price(price) .build(); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java index 58ed10a..487e2b9 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java @@ -8,7 +8,7 @@ public interface ArticleService { // 게시글 생성 - ArticleResponse createArticle(String name, String content, MultipartFile file, Long Price) throws Exception; + ArticleResponse createArticle(String name, String content, List files, Long Price) throws Exception; // 게시글 읽기 ArticleResponse readArticle(Long id); diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java index fb9e51f..9916af7 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java @@ -5,6 +5,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import project.buysellservice.domain.File.dto.response.FileResponse; +import project.buysellservice.domain.File.entity.File; import project.buysellservice.domain.File.service.FileService; import project.buysellservice.domain.article.dto.response.ArticleResponse; import project.buysellservice.domain.article.entity.Article; @@ -21,10 +23,9 @@ public class ArticleServiceImpl implements ArticleService { // 생성 @Override - public ArticleResponse createArticle(String name, String content, MultipartFile file, Long price) throws Exception { - String imageUrl = fileService.uploadFile(file, name); - - Article article = Article.createFrom(name, content, imageUrl, price); + public ArticleResponse createArticle(String name, String content, List files, Long price) throws Exception { + FileResponse file = fileService.uploadFile(files); + Article article = Article.createFrom(name, content, file, price); articleRepository.save(article); return ArticleResponse.of(article); } diff --git a/buycision-conf b/buycision-conf index 77c3da2..f8cbd9b 160000 --- a/buycision-conf +++ b/buycision-conf @@ -1 +1 @@ -Subproject commit 77c3da29a2b9ef6aedb590ad726989c8a8e4135b +Subproject commit f8cbd9b1620fd194c5ac4272c71cddd245ca503f From 2a5cb74ebfb7f08de745331b205478a0776f9a75 Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Wed, 12 Feb 2025 17:27:31 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor=20:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EC=A0=84=EC=B2=B4=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- buy-sell-service/build.gradle | 2 + .../File/dto/response/FileResponse.java | 20 ---- .../domain/File/entity/File.java | 40 ------- .../File/repository/FileRepository.java | 9 -- .../domain/File/service/FileService.java | 15 ++- .../File/service/impl/FileServiceImpl.java | 108 ++++++++++++------ .../controller/ApiV1ArticleController.java | 10 +- .../article/dto/response/ArticleResponse.java | 6 +- .../article/dto/response/FileResponse.java | 9 ++ .../domain/article/entity/Article.java | 23 ++-- .../article/repository/ArticleRepository.java | 7 ++ .../article/service/ArticleService.java | 5 +- .../service/impl/ArticleServiceImpl.java | 58 ++++++---- 13 files changed, 161 insertions(+), 151 deletions(-) delete mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/File/dto/response/FileResponse.java delete mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/File/entity/File.java delete mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/File/repository/FileRepository.java create mode 100644 buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/FileResponse.java diff --git a/buy-sell-service/build.gradle b/buy-sell-service/build.gradle index 35cd6e2..f59f133 100644 --- a/buy-sell-service/build.gradle +++ b/buy-sell-service/build.gradle @@ -53,6 +53,8 @@ dependencies { // MiniO implementation 'io.minio:minio:8.4.2' + implementation 'com.fasterxml.uuid:java-uuid-generator:4.0.1' // 최신 버전 확인 후 추가 + } dependencyManagement { diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/dto/response/FileResponse.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/dto/response/FileResponse.java deleted file mode 100644 index cd93be1..0000000 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/dto/response/FileResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package project.buysellservice.domain.File.dto.response; - -import project.buysellservice.domain.File.entity.File; - -import java.util.List; - -public record FileResponse( - Long id, - List imageUrls, - String bucketName - -) { - public static FileResponse of(File file) { - return new FileResponse( - file.getId(), - file.getImageUrls(), - file.getBucketName() - ); - } -} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/entity/File.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/entity/File.java deleted file mode 100644 index 8b9dd9f..0000000 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/entity/File.java +++ /dev/null @@ -1,40 +0,0 @@ -package project.buysellservice.domain.File.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import project.buysellservice.domain.article.entity.Article; -import project.buysellservice.global.Util.BaseEntity; - -import java.util.List; - -@Entity -@Getter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class File extends BaseEntity { - @Id - @GeneratedValue - private Long id; - - @ElementCollection // List을 JPA에서 사용할 수 있도록 설정 - @CollectionTable(name = "file_images") // 별도의 테이블을 생성 (file_images 테이블) - @Column(name = "image_url") // 테이블의 컬럼명 지정 - private List imageUrls; - - @OneToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "article_id") - private Article article; - - private String bucketName; - - public static File createFrom(List files, String bucketName) { - return File.builder() - .imageUrls(files) - .bucketName(bucketName) - .build(); - } -} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/repository/FileRepository.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/repository/FileRepository.java deleted file mode 100644 index 2472d8d..0000000 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/repository/FileRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package project.buysellservice.domain.File.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -import project.buysellservice.domain.File.entity.File; - -@Repository -public interface FileRepository extends JpaRepository { -} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java index 1a9471e..4ad4573 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java @@ -1,13 +1,19 @@ package project.buysellservice.domain.File.service; import org.springframework.web.multipart.MultipartFile; -import project.buysellservice.domain.File.dto.response.FileResponse; import java.util.List; +import java.util.Map; public interface FileService { // 파일 업로드 - FileResponse uploadFile(List file) throws Exception; + Map uploadFile(List files, String bucketName) throws Exception; + + // 파일 생성하기 + Map createFile(List files) throws Exception; + + // 파일 수정하기 + Map updateFile(List files, Long id) throws Exception; // 파일 삭제하기 void deleteFile(Long id) throws Exception; @@ -16,5 +22,8 @@ public interface FileService { void deleteBucket(Long id) throws Exception; // 파일 이름 가져오기 - String getFileName(Long id) throws Exception; + List getFileName(Long id) throws Exception; + + // 버킷 이름 생성 + String getBucketName() throws Exception; } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java index baa9c2d..60f5766 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java @@ -1,37 +1,45 @@ package project.buysellservice.domain.File.service.impl; +import com.fasterxml.uuid.Generators; +import com.fasterxml.uuid.impl.TimeBasedGenerator; import io.minio.*; import io.minio.http.Method; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import project.buysellservice.domain.File.dto.response.FileResponse; -import project.buysellservice.domain.File.entity.File; -import project.buysellservice.domain.File.repository.FileRepository; import project.buysellservice.domain.File.service.FileService; import project.buysellservice.domain.article.repository.ArticleRepository; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; @Service @RequiredArgsConstructor +@Slf4j public class FileServiceImpl implements FileService { private final MinioClient minioClient; private final ArticleRepository articleRepository; - private final FileRepository fileRepository; - // 파일 업로드 + // 버킷 네임 생성 @Override - public FileResponse uploadFile(List files) throws Exception { - List uploadedUrls = new ArrayList<>(); - String bucketName = "Post - " + UUID.randomUUID(); // 해당 고유값을 사용하여 게시글과 연관되게 만들고 싶음 + public String getBucketName() { + TimeBasedGenerator generator = Generators.timeBasedGenerator(); + // UUID 버전 1 생성 + UUID uuid = generator.generate(); - if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) { - minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); - } // 버킷이 없으면 만들어주는 코드 + return "post-" + uuid.toString().toLowerCase().replaceAll("-", ""); + } + + // 파일 업로드 과정 (생성, 수정 중복) + @Override + public Map uploadFile(List files, String bucketName) throws Exception { + Map result = new HashMap<>(); + + result.put("bucket", bucketName); + + // 이미지 url 저장 + List urls = new ArrayList<>(); for (MultipartFile file : files) { String fileName = file.getOriginalFilename(); // 파일이름 가져오고 @@ -55,42 +63,58 @@ public FileResponse uploadFile(List files) throws Exception { .build() ); - uploadedUrls.add(fileUrl); + urls.add(fileUrl); } - File file = File.createFrom(uploadedUrls, bucketName); + result.put("urls", urls); - fileRepository.save(file); + return result; + } + + // 파일 업로드 + @Override + public Map createFile(List files) throws Exception { + // 버킷 이름 저장 + String bucketName = getBucketName(); + + if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); + } // 버킷이 없으면 만들어주는 코드 - return FileResponse.of(file); + return uploadFile(files, bucketName); } + @Override + public Map updateFile(List files, Long id) throws Exception { + String bucketName = articleRepository.getByIdOrThrow(id).getBucketName(); + + return uploadFile(files, bucketName); + } + + // 이미지 파일 삭제하기 @Override public void deleteFile(Long id) throws Exception { - String bucketName = articleRepository.getByIdOrThrow(id).getTitle(); - String fileName = getFileName(id); + String bucketName = articleRepository.getByIdOrThrow(id).getBucketName(); + List fileNames = getFileName(id); - minioClient.removeObject( - RemoveObjectArgs.builder() - .bucket(bucketName) - .object(fileName) - .build() - ); + for (String file : fileNames) { + minioClient.removeObject( + RemoveObjectArgs.builder() + .bucket(bucketName) + .object(file) + .build() + ); + + } } // 버킷 전체 삭제하기 @Override public void deleteBucket(Long id) throws Exception { - String bucketName = articleRepository.getByIdOrThrow(id).getTitle(); - String fileName = getFileName(id); + String bucketName = articleRepository.getByIdOrThrow(id).getBucketName(); - minioClient.removeObject( - RemoveObjectArgs.builder() - .bucket(bucketName) - .object(fileName) - .build() - ); + deleteFile(id); minioClient.removeBucket( RemoveBucketArgs.builder() @@ -100,12 +124,20 @@ public void deleteBucket(Long id) throws Exception { // 이미지 url 에서 파일 이름 가져오기 @Override - public String getFileName(Long id) { - String url = articleRepository.getByIdOrThrow(id).getImageUrl(); + public List getFileName(Long id) { + List urls = articleRepository.getByIdOrThrow(id).getFiles(); - String fileNameWithParams = url.substring(url.lastIndexOf("/") + 1); + List fileNames = new ArrayList<>(); + + for (String url : urls) { + String fileNameWithParams = url.substring(url.lastIndexOf("/") + 1); + + String fileName = fileNameWithParams.split("\\?")[0]; + + fileNames.add(fileName); + } - return fileNameWithParams.split("\\?")[0]; + return fileNames; } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java index 493a384..784c047 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java @@ -4,11 +4,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import project.buysellservice.domain.File.service.FileService; import project.buysellservice.domain.article.dto.request.ArticleRequest; import project.buysellservice.domain.article.dto.response.ArticleResponse; import project.buysellservice.domain.article.service.ArticleService; @@ -21,7 +19,6 @@ @Slf4j public class ApiV1ArticleController { private final ArticleService articleService; - private final FileService fileService; // 게시글 전체 페이징 처리 @GetMapping @@ -46,19 +43,18 @@ public ResponseEntity createArticle(@RequestPart("articleReques } // 게시글 수정 - @PatchMapping("/{id}") + @PatchMapping(value = "/{id}", consumes = "multipart/form-data") public ResponseEntity updateArticle(@Valid @PathVariable("id") Long id, @RequestPart("articleRequest") ArticleRequest articleRequest, - @RequestPart MultipartFile file) throws Exception { + @RequestPart("file") List files) throws Exception { return ResponseEntity.ok(articleService.updateArticle( - id, articleRequest.title(), articleRequest.content(), file, articleRequest.price() + id, articleRequest.title(), articleRequest.content(), files, articleRequest.price() )); } // 게시글 삭제 @DeleteMapping("/{id}") public void deleteArticle(@PathVariable("id") Long id) throws Exception { - fileService.deleteBucket(id); articleService.deleteArticle(id); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java index 93f3e6c..671fa78 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java @@ -1,7 +1,5 @@ package project.buysellservice.domain.article.dto.response; -import project.buysellservice.domain.File.dto.response.FileResponse; -import project.buysellservice.domain.File.entity.File; import project.buysellservice.domain.article.entity.Article; import java.util.List; @@ -11,7 +9,7 @@ public record ArticleResponse( String title, String content, Long price, - FileResponse fileResponse + List files ) { public static ArticleResponse of(Article article) { @@ -19,7 +17,7 @@ public static ArticleResponse of(Article article) { article.getTitle(), article.getContent(), article.getPrice(), - FileResponse.of(article.getFile()) + article.getFiles() ); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/FileResponse.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/FileResponse.java new file mode 100644 index 0000000..6e22fe4 --- /dev/null +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/FileResponse.java @@ -0,0 +1,9 @@ +package project.buysellservice.domain.article.dto.response; + +import java.util.List; + +public record FileResponse( + String bucketName, + List files +) { +} diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java index a604a44..f1dcd54 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java @@ -5,7 +5,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import project.buysellservice.domain.File.entity.File; import project.buysellservice.global.Util.BaseEntity; import java.util.List; @@ -20,33 +19,43 @@ public class Article extends BaseEntity { @GeneratedValue private Long id; // 고유 아이디 + @Column(name = "title", nullable = false) private String title; // 글 제목 + @Column(name = "content", nullable = false) private String content; // 글 내용 + @Column(name = "price", length = 20) private Long price; // 가격 @ElementCollection // List을 JPA에서 사용할 수 있도록 설정 @CollectionTable(name = "file_images") // 별도의 테이블을 생성 (file_images 테이블) - @Column(name = "image_url") // 테이블의 컬럼명 지정 - private List imageUrls; + @Column(name = "image_url", length = 2048) // 테이블의 컬럼명 지정 + private List files; // 이미지 저장 - public static Article createFrom(String title, String content, File file, Long price) { + @Column(name = "bucketName") + private String bucketName; // UUID + Post + + // 게시글 생성 빌더 + public static Article createFrom(String title, String content, List files, Long price, String bucketName) { return Article.builder() .title(title) .content(content) - .file(file) + .files(files) .price(price) + .bucketName(bucketName) .build(); } - public static Article updateFrom(Long id, String title, String content, File file, Long price) { + // 게시글 수정 빌더 + public static Article updateFrom(Long id, String title, String content, List files, Long price, String bucketName) { return Article.builder() .id(id) .title(title) .content(content) - .file(file) + .files(files) .price(price) + .bucketName(bucketName) .build(); } } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepository.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepository.java index 0499cb5..cb17122 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepository.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/repository/ArticleRepository.java @@ -5,6 +5,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import project.buysellservice.domain.article.entity.Article; +import java.util.UUID; + public interface ArticleRepository extends JpaRepository, ArticleRepositoryCustom { // 해당 게시글 가져오기 default Article getByIdOrThrow(Long id) { @@ -12,4 +14,9 @@ default Article getByIdOrThrow(Long id) { // 예외처리 만들기 } + // 버킷 네임 생성 + default String bucketName() { + return "Post - " + UUID.randomUUID(); + } + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java index 487e2b9..f7d543a 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/ArticleService.java @@ -14,11 +14,12 @@ public interface ArticleService { ArticleResponse readArticle(Long id); // 게시글 수정 - ArticleResponse updateArticle(Long id, String name, String content, MultipartFile file, Long Price) throws Exception; + ArticleResponse updateArticle(Long id, String name, String content, List files, Long price) throws Exception; // 게시글 삭제 - void deleteArticle(Long id); + void deleteArticle(Long id) throws Exception; // 전체 게시글 읽기 List readAllArticles(Pageable pageable); + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java index 9916af7..bb1feab 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java @@ -5,8 +5,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import project.buysellservice.domain.File.dto.response.FileResponse; -import project.buysellservice.domain.File.entity.File; import project.buysellservice.domain.File.service.FileService; import project.buysellservice.domain.article.dto.response.ArticleResponse; import project.buysellservice.domain.article.entity.Article; @@ -14,6 +12,7 @@ import project.buysellservice.domain.article.service.ArticleService; import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -21,48 +20,65 @@ public class ArticleServiceImpl implements ArticleService { private final ArticleRepository articleRepository; private final FileService fileService; - // 생성 + // 모든 게시글 가져오기 @Override - public ArticleResponse createArticle(String name, String content, List files, Long price) throws Exception { - FileResponse file = fileService.uploadFile(files); - Article article = Article.createFrom(name, content, file, price); - articleRepository.save(article); - return ArticleResponse.of(article); + public List readAllArticles(Pageable pageable) { + Page
articles = articleRepository.findAllByOrderByCreatedAt(pageable); + + List
article = articles.getContent(); + + return ArticleResponse.listOf(article); } - // 읽기 + // 단일 게시글 가져오기 @Override public ArticleResponse readArticle(Long id) { Article article = articleRepository.getByIdOrThrow(id); + + return ArticleResponse.of(article); + } + + + // 생성 + @Override + public ArticleResponse createArticle(String name, String content, List files, Long price) throws Exception { + Map fileData = fileService.createFile(files); + + String bucketName = (String) fileData.get("bucket"); + + List images = (List) fileData.get("urls"); + + Article article = Article.createFrom(name, content, images, price, bucketName); + articleRepository.save(article); return ArticleResponse.of(article); } // 수정 @Override - public ArticleResponse updateArticle(Long id, String name, String content, MultipartFile file, Long price) throws Exception { + public ArticleResponse updateArticle(Long id, String name, String content, List files, Long price) throws Exception { fileService.deleteFile(id); - String imageUrl = fileService.uploadFile(file, name); + Map fileData = fileService.updateFile(files, id); + + String bucketName = (String) fileData.get("bucket"); + + List images = (List) fileData.get("urls"); Article article = articleRepository.getByIdOrThrow(id); - Article newArticle = Article.updateFrom(article.getId(), name, content, imageUrl, price); + Article newArticle = Article.updateFrom(article.getId(), name, content, images, price, bucketName); + articleRepository.save(newArticle); + return ArticleResponse.of(newArticle); } // 삭제 @Override - public void deleteArticle(Long id) { - articleRepository.deleteById(id); - } + public void deleteArticle(Long id) throws Exception { + fileService.deleteBucket(id); - // 모든 게시글 가져오기 - @Override - public List readAllArticles(Pageable pageable) { - Page
articles = articleRepository.findAllByOrderByCreatedAt(pageable); - List
article = articles.getContent(); - return ArticleResponse.listOf(article); + articleRepository.deleteById(id); } } From 28deab9e4ac9b0cf981d6414204eaa32586ceea3 Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Wed, 12 Feb 2025 17:38:11 +0900 Subject: [PATCH 09/11] refactor : Map -> FileResponse --- .../domain/File/service/FileService.java | 7 ++++--- .../File/service/impl/FileServiceImpl.java | 15 +++++---------- .../article/dto/response/FileResponse.java | 13 +++++++++++-- .../service/impl/ArticleServiceImpl.java | 17 ++++++----------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java index 4ad4573..ecc1e3a 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/FileService.java @@ -1,19 +1,20 @@ package project.buysellservice.domain.File.service; import org.springframework.web.multipart.MultipartFile; +import project.buysellservice.domain.article.dto.response.FileResponse; import java.util.List; import java.util.Map; public interface FileService { // 파일 업로드 - Map uploadFile(List files, String bucketName) throws Exception; + FileResponse uploadFile(List files, String bucketName) throws Exception; // 파일 생성하기 - Map createFile(List files) throws Exception; + FileResponse createFile(List files) throws Exception; // 파일 수정하기 - Map updateFile(List files, Long id) throws Exception; + FileResponse updateFile(List files, Long id) throws Exception; // 파일 삭제하기 void deleteFile(Long id) throws Exception; diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java index 60f5766..ea55253 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/File/service/impl/FileServiceImpl.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import project.buysellservice.domain.File.service.FileService; +import project.buysellservice.domain.article.dto.response.FileResponse; import project.buysellservice.domain.article.repository.ArticleRepository; import java.io.InputStream; @@ -33,11 +34,7 @@ public String getBucketName() { // 파일 업로드 과정 (생성, 수정 중복) @Override - public Map uploadFile(List files, String bucketName) throws Exception { - Map result = new HashMap<>(); - - result.put("bucket", bucketName); - + public FileResponse uploadFile(List files, String bucketName) throws Exception { // 이미지 url 저장 List urls = new ArrayList<>(); @@ -66,14 +63,12 @@ public Map uploadFile(List files, String bucketNa urls.add(fileUrl); } - result.put("urls", urls); - - return result; + return FileResponse.of(urls, bucketName); } // 파일 업로드 @Override - public Map createFile(List files) throws Exception { + public FileResponse createFile(List files) throws Exception { // 버킷 이름 저장 String bucketName = getBucketName(); @@ -85,7 +80,7 @@ public Map createFile(List files) throws Exceptio } @Override - public Map updateFile(List files, Long id) throws Exception { + public FileResponse updateFile(List files, Long id) throws Exception { String bucketName = articleRepository.getByIdOrThrow(id).getBucketName(); return uploadFile(files, bucketName); diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/FileResponse.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/FileResponse.java index 6e22fe4..885f3ae 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/FileResponse.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/FileResponse.java @@ -1,9 +1,18 @@ package project.buysellservice.domain.article.dto.response; +import project.buysellservice.domain.article.entity.Article; + import java.util.List; public record FileResponse( - String bucketName, - List files + List files, + String bucketName ) { + public static FileResponse of(List urls, String bucketName) { + return new FileResponse( + urls, + bucketName + ); + } + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java index bb1feab..bde15fc 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java @@ -7,6 +7,7 @@ import org.springframework.web.multipart.MultipartFile; import project.buysellservice.domain.File.service.FileService; import project.buysellservice.domain.article.dto.response.ArticleResponse; +import project.buysellservice.domain.article.dto.response.FileResponse; import project.buysellservice.domain.article.entity.Article; import project.buysellservice.domain.article.repository.ArticleRepository; import project.buysellservice.domain.article.service.ArticleService; @@ -42,14 +43,12 @@ public ArticleResponse readArticle(Long id) { // 생성 @Override public ArticleResponse createArticle(String name, String content, List files, Long price) throws Exception { - Map fileData = fileService.createFile(files); + FileResponse fileData = fileService.createFile(files); - String bucketName = (String) fileData.get("bucket"); + Article article = Article.createFrom(name, content, fileData.files(), price, fileData.bucketName()); - List images = (List) fileData.get("urls"); - - Article article = Article.createFrom(name, content, images, price, bucketName); articleRepository.save(article); + return ArticleResponse.of(article); } @@ -58,15 +57,11 @@ public ArticleResponse createArticle(String name, String content, List files, Long price) throws Exception { fileService.deleteFile(id); - Map fileData = fileService.updateFile(files, id); - - String bucketName = (String) fileData.get("bucket"); - - List images = (List) fileData.get("urls"); + FileResponse fileData = fileService.updateFile(files, id); Article article = articleRepository.getByIdOrThrow(id); - Article newArticle = Article.updateFrom(article.getId(), name, content, images, price, bucketName); + Article newArticle = Article.updateFrom(article.getId(), name, content, fileData.files(), price, fileData.bucketName()); articleRepository.save(newArticle); From c7cd6de5e15aca0aeab0177d1ca4416cd94d6afb Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Wed, 12 Feb 2025 17:42:30 +0900 Subject: [PATCH 10/11] fix : CollectionTable --- .../project/buysellservice/domain/article/entity/Article.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java index f1dcd54..8b8dbfa 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java @@ -29,7 +29,7 @@ public class Article extends BaseEntity { private Long price; // 가격 @ElementCollection // List을 JPA에서 사용할 수 있도록 설정 - @CollectionTable(name = "file_images") // 별도의 테이블을 생성 (file_images 테이블) + @CollectionTable(name = "bucketName") // 별도의 테이블을 생성 (file_images 테이블) @Column(name = "image_url", length = 2048) // 테이블의 컬럼명 지정 private List files; // 이미지 저장 From 359256c4a26d93f58740899a269ec6eaedc3a8bf Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Wed, 12 Feb 2025 18:10:53 +0900 Subject: [PATCH 11/11] =?UTF-8?q?feat=20:=20State=20=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ApiV1ArticleController.java | 9 ++++++++- .../article/dto/response/ArticleResponse.java | 7 +++++-- .../domain/article/entity/Article.java | 16 +++++++++++++++- .../domain/article/entity/State.java | 1 + .../domain/article/service/ArticleService.java | 3 +++ .../article/service/impl/ArticleServiceImpl.java | 11 +++++++++++ 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java index 784c047..2b8d301 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/controller/ApiV1ArticleController.java @@ -1,6 +1,7 @@ package project.buysellservice.domain.article.controller; import jakarta.validation.Valid; +import jakarta.ws.rs.core.Response; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; @@ -43,7 +44,7 @@ public ResponseEntity createArticle(@RequestPart("articleReques } // 게시글 수정 - @PatchMapping(value = "/{id}", consumes = "multipart/form-data") + @PutMapping(value = "/{id}", consumes = "multipart/form-data") public ResponseEntity updateArticle(@Valid @PathVariable("id") Long id, @RequestPart("articleRequest") ArticleRequest articleRequest, @RequestPart("file") List files) throws Exception { @@ -58,4 +59,10 @@ public void deleteArticle(@PathVariable("id") Long id) throws Exception { articleService.deleteArticle(id); } + // 해당 물품 판매 처리 -> 게시글 솔드 상태 + @PatchMapping("/{id}") + public ResponseEntity soldArticle(@Valid @PathVariable("id") Long id){ + return ResponseEntity.ok(articleService.soldArticle(id)); + } + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java index 671fa78..67b85d3 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/dto/response/ArticleResponse.java @@ -1,6 +1,7 @@ package project.buysellservice.domain.article.dto.response; import project.buysellservice.domain.article.entity.Article; +import project.buysellservice.domain.article.entity.State; import java.util.List; import java.util.stream.Collectors; @@ -9,7 +10,8 @@ public record ArticleResponse( String title, String content, Long price, - List files + List files, + State state ) { public static ArticleResponse of(Article article) { @@ -17,7 +19,8 @@ public static ArticleResponse of(Article article) { article.getTitle(), article.getContent(), article.getPrice(), - article.getFiles() + article.getFiles(), + article.getState() ); } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java index 8b8dbfa..64fa7b7 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/entity/Article.java @@ -11,7 +11,7 @@ @Entity @Getter -@Builder +@Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor public class Article extends BaseEntity { @@ -36,6 +36,10 @@ public class Article extends BaseEntity { @Column(name = "bucketName") private String bucketName; // UUID + Post + @Column(name = "state") + @Enumerated(EnumType.STRING) + private State state; + // 게시글 생성 빌더 public static Article createFrom(String title, String content, List files, Long price, String bucketName) { return Article.builder() @@ -44,6 +48,7 @@ public static Article createFrom(String title, String content, List file .files(files) .price(price) .bucketName(bucketName) + .state(State.SELL) .build(); } @@ -56,6 +61,15 @@ public static Article updateFrom(Long id, String title, String content, List readAllArticles(Pageable pageable); + // 게시글 팔림 처리 + ArticleResponse soldArticle(Long id); + } diff --git a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java index bde15fc..ff215af 100644 --- a/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java +++ b/buy-sell-service/src/main/java/project/buysellservice/domain/article/service/impl/ArticleServiceImpl.java @@ -31,6 +31,17 @@ public List readAllArticles(Pageable pageable) { return ArticleResponse.listOf(article); } + @Override + public ArticleResponse soldArticle(Long id) { + Article article = articleRepository.getByIdOrThrow(id); + + Article soldArticle = Article.soldFrom(article.getId(), article); + + articleRepository.save(soldArticle); + + return ArticleResponse.of(soldArticle); + } + // 단일 게시글 가져오기 @Override public ArticleResponse readArticle(Long id) {