사전 세팅

  • 먼저 AWS에서 S3 버킷을 만들고 IAM에서 사용자를 만들어 access key와 secret ket를 발급받는 작업이 필요하다. 
  • 참고 링크 : https://jojoldu.tistory.com/300
 

SpringBoot & AWS S3 연동하기

안녕하세요? 이번 시간엔 SpringBoot & AWS S3 연동하기 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와

jojoldu.tistory.com

 

구현 코드 (1) S3 부분

먼저, S3 라는 패키지를 만들어 이미지를 S3에 업로드하고 삭제하는 부분은 이곳에 구현을 했다.

 

1. AmazonS3Config 파일

@Configuration
public class AmazonS3Config {

    @Value("${cloud.aws.credentials.accessKey}")                        //S3 accessKey
    private String accessKey;

    @Value("${cloud.aws.credentials.secretKey}")                        //S3 secretKey
    private String secretKey;

    @Value("${cloud.aws.region.static}")                                //S3 region
    private String region;

    @Bean
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);

        return (AmazonS3Client) AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
                .build();
    }
}

 

2. S3Controller 파일

사실 이 파일은 전체 서비스와 합칠 때 필요한 부분은 아니지만, 그 전에 업로드가 되는지 보기위해 테스트용으로 있는 컨트롤러이다.

@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class S3Controller {
    private final S3Service s3Uploader;

    @GetMapping("/images")
    public String getImage(@AuthenticationPrincipal UserDetailsImpl userDetails)throws IOException{
        return s3Uploader.getThumbnailPath("img.png");
    }

    @PostMapping("/images")
    public String delete(@RequestParam(required = true) String fileName){
         s3Uploader.deleteFile(fileName);
        return "success";
    }
}

 

3. S3Service 부분

@Slf4j
@Component
@RequiredArgsConstructor
@Service
public class S3Service {

    private final AmazonS3Client amazonS3Client;

    private final ImageFileRepository imageFileRepository;
    @Value("${cloud.aws.s3.bucket}")                                                        //bucket 이름
    public String bucket;

    public void upload(MultipartFile multipartFile, String dirName, Board board, User user) throws IOException {
        File uploadFile = convert(multipartFile).orElseThrow(() -> new IllegalArgumentException("파일 전환 실패"));

        ImageFile imageFile = new ImageFile(upload(uploadFile, dirName), user, board);
        imageFileRepository.save(imageFile);
//        return upload(uploadFile, dirName);
    }

    // S3로 파일 업로드하기
    private String upload(File uploadFile, String dirName) {
        String fileName = dirName + "/" + UUID.randomUUID(); // S3에 저장된 파일 이름
        String uploadImageUrl = putS3(uploadFile, fileName); // s3로 업로드
        removeNewFile(uploadFile);
        return uploadImageUrl;
    }

    // S3로 업로드
    private String putS3(File uploadFile, String fileName) {
        amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
        return amazonS3Client.getUrl(bucket, fileName).toString();
    }

    // 로컬에 저장된 이미지 지우기
    private void removeNewFile(File targetFile) {
        if (targetFile.delete()) {
            log.info("File delete success");
            return;
        }
        log.info("File delete fail");
    }

    private Optional<File> convert(MultipartFile multipartFile) throws IOException {
        File convertFile = new File(System.getProperty("user.dir") + "/" + multipartFile.getOriginalFilename());
        // 바로 위에서 지정한 경로에 File이 생성됨 (경로가 잘못되었다면 생성 불가능)
        if (convertFile.createNewFile()) {
            try (FileOutputStream fos = new FileOutputStream(convertFile)) { // FileOutputStream 데이터를 파일에 바이트 스트림으로 저장하기 위함
                fos.write(multipartFile.getBytes());
            }
            return Optional.of(convertFile);
        }

        return Optional.empty();
    }

    // find image from s3
    public String getThumbnailPath(String path) {
        return amazonS3Client.getUrl(bucket, path).toString();
    }

    //remove s3 object
    public void deleteFile(String fileName){
        DeleteObjectRequest request = new DeleteObjectRequest(bucket, fileName);
        amazonS3Client.deleteObject(request);
    }
}

 

 

코드 구현 (2) 게시판 코드에 적용하기

1. ImageFile 엔티티를 추가해 주었다.

@Getter
@Entity
@NoArgsConstructor
public class ImageFile {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;                    // id

