imgupload, comment 모듈 헥사고날 아키텍쳐로 리아키텍쳐링

This commit is contained in:
jinia91
2022-03-26 22:22:26 +09:00
parent dc233daf71
commit 527edda336
29 changed files with 284 additions and 192 deletions

View File

@@ -5,8 +5,8 @@ import lombok.RequiredArgsConstructor;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase; import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout; import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.category.appliacation.port.response.CategorySimpleDto; import myblog.blog.category.appliacation.port.response.CategorySimpleDto;
import myblog.blog.comment.dto.CommentDtoForLayout; import myblog.blog.comment.application.port.incomming.CommentDtoForLayout;
import myblog.blog.comment.service.CommentService; import myblog.blog.comment.application.CommentService;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;

View File

@@ -1,7 +1,6 @@
package myblog.blog.category.adapter.imcomming; package myblog.blog.category.adapter.imcomming;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import myblog.blog.shared.exception.CustomFormException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
@@ -31,7 +30,7 @@ public class CategoryListValidator implements Validator {
springValidatorAdapter.validate(object,errors); springValidatorAdapter.validate(object,errors);
} }
if (errors.hasErrors()) { if (errors.hasErrors()) {
throw new CustomFormException(Objects.requireNonNull(errors.getFieldError()).getDefaultMessage()); throw new InvalidCategoryRequestException(Objects.requireNonNull(errors.getFieldError()).getDefaultMessage());
} }
} }
} }

View File

@@ -0,0 +1,9 @@
package myblog.blog.category.adapter.imcomming;
/*
- REST 컨트롤러 상태 메세지 전송용 커스텀 에러
*/
public class InvalidCategoryRequestException extends RuntimeException {
public InvalidCategoryRequestException(String message) {
super(message);
}
}

View File

