Rental Application (React & Spring boot Microservice) - 13 : post-service(2)

yellow_note·2021년 8월 26일
0

#1 service

이어서 서비스에 대한 부분을 구현하도록 하겠습니다. 그 전에 다음의 디펜던시를 추가하도록 하겠습니다.

  • pom.xml
<dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
  • PostService
package com.microservices.postservice.service;

import com.microservices.postservice.dto.PostDto;

public interface PostService {
    PostDto write(PostDto postDto) throws Exception;

    PostDto readPostById(Long id);

    Iterable<PostDto> getAllPosts();

    Iterable<PostDto> getAllPostsByStatus(String status);

    Iterable<PostDto> getPostsByUserId(String userId);

    PostDto deletePost(Long postId);
}
  • PostServiceImpl
package com.microservices.postservice.service;

import com.microservices.postservice.dto.PostDto;
import com.microservices.postservice.entity.CommentEntity;
import com.microservices.postservice.entity.ImageEntity;
import com.microservices.postservice.entity.PostEntity;
import com.microservices.postservice.repository.ImageRepository;
import com.microservices.postservice.repository.PostRepository;
import com.microservices.postservice.util.DateUtil;
import com.microservices.postservice.util.FileUploader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
@Slf4j
public class PostServiceImpl implements PostService {
    private PostRepository postRepository;
    private ImageRepository imageRepository;

    @Autowired
    public PostServiceImpl(
        PostRepository postRepository,
        ImageRepository imageRepository
    ) {
        this.postRepository = postRepository;
        this.imageRepository = imageRepository;
    }


    @Transactional
    @Override
    public PostDto write(PostDto postDto) throws Exception {
        log.info("Post Service's Service Layer :: Call write Method!");

        PostEntity postEntity = PostEntity.builder()
                                          .postType(postDto.getPostType())
                                          .rentalPrice(postDto.getRentalPrice())
                                          .title(postDto.getTitle())
                                          .content(postDto.getContent())
                                          .startDate(postDto.getStartDate())
                                          .endDate(postDto.getEndDate())
                                          .writer(postDto.getWriter())
                                          .userId(postDto.getUserId())
                                          .createdAt(DateUtil.dateNow())
                                          .status("CREATE_POST")
                                          .build();
//        List<ImageEntity> images = FileUploader.parseFileInfo(
//            postDto.getMultipartFiles(),
//            postEntity
//        );
//
//        if(!images.isEmpty()) {
//            for(ImageEntity image: images) {
//                postEntity.addImage(imageRepository.save(image));
//            }
//        }

        postRepository.save(postEntity);

        return PostDto.builder()
                      .id(postEntity.getId())
                      .userId(postEntity.getUserId())
                      .postType(postEntity.getPostType())
                      .rentalPrice(postEntity.getRentalPrice())
                      .title(postEntity.getTitle())
                      .content(postEntity.getContent())
                      .startDate(postEntity.getStartDate())
                      .endDate(postEntity.getEndDate())
                      .createdAt(postEntity.getCreatedAt())
                      .writer(postEntity.getWriter())
        //                      .images(images)
                      .build();
    }

    @Transactional
    @Override
    public PostDto readPostById(Long id) {
        log.info("Post Service's Service Layer :: Call readPostById Method!");

        PostEntity postEntity = postRepository.findPostById(id);
//        List<ImageEntity> images = new ArrayList<>();
        List<CommentEntity> comments = new ArrayList<>();

//        postEntity.getImages().forEach(i -> {
//            images.add(i);
//        });

        postEntity.getComments().forEach(i -> {
            comments.add(CommentEntity.builder()
                                      .id(i.getId())
                                      .comment(i.getComment())
                                      .writer(i.getWriter())
                                      .createdAt(i.getCreatedAt())
                                      .build());
        });

        return PostDto.builder()
                      .userId(postEntity.getUserId())
                      .postType(postEntity.getPostType())
                      .rentalPrice(postEntity.getRentalPrice())
                      .title(postEntity.getTitle())
                      .content(postEntity.getContent())
                      .startDate(postEntity.getStartDate())
                      .endDate(postEntity.getEndDate())
                      .createdAt(postEntity.getCreatedAt())
                      .writer(postEntity.getWriter())
//                      .images(images)
                      .comments(comments)
                      .status(postEntity.getStatus())
                      .build();
    }

