
이번에는 서비스 뉴스 도메인에 속하는,
Our News 게시판 페이지에 대한 API를 구현해 볼것이다.
서비스의 이벤트 및 신기능 Open시에 정보가 올라가는 게시판이다.
사진을 저장하는 것은 참 어렵다.
경로 문제에 대한 고민도 해야 하며(로컬용,배포용, 윈도우 리눅스 맥에 대한 고려 등등...),
평소에 사용하지 않는 코드들이기 때문이다.
우선 구현해보자.
먼저 다음과 같이 Util 디렉토리를 만들고 그 안에 경로 설정 클래스를 만들어준다.

코드는 다음과 같다.
/* 사진 저장 선택지 1 : 기본 저장 경로 (운영 체제 독립적으로 작성) */
//private final Path uploadDir = Paths.get(System.getProperty("user.home"), "uploads", "images");
/* 사진 저장 선택지 2 : 서버 */
private final Path uploadDir = Paths.get("C:\\Users\\류성열\\Desktop\\Personal-HomePage-Project-main\\Personal-HomePage-Project-main\\MyProject\\backend\\server\\spring\\src\\main\\resources\\images\\OurNews");
public OurNews_Set_Image_Path() {
// 디렉토리가 존재하지 않으면 생성
try {
if (!Files.exists(uploadDir)){
Files.createDirectories(uploadDir);
}
} catch (IOException e) {
throw new RuntimeException(" Error Location = (website.server.Domain.Healing_Space_News.Our_News.Util.OurNews_Set_Image_Path) - " +
" Failed to create upload directory: " + e.getMessage(), e);
}
}
public String uploadPhoto(MultipartFile file) throws IOException {
// 파일 이름 중복 방지를 위해 UUID 사용
String originalFilename = file.getOriginalFilename();
String uniqueFilename = UUID.randomUUID() + "_" + originalFilename;
// 저장할 파일의 경로
Path destinationPath = uploadDir.resolve(uniqueFilename);
// 파일 저장
file.transferTo(destinationPath.toFile());
// 저장된 파일 경로 반환
return destinationPath.toString();
}
두개의 메서드가 있다.
-1번 메서드
디렉토리가 존재하지 않는지를 확인하고 없으면 File의 createDirectories 메서드를 통해 디렉토리를 추가한다.
-2번 메서드
UUID라는 것을 사용한다.
UUID는 (Universally Unique Identifier)라고 불리는, 전 세계적으로 고유한 식별자를 생성하기 위한 표준이다.
이미지가 중복되지 않게 uuid와 파일의 오리지널 네임을 합성하여,
파일을 저장하고 파일의 경로를 반환한다.
우선 나는 게시물에 사진을 올려도 되고,
올리지 않아도 되는 것으로 비지니스 도메인을 설계했다.
사진을 올리지 않으면 400 bad request가 뜬다.

당연한 것이다.
정의한 데이터를 모두 보내지 않은것은 bad request이기 때문이다.
수정한 부분은
@RequestParam(value = "file", required = false) MultipartFile file, // 이미지 파일 받기
이 부분이다.
requeired = false라는 것은 꼭 이 데이터를 받지 않아도 용인 된다는 설정값이다.
(사진을 넣고 말고에 대한 구현이 이렇게 쉽게 정해지다니,
참 다행이다.)
Full code
/**
* 게시글 업로드 API
* @param request 사용자 요청
* @param file 이미지 파일
* @param title 게시글 제목
* @param content 게시글 본문
* @return 성공 메시지
*/
@Operation(summary = "", description = "")
@PostMapping("/")
public ResponseEntity<String> postNews(HttpServletRequest request,
@RequestParam(value = "file", required = false) MultipartFile file, // 이미지 파일 받기
@RequestParam("title") String title,
@RequestParam("content") String content){
/* DTO 등록 */
PostNewsRequest postNewsRequest = new PostNewsRequest(title, content, file);
/* 게시글 업로드 서비스 호출 */
ourNewsService.postNews(request, postNewsRequest);
return ResponseEntity.ok("게시글 업로드 완료");
}
하지만 코드를 수정한 후