    @Column(nullable = false)           // image 경로
    private String path;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "userid", nullable = false)
    private User user;                  // userid

    @OneToOne
    @JoinColumn(name = "boardid", nullable = false)
    private Board board;

    public ImageFile(String path, User user, Board board){
        this.path = path;
        this.user = user;
        this.board = board;
    }
}

 

 

2. BoardController

  • formdata 형식이기 때문에 POST 부분을 @RequestBody 가 아니라 @RequestPart 로 바꿔주어야 한다.
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class BoardController {

    // Connect to boardservice
    private final BoardService boardService;

    // DB save
    @PostMapping(value = "/boards", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<BoardResponseDto> createBoard(@RequestPart BoardRequestDto requestDto,
                                                        @RequestPart(required = false) MultipartFile image,
                                                        @AuthenticationPrincipal UserDetailsImpl userDetails) throws IOException{
        return ResponseEntity.ok(boardService.createBoard(requestDto, userDetails.getUser(), image));
    }

    // DB select all
    @GetMapping("/boards")
    public ResponseEntity<List<BoardResponseDto>> getBoards(){return ResponseEntity.ok(boardService.getBoards());}

    // DB select one
    @GetMapping("/boards/{id}")
    public ResponseEntity<BoardResponseDto> getBoard(@PathVariable long id){return ResponseEntity.ok(boardService.getBoard(id));}

    // DB update
    @PatchMapping("/boards/{id}")
    public ResponseEntity<BoardResponseDto> updateBoard(@PathVariable Long id,
                                                        @RequestBody BoardRequestDto requestDto,
                                                        @AuthenticationPrincipal UserDetailsImpl userDetails){
        return ResponseEntity.ok(boardService.update(id,requestDto, userDetails.getUser()));
    }

    // DB delete
    @DeleteMapping("/boards/{id}")
    public ResponseEntity<BoardDeleteResponseDto> deleteBoard(@PathVariable Long id,
                                                              @AuthenticationPrincipal UserDetailsImpl userDetails) {
        return ResponseEntity.ok(boardService.deleteBoard(id, userDetails.getUser()));
    }
}

 

3. BoardService 

 



@Slf4j
@Service
@RequiredArgsConstructor
public class BoardService {

    private final BoardRepository boardRepository;                                                      // board repo connect
    private final CommentRepository commentRepository;                                                  // comment repo connect
    private final CommentLikeRepository commentLikeRepository;                                          // commentLike repo connect
    private final BoardLikeRepository boardLikeRepository;                                              // boardLike repo connect
    private final ImageFileRepository imageFileRepository;                                              // ImageFile repo connect
    private final S3Service s3Uploader;

    @Value("${cloud.aws.s3.bucket}")
    public String bucket;

    // Board Create function
    public BoardResponseDto createBoard(BoardRequestDto requestDto, User user,MultipartFile image) throws IOException {

        // 1. create board Object and insert DB
        Board board = new Board(requestDto, user);                                                      // DTO -> Entity
        boardRepository.save(board);

//        if(!image.isEmpty()) {
        if (image != null){
            s3Uploader.upload(image, "static", board, user);
        }

        return new BoardResponseDto(board,user.getUsername(), "");                                          // return Response  Entity -> DTO

    }

    // Get Boards from DB (all)
    public List<BoardResponseDto> getBoards() {
        // 1. find boardList
        List<Board> BoardList = boardRepository.findAllByOrderByModifiedAtAsc();                            // Select All
        List<BoardResponseDto> BoardResponseDtoList = new ArrayList<>();

        // 2. find commentList and add ResponseDto
        for(Board board: BoardList){
            List<Comment> comments = commentRepository.findAllByBoardOrderByIdAsc(board);
            List<CommentResponseDto> commentList = new ArrayList<>();

            Optional<ImageFile> Img = imageFileRepository.findByBoard(board);
            String Img_path = "";

            if(Img.isEmpty()){
                Img_path = "";
            }
            else{
                Img_path = Img.get().getPath();
            }

            for (Comment comment : comments){
                commentList.add(new CommentResponseDto(comment));
            }

            if(comments.isEmpty()){
                BoardResponseDtoList.add(new BoardResponseDto(board,board.getUser().getUsername(), Img_path));
            }else{
                BoardResponseDtoList.add(new BoardResponseDto(board,board.getUser().getUsername(), commentList, Img_path));
            }
        }
        return BoardResponseDtoList;
    }

    // Get memo from DB (one)
    public BoardResponseDto getBoard(long id){
        // 1. find board
        Board board = boardRepository.findById(id).orElseThrow(()->                                        // Select one
                new CustomException(ErrorCode.NO_POST_FOUND)
        );

        // 2. find comment
        List<Comment> comments = commentRepository.findAllByBoardOrderByIdAsc(board);
        List<CommentResponseDto> commentList = new ArrayList<>();

        Optional<ImageFile> Img = imageFileRepository.findByBoard(board);
        String Img_path = "";

        if(Img.isEmpty()){
            Img_path = "";
        }
        else{
            Img_path = Img.get().getPath();
        }

        // 3. add ResponseDto
        for (Comment comment : comments){
            commentList.add(new CommentResponseDto(comment));
        }
        if(comments.isEmpty()){
            return new BoardResponseDto(board,board.getUser().getUsername(), Img_path);                               // Entity -> DTO
        }else{
            return new BoardResponseDto(board, board.getUser().getUsername(),commentList, Img_path);
        }
    }

    // DB update function
    @Transactional
    public BoardResponseDto update(Long id, BoardRequestDto requestDto, User user) {
        // 1. 유저 권한 GET
        UserRoleEnum userRoleEnum = user.getRole();
        Board board;

        // 2. 유저 권한에 따른 동작 방식 결정
        if(userRoleEnum == UserRoleEnum.USER){
            board = boardRepository.findById(id).orElseThrow(                                               // find memo
                    () -> new CustomException(ErrorCode.NO_POST_FOUND)
            );

            if(board.getUser().getId().equals(user.getId())){                                               // 자기 자신이 작성한 게시물이면
                board.update(requestDto);                                                                   // DB Update
            }else{
                throw new CustomException(ErrorCode.NO_MODIFY_POST);
            }
        }else{                                                                                              // 관리자 권한일 때,
            board = boardRepository.findById(id).orElseThrow(                                               // find board
                    () -> new CustomException(ErrorCode.NO_POST_FOUND)
            );
            board.update(requestDto);
        }
        return new BoardResponseDto(board,user.getUsername(), "");
    }

    // DB delete function (data delete)
    public BoardDeleteResponseDto deleteBoard(Long id, User user) {
        // 1. 유저 권한 GET
        UserRoleEnum userRoleEnum = user.getRole();
        Board board;

        // 2. 유저 권한에 따른 동작 방식 결정
        if(userRoleEnum == UserRoleEnum.USER){
            board = boardRepository.findById(id).orElseThrow(                                               // find memo
                    () -> new CustomException(ErrorCode.NO_ROLLING_FOUND)
            );

            if(board.getUser().getId().equals(user.getId())){                                               // 자기 자신이 작성한 게시물이면
                boardLikeRepository.deleteAllByBoard(board);                                                // DB DELETE (선행)
                List<Comment> commentList = commentRepository.findAllByBoardOrderByIdAsc(board);
                for(Comment comment: commentList){
                    commentLikeRepository.deleteAllByComment(comment);
                }

                Optional<ImageFile> imageFile = imageFileRepository.findByBoard(board);

                if (imageFile.isPresent()){
                    String path =  imageFile.get().getPath(); //이미지 파일 path값 가져오기
                    String filename = path.substring(58); // path값에서 키값 추출
                    s3Uploader.deleteFile(filename); //키값으로 s3 삭제

                    imageFileRepository.deleteAllByBoard(board);
                }

                commentRepository.deleteAllByBoard(board);
                boardRepository.deleteById(id);                                                             // 해당 게시물 삭제
            }else{
                System.out.println("Error");
                throw new CustomException(ErrorCode.NO_DELETE_POST);

            }
        }else{                                                                                              // 관리자 권한일 떄
            board = boardRepository.findById(id).orElseThrow(                                               // find board
                    () -> new CustomException(ErrorCode.NO_POST_FOUND)
            );
            boardLikeRepository.deleteAllByBoard(board);
            List<Comment> commentList = commentRepository.findAllByBoardOrderByIdAsc(board);
            for(Comment comment: commentList){
                commentLikeRepository.deleteAllByComment(comment);
            }

            commentRepository.deleteAllByBoard(board);
            boardRepository.deleteById(id);
        }
        return  new BoardDeleteResponseDto("삭제 성공", HttpStatus.OK.value());
    }

}

+ Recent posts