    @Transactional
    @Override
    public List<PostDto> getAllPosts() {
        log.info("Post Service's Service Layer :: Call getAllPosts Method!");

        Iterable<PostEntity> posts = postRepository.findAll();
        List<PostDto> postList = new ArrayList<>();

        posts.forEach(v -> {
            List<CommentEntity> comments = new ArrayList<>();

            v.getComments().forEach(i -> {
                comments.add(CommentEntity.builder()
                                          .id(i.getId())
                                          .comment(i.getComment())
                                          .writer(i.getWriter())
                                          .createdAt(i.getCreatedAt())
                                          .build());
            });

            postList.add(PostDto.builder()
                                .userId(v.getUserId())
                                .postType(v.getPostType())
                                .rentalPrice(v.getRentalPrice())
                                .title(v.getTitle())
                                .content(v.getContent())
                                .startDate(v.getStartDate())
                                .endDate(v.getEndDate())
                                .createdAt(v.getCreatedAt())
                                .writer(v.getWriter())
//                                .images(v.getImages())
                                .comments(comments)
                                .status(v.getStatus())
                                .build());
        });

        return postList;
    }

    @Transactional
    @Override
    public Iterable<PostDto> getAllPostsByStatus(String status) {
        log.info("Post Service's Service Layer :: Call getAllPostsByStatus Method!");

        Iterable<PostEntity> posts = postRepository.findAllByStatus(status);
        List<PostDto> postList = new ArrayList<>();

        posts.forEach(v -> {
            List<CommentEntity> comments = new ArrayList<>();

            v.getComments().forEach(i -> {
                comments.add(CommentEntity.builder()
                                          .id(i.getId())
                                          .comment(i.getComment())
                                          .writer(i.getWriter())
                                          .createdAt(i.getCreatedAt())
                                          .build());
            });

            postList.add(PostDto.builder()
                    .userId(v.getUserId())
                    .postType(v.getPostType())
                    .rentalPrice(v.getRentalPrice())
                    .title(v.getTitle())
                    .content(v.getContent())
                    .startDate(v.getStartDate())
                    .endDate(v.getEndDate())
                    .createdAt(v.getCreatedAt())
                    .writer(v.getWriter())
    //                .images(v.getImages())
                    .comments(comments)
                    .status(v.getStatus())
                    .build());
        });

        return postList;
    }

    @Transactional
    @Override
    public List<PostDto> getPostsByUserId(String userId) {
        log.info("Post Service's Service Layer :: Call getPostsByUserId Method!");

        Iterable<PostEntity> posts = postRepository.findAllByUserId(userId);
        List<PostDto> postList = new ArrayList<>();

        posts.forEach(v -> {
            List<CommentEntity> comments = new ArrayList<>();

            v.getComments().forEach(i -> {
                comments.add(CommentEntity.builder()
                                          .id(i.getId())
                                          .comment(i.getComment())
                                          .writer(i.getWriter())
                                          .createdAt(i.getCreatedAt())
                                          .build());
            });

            postList.add(PostDto.builder()
                                .userId(v.getUserId())
                                .postType(v.getPostType())
                                .rentalPrice(v.getRentalPrice())
                                .title(v.getTitle())
                                .content(v.getContent())
                                .startDate(v.getStartDate())
                                .endDate(v.getEndDate())
                                .createdAt(v.getCreatedAt())
                                .writer(v.getWriter())
//                                .images(v.getImages())
                                .comments(comments)
                                .status(v.getStatus())
                                .build());
        });

        return postList;
    }

