Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions buy-sell-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ dependencies {
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

// MiniO
implementation 'io.minio:minio:8.4.2'

implementation 'com.fasterxml.uuid:java-uuid-generator:4.0.1' // 최신 버전 확인 후 추가

}

dependencyManagement {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

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)
@EnableJpaAuditing
public class BuySellServiceApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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 {
// 파일 업로드
FileResponse uploadFile(List<MultipartFile> files, String bucketName) throws Exception;

// 파일 생성하기
FileResponse createFile(List<MultipartFile> files) throws Exception;

// 파일 수정하기
FileResponse updateFile(List<MultipartFile> files, Long id) throws Exception;

// 파일 삭제하기
void deleteFile(Long id) throws Exception;

// 버킷 삭제하기
void deleteBucket(Long id) throws Exception;

// 파일 이름 가져오기
List<String> getFileName(Long id) throws Exception;

// 버킷 이름 생성
String getBucketName() throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
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.service.FileService;
import project.buysellservice.domain.article.dto.response.FileResponse;
import project.buysellservice.domain.article.repository.ArticleRepository;

import java.io.InputStream;
import java.util.*;

@Service
@RequiredArgsConstructor
@Slf4j
public class FileServiceImpl implements FileService {
private final MinioClient minioClient;
private final ArticleRepository articleRepository;

// 버킷 네임 생성
@Override
public String getBucketName() {
TimeBasedGenerator generator = Generators.timeBasedGenerator();
// UUID 버전 1 생성
UUID uuid = generator.generate();

return "post-" + uuid.toString().toLowerCase().replaceAll("-", "");
}

// 파일 업로드 과정 (생성, 수정 중복)
@Override
public FileResponse uploadFile(List<MultipartFile> files, String bucketName) throws Exception {
// 이미지 url 저장
List<String> urls = new ArrayList<>();

for (MultipartFile file : files) {
String fileName = file.getOriginalFilename(); // 파일이름 가져오고
InputStream fileStream = file.getInputStream(); // 파일 데이터를 읽고 가져온다.

PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(fileStream, file.getSize(), -1)
.contentType(file.getContentType())
.build(); // 파일 빌더

minioClient.putObject(putObjectArgs);
// 미니오 서버에 반환하는 응답 객체

String fileUrl = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(fileName)
.build()
);

urls.add(fileUrl);
}

return FileResponse.of(urls, bucketName);
}

// 파일 업로드
@Override
public FileResponse createFile(List<MultipartFile> files) throws Exception {
// 버킷 이름 저장
String bucketName = getBucketName();

if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} // 버킷이 없으면 만들어주는 코드

return uploadFile(files, bucketName);
}

@Override
public FileResponse updateFile(List<MultipartFile> 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).getBucketName();
List<String> fileNames = getFileName(id);

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).getBucketName();

deleteFile(id);

minioClient.removeBucket(
RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
}

// 이미지 url 에서 파일 이름 가져오기
@Override
public List<String> getFileName(Long id) {
List<String> urls = articleRepository.getByIdOrThrow(id).getFiles();

List<String> fileNames = new ArrayList<>();

for (String url : urls) {
String fileNameWithParams = url.substring(url.lastIndexOf("/") + 1);

String fileName = fileNameWithParams.split("\\?")[0];

fileNames.add(fileName);
}

return fileNames;
}


}
Original file line number Diff line number Diff line change
@@ -1,46 +1,68 @@
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;
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 java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/article")
@Slf4j
public class ApiV1ArticleController {
private final ArticleService articleService;

// 게시글 전체 페이징 처리
@GetMapping
public ResponseEntity<List<ArticleResponse>> getArticle(@Valid @RequestParam("page") int page,
@Valid @RequestParam("size") int size) {
return ResponseEntity.ok(articleService.readAllArticles(PageRequest.of(page, size)));
}

// 게시글 가져오기
@GetMapping("/{id}")
public ResponseEntity<ArticleResponse> getArticleById(@PathVariable("id") Long id) {
return ResponseEntity.ok(articleService.readArticle(id));
}

// 게시글 생성
@PostMapping
public ResponseEntity<ArticleResponse> createArticle(@Valid @RequestBody ArticleRequest articleRequest) {
@PostMapping(consumes = "multipart/form-data")
public ResponseEntity<ArticleResponse> createArticle(@RequestPart("articleRequest") @Valid ArticleRequest articleRequest,
@RequestPart("file") List<MultipartFile> files) throws Exception {
return ResponseEntity.ok(articleService.createArticle(
articleRequest.title(), articleRequest.content(), articleRequest.imageUrl(), articleRequest.price()
articleRequest.title(), articleRequest.content(), files, articleRequest.price()
));
}

// 게시글 수정
@PutMapping("/{id}")
@PutMapping(value = "/{id}", consumes = "multipart/form-data")
public ResponseEntity<ArticleResponse> updateArticle(@Valid @PathVariable("id") Long id,
@RequestBody ArticleRequest articleRequest) {
@RequestPart("articleRequest") ArticleRequest articleRequest,
@RequestPart("file") List<MultipartFile> files) throws Exception {
return ResponseEntity.ok(articleService.updateArticle(
id, articleRequest.title(), articleRequest.content(), articleRequest.imageUrl(), articleRequest.price()
id, articleRequest.title(), articleRequest.content(), files, articleRequest.price()
));
}

// 게시글 삭제
@DeleteMapping("/{id}")
public void deleteArticle(@PathVariable("id") Long id) {
public void deleteArticle(@PathVariable("id") Long id) throws Exception {
articleService.deleteArticle(id);
}

// 해당 물품 판매 처리 -> 게시글 솔드 상태
@PatchMapping("/{id}")
public ResponseEntity<ArticleResponse> soldArticle(@Valid @PathVariable("id") Long id){
return ResponseEntity.ok(articleService.soldArticle(id));
}

}
Original file line number Diff line number Diff line change
@@ -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
) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
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;

public record ArticleResponse(
String title,
String content,
String imageUrl,
Long price
Long price,
List<String> files,
State state

) {
public static ArticleResponse of(Article article) {
return new ArticleResponse(
article.getTitle(),
article.getContent(),
article.getImageUrl(),
article.getPrice()
article.getPrice(),
article.getFiles(),
article.getState()

);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package project.buysellservice.domain.article.dto.response;

import project.buysellservice.domain.article.entity.Article;

import java.util.List;

public record FileResponse(
List<String> files,
String bucketName
) {
public static FileResponse of(List<String> urls, String bucketName) {
return new FileResponse(
urls,
bucketName
);
}

}
Loading