사전 세팅
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());
}
}