    @Transactional
    @Override
    public PostDto deletePost(Long id) {
        log.info("Post Service's Service Layer :: Call deletePost Method!");

        PostEntity postEntity = postRepository.findPostById(id);

        postEntity.setStatus("DELETE_POST");

        postRepository.save(postEntity);

        return PostDto.builder()
                      .id(postEntity.getId())
                      .status(postEntity.getStatus())
                      .build();
    }
}

본격적인 비즈니스 로직을 담당하는 Impl클래스입니다. 이 클래스의 주요 메서드들을 살펴보겠습니다.

1) write
controller에서 넘어온 dto객체를 받아와 entity로 변경하는 작업을 합니다.

이 과정에서 postEntity와 imageEntity 리스트가 생기는데 postEntity에서는 status에 CREATE_POST값을 넣어 추후에 대여가 되지 않은 포스트들만 불러올 때 사용할 상태 값입니다. 그리고 images는 parseFileInfo라는 인스턴스를 통해 만들어지는 리스트입니다. FileUploader클래스는 아래에서 자세하게 다루도록 하겠습니다.

그리고 이 과정을 마치고 최종적으로 postEntity를 데이터베이스에 저장합니다.

2) readPostById
id를 기반으로 게시글을 읽어오는 메서드입니다.

postId로 게시글을 불러오고 여기에 저장되어 있는 이미지, 댓글들을 ImageEntity, CommentEntity 리스트에 담아주고 최종적으로 builder패턴을 이용한 PostDto에 모든 값을 담아 반환해주는 메서드입니다.

postEntity를 만들고 이와 연결된 테이블들인 image, comment에 저장된 관련 데이터들은 앞서 entity에 정의되었던 일대다매핑을 이용해 데이터들을 불러옵니다. 즉, PostEntity와 ImageEntity, CommentEntity들은 서로 매핑을 통해 연결되어 있으니 일인 PostEntity를 호출하면 mappedBy = "post" 관련 entity들을 불러올 수 있다는 의미입니다.

3) getAllPostsByStatus

실제로 게시판을 들어갔을 때 유저들은 이미 대여가 완료된 게시글을 보고싶지 않을수도 있습니다. 즉 이런 상태의 게시글들을 제외하고 status의 값이 CREATE_POST만 볼 수 있다면 대여가 가능한 게시글들만 볼 수 있겠죠. 이런 취지에서 만든 메서드입니다.

PostRepsitory에서는 Iterable형태로 post를 불러옵니다. 그래서 List형태로 변환해야 될 필요가 있으므로 forEach구문을 이용하여 postList를 만들어 반환하도록 합니다.

4) deletePost
실제로 delete를 하는 것은 아니고 post의 상태값을 변경해주기 위한 메서드입니다. 실수로 유저가 게시글을 지울수도 혹은 예전에 지운 게시글을 다시 등록시키고 싶을 수 있으니까요. 이런 글들은 status의 값을 DELETE_POST로 변경시키도록 하겠습니다.

  • PostRepository
package com.microservices.postservice.repository;

import com.microservices.postservice.entity.PostEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<PostEntity, Long> {
    Iterable<PostEntity> findAllByStatus(String status);

    Iterable<PostEntity> findAllByUserId(String userId);

    PostEntity findPostById(Long postId);
}

여기까지 post를 위한 controller ~ repository까지 구현 부분이었습니다. 하지만 post는 comment, image 총 2개의 테이블과 연관이 되어있기 때문에 comment, table의 controller ~ repository까지 마저 구현을 해보겠습니다.

image, comment는 최대한 간소화하여 구현을 하겠습니다.

  • ImageService
package com.microservices.postservice.service;

import com.microservices.postservice.entity.ImageEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Slf4j
@Service
public class ImageService {
    @Autowired
    private ImageRepository imageRepository;