정상적으로 사진 없이도 입력이 됨을 알 수 있다.
DB에도 정상적으로 null 값이 들어갔다. (3번 게시물)

/**
* 게시글 업로드 API
* @param request 사용자 요청
* @param file 이미지 파일
* @param title 게시글 제목
* @param content 게시글 본문
* @return 성공 메시지
*/
@Operation(summary = "", description = "")
@PostMapping("/")
public ResponseEntity<String> postNews(HttpServletRequest request,
@RequestParam(value = "file", required = false) MultipartFile file, // 이미지 파일 받기
@RequestParam("title") String title,
@RequestParam("content") String content){
/* DTO 등록 */
PostNewsRequest postNewsRequest = new PostNewsRequest(title, content, file);
/* 게시글 업로드 서비스 호출 */
ourNewsService.postNews(request, postNewsRequest);
return ResponseEntity.ok("게시글 업로드 완료");
}
이번에는 MultipartFile이라는 것을 사용한다.
평소였으면 위 파라미터를 애당초 전부 DTO에 담아 매개변수로 넘겨줬을텐데,
이번에는 MultipartFile이 있기에, 저렇게 파라미터로 받고
그 다음에 DTO에 저장을 해준다.
MultipartFile는 클라이언트로부터 파일을 받을 때 사용하는 라이브러리이다.
/**
* 게시글 업로드 메서드
* @param request|사용자 요청
* @param postNewsRequest 게시글 데이터
*/
void postNews(HttpServletRequest request, PostNewsRequest postNewsRequest);
추후 비지니스 전략이 변동과 유연성을 염두에 두고 인터페이스를 구현체와 분리한다.
/**
* 게시글 올리기 API
* @param request 사용자 요청
* @param postNewsRequest 게시글 데이터
*/
@Override
public void postNews(HttpServletRequest request,
PostNewsRequest postNewsRequest) {
/* 파일 업로드 */
MultipartFile file = postNewsRequest.file();
String imgPath = null;
/* 이미지 파일이 있을 경우 파일을 업로드하고 경로를 저장 */
if (file != null && !file.isEmpty()) {
try {
imgPath = ourNewsSetImagePath.uploadPhoto(file); // 파일 경로를 반환받음
} catch (IOException e) {
e.printStackTrace();
}
}
/* 데이터 추출 */
Long userNumber = jwtService.extractUserNumberFromRequest(request);
String title = postNewsRequest.title();
String content = postNewsRequest.content();
/* 데이터 저장 */
ourNewsMapper.postNews(userNumber,title,content,imgPath);
}
multipartfile을 통해 file 객체를 만들고,
매개변수로 받은 file 데이터를 넣어준다.
이전의 코드들이 이 file 데이터를 제대로 받아왔으면,
파일을 업로드 하고 경로를 저장한다.
이로써 사진이 서버 안에 저장이 완료된다.
하지만 아직 이 사진이 DB에 저장된 것은 아니다.
그 아래 데이터를 추출하고 데이터를 저장하는 코드를 통해,
게시글의 모든 데이터가 저장된다.
/**
* 게시글 업로드
* @param userNumber 사용자 고유번호
* @param title 게시글 제목
* @param content 게시글 본문
* @param img_path 사진의 경로
*/
void postNews(@Param("userNumber") Long userNumber,
@Param("title") String title,
@Param("content") String content,
@Param("img_path") String img_path);
<!-- 게시글 업로드 -->
<insert id="postNews" parameterType="map">
INSERT INTO our_news (userNumber, title, content, img_path, createdDate, watched)
VALUES (#{userNumber}, #{title}, #{content}, #{img_path}, CURRENT_TIMESTAMP, 0)
</insert>
사진을 넣어서 저장한 결과 1,2번 게시물(성공)
사진을 넣지 않고 저장한 결과 3번 게시물(성공)

200ok + 게시글 업로드 성공 메시지