@@ -1,35 +1,31 @@
package myblog.blog.comment.controller; package myblog.blog.comment.adapter.incomming;
import myblog.blog.comment.application.port.incomming.CommentUseCase;
import myblog.blog.comment.application.port.incomming.CommentDto;
import myblog.blog.member.auth.PrincipalDetails;
import myblog.blog.member.doamin.Member;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import myblog.blog.article.application.port.incomming.ArticleQueriesUseCase;
import myblog.blog.article.domain.Article;
import myblog.blog.article.application.ArticleService;
import myblog.blog.comment.dto.CommentDto;
import myblog.blog.comment.dto.CommentForm;
import myblog.blog.comment.service.CommentService;
import myblog.blog.shared.exception.CustomFormException;
import myblog.blog.member.auth.PrincipalDetails;
import myblog.blog.member.doamin.Member;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.List;
import java.util.Objects;
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
public class CommentController { public class CommentController {
private final CommentService commentService; private final CommentUseCase commentUseCase;
/* /*
- 아티클 조회시 아티클에 달린 댓글들 전체 조회 - 아티클 조회시 아티클에 달린 댓글들 전체 조회
*/ */
@GetMapping("/comment/list/{articleId}") @GetMapping("/comment/list/{articleId}")
public List<CommentDto> getCommentList(@PathVariable Long articleId){ public List<CommentDto> getCommentList(@PathVariable Long articleId){
return CommentDto.listCreateFrom(commentService.getCommentList(articleId),0); return commentUseCase.getCommentList(articleId);
} }
/* /*
@@ -41,19 +37,19 @@ public class CommentController {
@Validated @RequestBody CommentForm commentForm, Errors errors, @Validated @RequestBody CommentForm commentForm, Errors errors,
@AuthenticationPrincipal PrincipalDetails principal){ @AuthenticationPrincipal PrincipalDetails principal){
if (errors.hasErrors()) { if (errors.hasErrors()) {
throw new CustomFormException(Objects.requireNonNull(errors.getFieldError()).getDefaultMessage()); throw new InvalidCommentRequestException(Objects.requireNonNull(errors.getFieldError()).getDefaultMessage());
} }
Member member = principal.getMember(); Member member = principal.getMember();
// 부모 댓글인지 자식댓글인지 분기로 저장 // 부모 댓글인지 자식댓글인지 분기로 저장
if(parentId != null){ if(parentId != null){
commentService.saveCComment(commentForm, member, articleId, parentId); commentUseCase.saveCComment(commentForm.getContent(), commentForm.isSecret(), member, articleId, parentId);
} }
else { else {
commentService.savePComment(commentForm, member, articleId); commentUseCase.savePComment(commentForm.getContent(), commentForm.isSecret(), member, articleId);
} }
return CommentDto.listCreateFrom(commentService.getCommentList(articleId),0); return commentUseCase.getCommentList(articleId);
} }
/* /*
@@ -62,7 +58,7 @@ public class CommentController {
@PostMapping("/comment/delete") @PostMapping("/comment/delete")
public List<CommentDto> deleteComment(@RequestParam Long articleId, public List<CommentDto> deleteComment(@RequestParam Long articleId,
@RequestParam Long commentId) { @RequestParam Long commentId) {
commentService.deleteComment(commentId); commentUseCase.deleteComment(commentId);
return CommentDto.listCreateFrom(commentService.getCommentList(articleId),0); return commentUseCase.getCommentList(articleId);
} }
} }

View File

@@ -1,4 +1,4 @@
package myblog.blog.comment.dto; package myblog.blog.comment.adapter.incomming;
import lombok.Data; import lombok.Data;

View File

@@ -0,0 +1,9 @@
package myblog.blog.comment.adapter.incomming;
/*
- REST 컨트롤러 상태 메세지 전송용 커스텀 에러
*/
public class InvalidCommentRequestException extends RuntimeException {
public InvalidCommentRequestException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,48 @@
package myblog.blog.comment.adapter.outgoing.persistence;
import lombok.RequiredArgsConstructor;
import myblog.blog.comment.application.port.outgoing.CommentRepositoryPort;
import myblog.blog.article.domain.Article;
import myblog.blog.comment.domain.Comment;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
@RequiredArgsConstructor
public class CommentRepositoryAdapter implements CommentRepositoryPort {
private final JpaCommentRepository jpaCommentRepository;
private final MybatisCommentRepository mybatisCommentRepository;
@Override
public int countCommentsByArticleAndTier(Article article, int tier) {
return jpaCommentRepository.countCommentsByArticleAndTier(article, tier);
}
@Override
public void save(Comment comment) {
jpaCommentRepository.save(comment);
}
@Override
public List<Comment> findCommentsByArticleId(Long articleId) {
return jpaCommentRepository.findCommentsByArticleId(articleId);
}
@Override
public void deleteComment(Long commentId) {
mybatisCommentRepository.deleteComment(commentId);
}
@Override
public List<Comment> findTop5ByOrderByIdDesc() {
return jpaCommentRepository.findTop5ByOrderByIdDesc();
}
@Override
public Optional<Comment> findById(Long parentId) {
return jpaCommentRepository.findById(parentId);
}
}

View File

@@ -1,4 +1,4 @@
package myblog.blog.comment.repository; package myblog.blog.comment.adapter.outgoing.persistence;
import myblog.blog.article.domain.Article; import myblog.blog.article.domain.Article;
import myblog.blog.comment.domain.Comment; import myblog.blog.comment.domain.Comment;
@@ -8,7 +8,7 @@ import org.springframework.data.repository.query.Param;
import java.util.List; import java.util.List;
public interface CommentRepository extends JpaRepository<Comment, Long> { public interface JpaCommentRepository extends JpaRepository<Comment, Long> {
/* /*
- 특정 아티클에 해당하는 댓글 리스트 가져오기 - 특정 아티클에 해당하는 댓글 리스트 가져오기

View File

@@ -1,10 +1,10 @@
package myblog.blog.comment.repository; package myblog.blog.comment.adapter.outgoing.persistence;
import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@Mapper @Mapper
public interface NaCommentRepository { public interface MybatisCommentRepository {
/* /*
- cascade 삭제처리 - cascade 삭제처리

View File

@@ -1,57 +1,57 @@
package myblog.blog.comment.service; package myblog.blog.comment.application;
import myblog.blog.article.application.port.incomming.ArticleUseCase;
import myblog.blog.comment.application.port.incomming.CommentUseCase;
import myblog.blog.comment.application.port.incomming.CommentDto;
import myblog.blog.comment.application.port.incomming.CommentDtoForLayout;
import myblog.blog.comment.application.port.outgoing.CommentRepositoryPort;
import myblog.blog.comment.domain.Comment;
import myblog.blog.article.domain.Article;
import myblog.blog.member.doamin.Member;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import myblog.blog.article.application.port.incomming.ArticleUseCase;
import myblog.blog.article.domain.Article;
import myblog.blog.comment.domain.Comment;
import myblog.blog.comment.dto.CommentDtoForLayout;
import myblog.blog.comment.dto.CommentForm;
import myblog.blog.comment.repository.CommentRepository;
import myblog.blog.comment.repository.NaCommentRepository;
import myblog.blog.member.doamin.Member;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@Transactional @Transactional
@RequiredArgsConstructor @RequiredArgsConstructor
public class CommentService { public class CommentService implements CommentUseCase {
private final ArticleUseCase articleUseCase; private final ArticleUseCase articleUseCase;
private final CommentRepositoryPort commentRepositoryPort;
private final CommentRepository commentRepository;
private final NaCommentRepository naCommentRepository;
/* /*
- 아티클에 달린 댓글 전체 가져오기 - 아티클에 달린 댓글 전체 가져오기
*/ */
public List<Comment> getCommentList(Long articleId){ @Override
return commentRepository.findCommentsByArticleId(articleId); public List<CommentDto> getCommentList(Long articleId){
return CommentDto.listCreateFrom(commentRepositoryPort.findCommentsByArticleId(articleId),0);
} }
/* /*
- 부모 댓글 저장 - 부모 댓글 저장
*/ */
@CacheEvict(value = "layoutRecentCommentCaching", allEntries = true) @CacheEvict(value = "layoutRecentCommentCaching", allEntries = true)
public void savePComment(CommentForm commentForm, Member member, Long articleId){ @Override
public void savePComment(String content, boolean secret, Member member, Long articleId){
Article article = articleUseCase.getArticle(articleId); Article article = articleUseCase.getArticle(articleId);
Comment comment = Comment.builder() Comment comment = Comment.builder()
.article(article) .article(article)
.content(removeDuplicatedEnter(commentForm)) .content(removeDuplicatedEnter(content))
.tier(0) .tier(0)
.pOrder(commentRepository.countCommentsByArticleAndTier(article,0)+1) .pOrder(commentRepositoryPort.countCommentsByArticleAndTier(article,0)+1)
.member(member) .member(member)
.secret(commentForm.isSecret()) .secret(secret)
.build(); .build();
commentRepository.save(comment); commentRepositoryPort.save(comment);
} }
@@ -59,22 +59,24 @@ public class CommentService {
- 자식 댓글 저장 - 자식 댓글 저장
*/ */
@CacheEvict(value = "layoutRecentCommentCaching", allEntries = true) @CacheEvict(value = "layoutRecentCommentCaching", allEntries = true)
public void saveCComment(CommentForm commentForm, Member member, Long articleId, Long parentId) { @Override
public void saveCComment(String content, boolean secret, Member member, Long articleId, Long parentId) {
Article article = articleUseCase.getArticle(articleId); Article article = articleUseCase.getArticle(articleId);
Comment pComment = commentRepository.findById(parentId).get(); Comment pComment = commentRepositoryPort.findById(parentId)
.orElseThrow(() -> new IllegalArgumentException("NotfoundParentCommentException"));
Comment comment = Comment.builder() Comment comment = Comment.builder()
.article(article) .article(article)
.content(removeDuplicatedEnter(commentForm)) .content(removeDuplicatedEnter(content))
.tier(1) .tier(1)
.pOrder(pComment.getPOrder()) .pOrder(pComment.getPOrder())
.member(member) .member(member)
.parents(pComment) .parents(pComment)
.secret(commentForm.isSecret()) .secret(secret)
.build(); .build();
commentRepository.save(comment); commentRepositoryPort.save(comment);
} }
@@ -82,8 +84,9 @@ public class CommentService {
- 댓글 삭제 - 댓글 삭제
*/ */
@CacheEvict(value = "layoutRecentCommentCaching", allEntries = true) @CacheEvict(value = "layoutRecentCommentCaching", allEntries = true)
@Override
public void deleteComment(Long commentId){ public void deleteComment(Long commentId){
naCommentRepository.deleteComment(commentId); commentRepositoryPort.deleteComment(commentId);
} }
/* /*
@@ -93,8 +96,9 @@ public class CommentService {
DTO 매핑 로직 서비스단에서 처리 DTO 매핑 로직 서비스단에서 처리
*/ */
@Cacheable(value = "layoutRecentCommentCaching", key = "0") @Cacheable(value = "layoutRecentCommentCaching", key = "0")
@Override
public List<CommentDtoForLayout> recentCommentList(){ public List<CommentDtoForLayout> recentCommentList(){
return commentRepository.findTop5ByOrderByIdDesc() return commentRepositoryPort.findTop5ByOrderByIdDesc()
.stream() .stream()
.map(comment -> .map(comment ->
new CommentDtoForLayout(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret())) new CommentDtoForLayout(comment.getId(), comment.getArticle().getId(), comment.getContent(), comment.isSecret()))
@@ -104,15 +108,15 @@ public class CommentService {
/* /*
- 중복 개행 개행 하나로 압축 알고리즘 - 중복 개행 개행 하나로 압축 알고리즘
*/ */
private String removeDuplicatedEnter(CommentForm commentForm) { private String removeDuplicatedEnter(String content) {
char[] contentBox = new char[commentForm.getContent().length()]; char[] contentBox = new char[content.length()];
int idx = 0; int idx = 0;
String zipWord = "\n\n"; String zipWord = "\n\n";
for(int i = 0; i< commentForm.getContent().length(); i++){ for(int i = 0; i< content.length(); i++){
contentBox[idx] = commentForm.getContent().charAt(i); contentBox[idx] = content.charAt(i);
if(contentBox[idx] == '\n'&&idx >= 1){ if(contentBox[idx] == '\n'&&idx >= 1){

View File

@@ -1,13 +1,11 @@
package myblog.blog.comment.dto; package myblog.blog.comment.application.port.incomming;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import myblog.blog.comment.domain.Comment; import myblog.blog.comment.domain.Comment;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Optional;
/* /*

View File

@@ -1,4 +1,4 @@
package myblog.blog.comment.dto; package myblog.blog.comment.application.port.incomming;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;

View File

@@ -0,0 +1,13 @@
package myblog.blog.comment.application.port.incomming;
import myblog.blog.member.doamin.Member;
import java.util.List;
public interface CommentUseCase {
List<CommentDto> getCommentList(Long articleId);
void savePComment(String content, boolean secret, Member member, Long articleId);
void saveCComment(String content, boolean secret, Member member, Long articleId, Long parentId);
void deleteComment(Long commentId);
List<CommentDtoForLayout> recentCommentList();
}

View File

@@ -0,0 +1,16 @@
package myblog.blog.comment.application.port.outgoing;
import myblog.blog.article.domain.Article;
import myblog.blog.comment.domain.Comment;
import java.util.List;
import java.util.Optional;
public interface CommentRepositoryPort {
int countCommentsByArticleAndTier(Article article, int tier);
void save(Comment comment);
List<Comment> findCommentsByArticleId(Long articleId);
void deleteComment(Long commentId);
List<Comment> findTop5ByOrderByIdDesc();
Optional<Comment> findById(Long parentId);
}

View File

@@ -0,0 +1,20 @@
package myblog.blog.imgupload.adapter.incomming;
import myblog.blog.imgupload.service.port.incomming.ImgUploadUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@RestController
@RequiredArgsConstructor
public class UploadImgController {
private final ImgUploadUseCase imgUploadUseCase;
@PostMapping("/article/uploadImg")
public @ResponseBody
String imgUpload(@ModelAttribute UploadImgForm uploadImgForm) throws IOException {
return imgUploadUseCase.storeImg(uploadImgForm.getImg());
}
}

View File

@@ -1,14 +1,11 @@
package myblog.blog.imgupload.dto; package myblog.blog.imgupload.adapter.incomming;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
/*
- 멀티파트 파일 래핑용 DTO
*/
@Getter @Getter
@Setter @Setter
public class UploadImgDto { public class UploadImgForm {
private MultipartFile img; private MultipartFile img;
} }

View File

@@ -1,8 +1,10 @@
package myblog.blog.imgupload.service; package myblog.blog.imgupload.adapter.outgoing;
import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*; import com.amazonaws.services.s3.model.*;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import myblog.blog.imgupload.domain.ImageFile;
import myblog.blog.imgupload.service.port.outgoing.ImgUploadStrategyPort;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -15,22 +17,22 @@ import java.io.InputStream;
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@Service @Service
public class AwsS3ImgUploadStrategy implements ImgUploadStrategy { public class AwsS3ImgUploadStrategyAdapter implements ImgUploadStrategyPort {
private final AmazonS3 amazonS3; private final AmazonS3 amazonS3;
@Value("${cloud.aws.s3.bucket}") @Value("${cloud.aws.s3.bucket}")
private String bucket; private String bucket;
@Override public String uploadFile(ImageFile imageFile) {
public String uploadFile(MultipartFile file, String storeFileName) { MultipartFile file = imageFile.getMultipartFile();
ObjectMetadata metadata = createObjectMetadata(file); ObjectMetadata metadata = createObjectMetadata(file);
try(InputStream inputStream = file.getInputStream()) { try(InputStream inputStream = file.getInputStream()) {
amazonS3.putObject(new PutObjectRequest(bucket, storeFileName, inputStream, metadata) amazonS3.putObject(new PutObjectRequest(bucket, imageFile.getStoredFileName(), inputStream, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead)); .withCannedAcl(CannedAccessControlList.PublicRead));
} catch (IOException e) { } catch (IOException e) {
throw new IllegalArgumentException("파일 업로드에 실패했습니다."); throw new IllegalArgumentException("파일 업로드에 실패했습니다.");
} }
return amazonS3.getUrl(bucket, storeFileName).toString(); return amazonS3.getUrl(bucket, imageFile.getStoredFileName()).toString();
} }
/** /**

View File

@@ -1,11 +1,9 @@
package myblog.blog.imgupload.service; package myblog.blog.imgupload.adapter.outgoing;
import lombok.RequiredArgsConstructor;
import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHub;
import org.kohsuke.github.GitHubBuilder; import org.kohsuke.github.GitHubBuilder;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
@@ -15,7 +13,8 @@ import java.io.IOException;
*/ */
//@RequiredArgsConstructor //@RequiredArgsConstructor
//@Service //@Service
public class GithubRepoImgUploadStrategy implements ImgUploadStrategy { @Deprecated
public class GithubRepoImgUploadStrategyAdapter {
/* /*
- 설정 파일로 잡아놓은 깃헙 이미지 레포지토리와 토큰 - 설정 파일로 잡아놓은 깃헙 이미지 레포지토리와 토큰
@@ -34,7 +33,6 @@ public class GithubRepoImgUploadStrategy implements ImgUploadStrategy {
1. 깃허브 Repo에 이미지 업로드 1. 깃허브 Repo에 이미지 업로드
2. 업로드된 Url 반환 2. 업로드된 Url 반환
*/ */
@Override
public String uploadFile(MultipartFile multipartFile, String storeFileName) throws IOException { public String uploadFile(MultipartFile multipartFile, String storeFileName) throws IOException {
GitHub gitHub = new GitHubBuilder().withOAuthToken(gitToken).build(); GitHub gitHub = new GitHubBuilder().withOAuthToken(gitToken).build();
GHRepository repository = gitHub.getRepository(gitRepo); GHRepository repository = gitHub.getRepository(gitRepo);

View File

@@ -1,27 +0,0 @@
package myblog.blog.imgupload.controller;
import lombok.RequiredArgsConstructor;
import myblog.blog.imgupload.dto.UploadImgDto;
import myblog.blog.imgupload.service.ImgUploadServiceImpl;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
@RequiredArgsConstructor
public class UploadImgController {
private final ImgUploadServiceImpl imgUploadServiceImpl;
/*
- 썸네일 업로드 요청
*/
@PostMapping("/article/uploadImg")
public @ResponseBody
String imgUpload(@ModelAttribute UploadImgDto uploadImgDto) throws IOException {
return imgUploadServiceImpl.storeImg(uploadImgDto.getImg());
}
}

View File

@@ -0,0 +1,38 @@
package myblog.blog.imgupload.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.web.multipart.MultipartFile;
import java.util.UUID;
@Getter
@AllArgsConstructor
public class ImageFile {
String originalFileName;
String storedFileName;
MultipartFile multipartFile;
static public ImageFile from(MultipartFile multipartFile){
return new ImageFile(multipartFile.getOriginalFilename(),
createStoreFileName(multipartFile.getOriginalFilename()),
multipartFile
);
}
/*
- 이미지 중복 방지용 무작위 파일 이름 생성기
*/
private static String createStoreFileName(String originalFilename) {
String ext = extractExt(originalFilename);
String uuid = UUID.randomUUID().toString();
return uuid + "." + ext;
}
/*
- 파일 이름 추출
*/
private static String extractExt(String originalFilename) {
int pos = originalFilename.lastIndexOf(".");
return originalFilename.substring(pos + 1);
}
}

View File

@@ -1,9 +1,33 @@
package myblog.blog.imgupload.service; package myblog.blog.imgupload.service;
import lombok.RequiredArgsConstructor;
import myblog.blog.imgupload.domain.ImageFile;
import myblog.blog.imgupload.service.port.incomming.ImgUploadUseCase;
import myblog.blog.imgupload.service.port.outgoing.ImgUploadStrategyPort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.io.IOException;
import java.util.UUID;
public interface ImgUploadService { @Service
List<String> storeFile(List<MultipartFile> imgList); @Transactional
} @RequiredArgsConstructor
public class ImgUploadService implements ImgUploadUseCase {
private final ImgUploadStrategyPort imgUploadStrategyPort;
@Override
public String storeImg(MultipartFile multipartFile) {
validateFile(multipartFile);
ImageFile imageFile = ImageFile.from(multipartFile);
return imgUploadStrategyPort.uploadFile(imageFile);
}
private void validateFile(MultipartFile multipartFile) {
if (multipartFile.isEmpty()) {
throw new IllegalArgumentException("이미지가 존재하지 않습니다.");
}
}
}

View File

@@ -1,51 +0,0 @@
package myblog.blog.imgupload.service;
import lombok.RequiredArgsConstructor;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.GitHubBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@Service
@Transactional
@RequiredArgsConstructor
public class ImgUploadServiceImpl {
private final ImgUploadStrategy imgUploadStrategy;
public String storeImg(MultipartFile multipartFile) throws IOException {
validateFile(multipartFile);
String storeFileName = createStoreFileName(multipartFile.getOriginalFilename());
return imgUploadStrategy.uploadFile(multipartFile, storeFileName);
}
private void validateFile(MultipartFile multipartFile) {
if (multipartFile.isEmpty()) {
throw new IllegalArgumentException("이미지가 존재하지 않습니다.");
}
}
/*
- 이미지 중복 방지용 무작위 파일 이름 생성기
*/
private String createStoreFileName(String originalFilename) {
String ext = extractExt(originalFilename);
String uuid = UUID.randomUUID().toString();
return uuid + "." + ext;
}
/*
- 파일 이름 추출
*/
private String extractExt(String originalFilename) {
int pos = originalFilename.lastIndexOf(".");
return originalFilename.substring(pos + 1);
}
}

View File

@@ -1,12 +0,0 @@
package myblog.blog.imgupload.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
* 파일 업로드 전략패턴 추상화 인터페이스
*/
public interface ImgUploadStrategy {
String uploadFile(MultipartFile file, String storeFileName) throws IOException;
}

View File

@@ -0,0 +1,9 @@
package myblog.blog.imgupload.service.port.incomming;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public interface ImgUploadUseCase {
String storeImg(MultipartFile img);
}

View File

@@ -0,0 +1,10 @@
package myblog.blog.imgupload.service.port.outgoing;
import myblog.blog.imgupload.domain.ImageFile;
/**
* 파일 업로드 전략패턴 추상화 인터페이스
*/
public interface ImgUploadStrategyPort {
String uploadFile(ImageFile imageFile);
}

View File

@@ -3,8 +3,8 @@ package myblog.blog.member.controller;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout; import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.category.appliacation.CategoryService; import myblog.blog.category.appliacation.CategoryService;
import myblog.blog.comment.dto.CommentDtoForLayout; import myblog.blog.comment.application.port.incomming.CommentDtoForLayout;
import myblog.blog.comment.service.CommentService; import myblog.blog.comment.application.CommentService;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;

View File

@@ -1,9 +0,0 @@
package myblog.blog.shared.exception;
/*
- REST 컨트롤러 상태 메세지 전송용 커스텀 에러
*/
public class CustomFormException extends RuntimeException {
public CustomFormException(String message) {
super(message);
}
}

View File

@@ -1,5 +1,6 @@
package myblog.blog.shared.exception; package myblog.blog.shared.exception;
import myblog.blog.comment.adapter.incomming.InvalidCommentRequestException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
@@ -10,7 +11,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
*/ */
@RestControllerAdvice @RestControllerAdvice
public class ExceptionRestControllerAdvice { public class ExceptionRestControllerAdvice {
@ExceptionHandler(CustomFormException.class) @ExceptionHandler(InvalidCommentRequestException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleCategoryControllerException(RuntimeException e) { public String handleCategoryControllerException(RuntimeException e) {
return e.getMessage(); return e.getMessage();

View File

@@ -3,8 +3,8 @@ package myblog.blog.shared.queries;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import myblog.blog.category.appliacation.port.incomming.CategoryUseCase; import myblog.blog.category.appliacation.port.incomming.CategoryUseCase;
import myblog.blog.category.appliacation.port.response.CategoryViewForLayout; import myblog.blog.category.appliacation.port.response.CategoryViewForLayout;
import myblog.blog.comment.dto.CommentDtoForLayout; import myblog.blog.comment.application.port.incomming.CommentDtoForLayout;
import myblog.blog.comment.service.CommentService; import myblog.blog.comment.application.CommentService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.ui.Model; import org.springframework.ui.Model;