    @Transactional
    public void saveImage(ImageEntity imageEntity) {
        imageRepository.save(imageEntity);
    }

    @Transactional
    public Iterable<ImageEntity> getImages(String postId) {
        return imageRepository.findByPostId(postId);
    }
}

그리고 이미지를 저장하기 위한 FileUpload 클래스를 util 패키지를 만들어 구현하도록 하겠습니다.

  • FileUploader
package com.microservices.postservice.util;

import com.microservices.postservice.entity.ImageEntity;
import com.microservices.postservice.entity.PostEntity;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.io.FilenameUtils;

import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Component
public class FileUploader {
    private static final String basePath = "/home/biuea/Desktop/MyRentalPlatform/post-service/src/main/resources";

    public static List<ImageEntity> parseFileInfo(
        List<MultipartFile> images,
        PostEntity post
    ) throws Exception {
        if(CollectionUtils.isEmpty(images)) {
            return Collections.emptyList();
        }

        String savePath = Paths.get(basePath, "upload").toString();

        if(!new File(savePath).exists()) {
            try {
                new File(savePath).mkdir();
            } catch(Exception e) {
                e.getStackTrace();
            }
        }

        List<ImageEntity> imageList = new ArrayList<>();

        for(MultipartFile image : images) {
            String orgFilename = image.getOriginalFilename();

            if(orgFilename == null || "".equals(orgFilename)) {
                continue;
            }

            String fileName = MD5Generator(FilenameUtils.getBaseName(orgFilename)).toString() + "." + FilenameUtils.getExtension(orgFilename);
            String filePath = Paths.get(savePath, fileName).toString();

            ImageEntity temp_image = ImageEntity.builder()
                                                .post(post)
                                                .orgFilename(orgFilename)
                                                .fileName(fileName)
                                                .filePath(filePath)
                                                .fileSize(image.getSize())
                                                .build();

            imageList.add(temp_image);

            try {
                File file = new File(filePath);

                image.transferTo(file);

                file.setWritable(true);
                file.setReadable(true);
            } catch(IOException e) {
                throw new FileUploadException("[" + image.getOriginalFilename() + "] failed to save file...");
            } catch(Exception e) {
                throw new FileUploadException("[" + image.getOriginalFilename() + "] failed to save file...");
            }
        }

        return imageList;
    }

    public static String MD5Generator(String input) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        MessageDigest mdMD5 = MessageDigest.getInstance("MD5");

        mdMD5.update(input.getBytes("UTF-8"));

        byte[] md5Hash = mdMD5.digest();
        StringBuilder hexMD5hash = new StringBuilder();

        for(byte b : md5Hash) {
            String hexString = String.format("%02x", b);

            hexMD5hash.append(hexString);
        }

        return hexMD5hash.toString();
    }
}

auth-service와 비슷한 코드가 많아 이해하는데 어려움이 없을 코드들이 많을텐데 FileUploader부분만 설명하도록 하겠습니다.

FileUploader는 다중 이미지를 저장하기 위한 유틸 클래스입니다. parseFileInfo라는 인스턴스를 이용해 이미지와 관련된 파일명이라던지, 파일경로라던지 이런 string의 값들을 데이터베이스에 저장하고 실질적인 파일이미지는 post-service에 저장하는 방식입니다. 그래서 추후에 이미지를 쓰고싶다면 파일경로를 데이터베이스로부터 읽어와 basePath 경로에 있는 저장된 파일 이미지를 쓰게 되는 방식이죠.

parseInfo는 images, post를 받아와 ImageEntity에 정의해둔 변수들에 builder패턴으로 값을 넣어줍니다. 중요한 부분을 순서대로 살펴보겠습니다.

1) String savePath는 이미지 파일이 저장되는 경로입니다. 그리고 이 경로에 디렉토리가 없다면 디렉토리를 생성하죠.

