본문 바로가기
서버 공부

[스프링] 글 작성 API : 이미지 업로드 API와 글쓰기 API 따로 생성하기

by 모선효 2024. 6. 13.

S3를 사용하여 이미지를 업로드하는 코드는 이전 게시물에 올려뒀습니다! 먼저 아래 글을 읽고 오는 것을 추천합니다.

2024.06.13 - [서버 공부] - [스프링] S3를 이용한 이미지 업로드

 

[스프링] S3를 이용한 이미지 업로드

사진 업로드를 할 수 있는 외주 작업을 맡게 되어 S3를 이용한 이미지 업로드 로직을 작성했습니다.더보기요구사항 - 게시물에 이미지는 필수값이 아니다.- 하나의 게시물에 올릴 수 있는 파일

hy5sun.tistory.com

 

 

보통 한 페이지에 하나의 API를 요청하는 게 좋지만, 그럴 경우 form-data로 List<MultipartFile>과 JSON 데이터를 한 번에 받아야 합니다. 

 

플러터 같은 경우, form-data에 두 가지 타입의 데이터를 받는 게 어렵다고 하여 이와 같이 코드를 작성했습니다!

 

 

1. S3Controller 코드 작성 : 이미지 올리기 API 생성

@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/images")
public class S3Controller {
    private final S3Service s3Service;

    @PostMapping()
    @ResponseStatus(HttpStatus.CREATED)
    public CustomResponse uploadImages(@RequestPart(value = "files", required = false) List<MultipartFile> files, @Login Member member) {
        FileDto image = s3Service.uploadFiles(files);
        return CustomResponse.response(HttpStatus.CREATED, "이미지를 정상적으로 올렸습니다.", image);
    }
}

 

파일은 @RequestPart로 받아왔습니다. @RequestPart는 HTTP Request Body에 multipart/form-data가 포함되어 있는 경우 사용합니다. @RequestPart는 JSON이나 XML과 같은 데이터를 받을 수 있다는 특징이 있습니다.

 

@RequestParam을 사용해도 괜찮습니다!  다만 @RequestParam은 multipartFile이 아닌 경우 등록된 Converter를 사용하고, @RequestPart는 Content-Type 헤더를 고려한 HttpMessageConverters에 의존한다는 차이점이 있습니다.

 

이미지 업로드 API에 대한 Response는 다음과 같습니다.

{
    "statusCode": 201,
    "message": "이미지를 정상적으로 올렸습니다.",
    "data": {
        "imageUrl": []
    }
}

여기서 받은 imageUrl을 글 작성 API 요청할 때 서버로 전달하면 됩니다!

 

2. Image Entity 생성

@Table(name="image")
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Image extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(nullable = false)
    private String imageUrl;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "board_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Board board;

    @Builder
    public Image(String imageUrl, Board board) {
        this.imageUrl = imageUrl;
        this.board = board;
    }
}

 

저는 Image Entity를 따로 만들어서 imageUrl로 받은 데이터를 저장하도록 했습니다.

필드 값으로 이미지 url 과 board_id를 생성하여 추후에 게시물을 조회할 때, boardId를 통해 이미지 url 값을 가져오도록 했습니다.

 

4. CreateBoardRequest  & BoardService & BoardController 작성 : 글 작성 API 생성

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class CreateBoardRequest {
    
    @NotBlank(message = "제목을 입력하지 않았습니다.")
    private String title;

    @NotBlank(message = "내용을 입력하지 않았습니다.")
    private String content;

    @NotNull(message = "PET HELP 여부를 선택하지 않았습니다.")
    private Boolean isPetHelp;

    @NotNull(message = "이미지 URL을 입력하지 않았습니다.")
    private List<String> imageUrl;
}
@Service
@RequiredArgsConstructor
@Slf4j
public class BoardService {
    private final BoardRepository boardRepository;
    private final ImageRepository imageRepository;

    @Transactional
    public DetailBoardResponse createBoard(Member member, CreateBoardRequest req) {
        List<Image> images = makeImage(req.getImageUrl());

        Board board = Board.builder()
                .title(req.getTitle())
                .content(req.getContent())
                .isPetHelp(req.getIsPetHelp())
                .images(images)
                .member(member)
                .build();

        boardRepository.save(board);
        
        // Image 엔티티 속 Board 저장
        images.forEach(image -> {
            image.setBoard(board);
        });

        imageRepository.saveAll(images);

        return DetailBoardResponse.toDto(board);
    }
    
    // image builder
    private List<Image> makeImage(List<String> urls) {
        return urls.stream()
                .map(url -> Image.builder().imageUrl(url).build())
                .collect(Collectors.toList());
    }
}
@RestController
@RequestMapping("/boards")
@RequiredArgsConstructor
@Slf4j
public class BoardController {

    private final BoardService boardService;

    @PostMapping()
    @ResponseStatus(HttpStatus.CREATED)
    public CustomResponse create(@Validated @RequestBody CreateBoardRequest req, @Login Member member) throws IOException {
        DetailBoardResponse board = boardService.createBoard(member, req);
        return CustomResponse.response(HttpStatus.CREATED, "게시물을 정상적으로 작성했습니다.", board);
    }
}

 

 

클라이언트는 글을 작성할 때, 이미지 업로드 API 요청 후 글 작성 API를 요청하면 됩니다!

 

감사합니다.

 

Reference

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestPart.html