2) ImageEntity들을 담기 위한 imageList를 만듭니다.

3) MultipartFile 리스트를 for구문을 이용해 개별 MultipartFile로 나누어 저장합니다.

4) MD5Generator메서드는 원본의 파일명으로 디렉토리에 저장되는 게 아니라 유니크한 파일명으로 파일명을 변경해 디렉토리에 저장시키게 하기 위한 파일명 생성 메서드입니다.

5) 데이터베이스에 저장하기 위한 ImageEntity를 생성하고 이 객체를 앞서 생성해둔 imageList에 담습니다.

6) 마지막으로 File 객체를 생성해 filePath로 image를 전송합니다. 그리고 이 image는 읽고 쓰기가 가능한 파일로 설정을 마친채로 전체적인 imageList를 반환합니다.

comment부분 작업을 하겠습니다.

  • RequestCreateComment
package com.microservices.postservice.vo;

import lombok.Getter;

import javax.validation.constraints.NotNull;

@Getter
public class RequestCreateComment {
    @NotNull()
    String writer;

    @NotNull(message="Comment cannot be null")
    String comment;

    @NotNull(message="PostId cannot be null")
    Long postId;

    String createdAt;
}
  • CommentEntity
package com.microservices.postservice.entity;

import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Data
@Entity
@Table(name="comments")
@NoArgsConstructor
public class CommentEntity {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    Long id;

    @Column(nullable = false)
    String writer;

    @Column(nullable = false)
    String comment;

    @Column(nullable = false)
    String createdAt;

    @ManyToOne
    @JoinColumn(name="post_id")
    private PostEntity post;

    @Builder
    public CommentEntity(
        String writer,
        String comment,
        String createdAt,
        PostEntity post
    ) {
        this.writer = writer;
        this.comment = comment;
        this.createdAt = createdAt;
        this.post = post;
    }

    public void setPost(PostEntity post) {
        this.post = post;

        if(!post.getComments().contains(this)) {
            post.getComments().add(this);
        }
    }
}
  • CommentService
package com.microservices.postservice.service;

import com.microservices.postservice.dto.CommentDto;
import com.microservices.postservice.entity.CommentEntity;
import com.microservices.postservice.repository.CommentRepository;
import com.microservices.postservice.repository.PostRepository;
import com.microservices.postservice.util.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
public class CommentService {
    private CommentRepository commentRepository;
    private PostRepository postRepository;

    @Autowired
    public CommentService(
        CommentRepository commentRepository,
        PostRepository postRepository
    ) {
        this.commentRepository = commentRepository;
        this.postRepository = postRepository;
    }

    @Transactional
    public Long writeComment(CommentDto commentDto) {
        CommentEntity commentEntity = CommentEntity.builder()
                                                   .writer(commentDto.getWriter())
                                                   .comment(commentDto.getComment())
                                                   .post(postRepository.getOne(commentDto.getPostId()))
                                                   .createdAt(DateUtil.dateNow())
                                                   .build();

        return commentRepository.save(commentEntity).getId();
    }

    @Transactional
    public String deleteComment(Long id) {
        CommentEntity commentEntity = commentRepository.getOne(id);

        commentRepository.delete(commentEntity);

        return "Successfully delete this comment";
    }
}
  • CommentRepository
package com.microservices.postservice.repository;

import com.microservices.postservice.entity.CommentEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CommentRepository extends JpaRepository<CommentEntity, Long> {
}

post-service를 전체적으로 완성을 해보았는데 아직 테스트를 진행해보지 않아 오류가 생길수도 있습니다. 다음 포스트에서는 테스트를 통해 게시글들이 이미지와 함께 잘 저장이 되는지, 게시글을 잘 읽어 오는지, 댓글은 잘 저장이 되는지 등 post-service와 관련된 테스트를 진행해보겠습니다.

0개의 댓글

관련 채용 정보