Skip to content

Commit be0d58f

Browse files
committed
feat: News 이미지 관련 파일 수정
1 parent 5c11932 commit be0d58f

File tree

8 files changed

+205
-70
lines changed

8 files changed

+205
-70
lines changed

src/main/java/dmu/dasom/api/domain/news/controller/NewsController.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1010
import io.swagger.v3.oas.annotations.media.Content;
1111
import io.swagger.v3.oas.annotations.media.ExampleObject;
12+
import org.springframework.http.MediaType;
1213
import org.springframework.http.ResponseEntity;
1314
import org.springframework.web.bind.annotation.*;
1415

1516
import jakarta.validation.Valid;
17+
import org.springframework.web.multipart.MultipartFile;
18+
1619
import java.util.List;
1720

1821
@Tag(name = "NEWS API", description = "다솜소식 API")
@@ -48,29 +51,26 @@ public ResponseEntity<NewsResponseDto> getNewsById(@PathVariable Long id) {
4851
return ResponseEntity.ok(responseDto);
4952
}
5053

51-
// 생성
54+
// 생성 (multipart/form-data 사용)
5255
@Operation(summary = "소식 등록", description = "새로운 소식을 등록")
53-
@ApiResponses(value = {
54-
@ApiResponse(responseCode = "201", description = "생성 완료"),
55-
@ApiResponse(responseCode = "400", description = "유효하지 않은 요청 데이터",
56-
content = @Content(mediaType = "application/json",
57-
examples = @ExampleObject(value = "{ \"errorCode\": \"E003\", \"errorMessage\": \"유효하지 않은 입력입니다.\" }")))
58-
})
59-
@PostMapping
60-
public ResponseEntity<NewsResponseDto> createNews(@Valid @RequestBody NewsRequestDto requestDto) {
61-
NewsResponseDto responseDto = newsService.createNews(requestDto);
56+
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
57+
public ResponseEntity<NewsResponseDto> createNews(
58+
@Valid @RequestPart("news") NewsRequestDto requestDto, // JSON 데이터 받기
59+
@RequestPart(value = "images", required = false) List<MultipartFile> imageFiles // 이미지 파일 받기
60+
) {
61+
NewsResponseDto responseDto = newsService.createNews(requestDto, imageFiles);
6262
return ResponseEntity.status(201).body(responseDto);
6363
}
6464

65-
// 수정
65+
// 수정 (multipart/form-data 사용)
6666
@Operation(summary = "소식 수정", description = "ID로 특정 소식을 수정")
67-
@ApiResponses({
68-
@ApiResponse(responseCode = "200", description = "수정 성공"),
69-
@ApiResponse(responseCode = "400", description = "소식을 찾을 수 없음")
70-
})
71-
@PutMapping("/{id}")
72-
public ResponseEntity<NewsResponseDto> updateNews(@PathVariable Long id, @Valid @RequestBody NewsRequestDto requestDto) {
73-
NewsResponseDto updatedNews = newsService.updateNews(id, requestDto);
67+
@PutMapping(value = "/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
68+
public ResponseEntity<NewsResponseDto> updateNews(
69+
@PathVariable Long id,
70+
@Valid @RequestPart("news") NewsRequestDto requestDto,
71+
@RequestPart(value = "images", required = false) List<MultipartFile> imageFiles
72+
) {
73+
NewsResponseDto updatedNews = newsService.updateNews(id, requestDto, imageFiles);
7474
return ResponseEntity.ok(updatedNews);
7575
}
7676

@@ -85,5 +85,5 @@ public ResponseEntity<Void> deleteNews(@PathVariable Long id) {
8585
newsService.deleteNews(id);
8686
return ResponseEntity.noContent().build();
8787
}
88-
88+
8989
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dmu.dasom.api.domain.news.controller;
2+
3+
import dmu.dasom.api.domain.news.service.NewsImageService;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.*;
7+
import org.springframework.web.multipart.MultipartFile;
8+
9+
import java.util.List;
10+
11+
@RestController
12+
@RequestMapping("/api/news/images")
13+
@RequiredArgsConstructor
14+
public class NewsImageController {
15+
16+
private final NewsImageService newsImageService;
17+
18+
@PostMapping("/upload")
19+
public ResponseEntity<List<String>> uploadImages(@RequestParam("images") List<MultipartFile> images) {
20+
try {
21+
List<String> imageUrls = newsImageService.uploadImages(images);
22+
return ResponseEntity.ok(imageUrls);
23+
} catch (Exception e) {
24+
return ResponseEntity.status(500).body(List.of("파일 업로드 실패: " + e.getMessage()));
25+
}
26+
}
27+
28+
}

src/main/java/dmu/dasom/api/domain/news/dto/NewsRequestDto.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import lombok.AllArgsConstructor;
77
import lombok.Getter;
88
import lombok.NoArgsConstructor;
9+
import org.springframework.web.multipart.MultipartFile;
10+
11+
import java.util.List;
912

1013
@Getter
1114
@NoArgsConstructor
@@ -24,6 +27,6 @@ public class NewsRequestDto {
2427

2528
@Size(max = 255, message = "이미지 URL은 최대 255자입니다.")
2629
@Schema(description = "뉴스 이미지 URL", example = "http://example.com/image.jpg", nullable = true)
27-
private String imageUrl;
30+
private List<MultipartFile> images;
2831

2932
}

src/main/java/dmu/dasom/api/domain/news/dto/NewsResponseDto.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import lombok.Getter;
55

66
import java.time.LocalDateTime;
7+
import java.util.List;
78

89
@Getter
910
@Schema(name = "NewsResponseDto", description = "뉴스 응답 DTO")
@@ -21,15 +22,15 @@ public class NewsResponseDto {
2122
@Schema(description = "작성일", example = "2025-02-14T12:00:00")
2223
private LocalDateTime createdAt;
2324

24-
@Schema(description = "뉴스 이미지 URL", example = "https://example.com/image.jpg", nullable = true)
25-
private String imageUrl;
25+
@Schema(description = "뉴스 이미지 URL", example = "['https://example.com/image.jpg', 'https://example.com/image2.jpg']", nullable = true)
26+
private List<String> imageUrls;
2627

27-
public NewsResponseDto(Long id, String title, String content, LocalDateTime createdAt, String imageUrl) {
28+
public NewsResponseDto(Long id, String title, String content, LocalDateTime createdAt, List<String> imageUrls) {
2829
this.id = id;
2930
this.title = title;
3031
this.content = content;
3132
this.createdAt = createdAt;
32-
this.imageUrl = imageUrl;
33+
this.imageUrls = imageUrls;
3334
}
3435

3536
}

src/main/java/dmu/dasom/api/domain/news/entity/NewsEntity.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import jakarta.persistence.*;
88
import lombok.*;
99

10+
import java.util.List;
11+
1012
@Getter
1113
@Entity
1214
@Table(name = "news")
@@ -30,9 +32,11 @@ public class NewsEntity extends BaseEntity {
3032
@Schema(description = "뉴스 내용", example = "뉴스 예제 내용")
3133
private String content;
3234

33-
@Column(length = 255)
34-
@Schema(description = "뉴스 이미지 URL", example = "https://example.com/image.jpg")
35-
private String imageUrl;
35+
@ElementCollection
36+
@CollectionTable(name = "news_images", joinColumns = @JoinColumn(name = "news_id"))
37+
@Column(name = "image_url", length = 255)
38+
@Schema(description = "뉴스 이미지 URL 리스트", example = "[\"https://example.com/image1.jpg\", \"https://example.com/image2.jpg\"]")
39+
private List<String> imageUrls;
3640

3741
// 뉴스 상태 업데이트
3842
public void updateStatus(Status status) {
@@ -41,14 +45,14 @@ public void updateStatus(Status status) {
4145

4246
// NewsEntity → NewsResponseDto 변환
4347
public NewsResponseDto toResponseDto() {
44-
return new NewsResponseDto(id, title, content, getCreatedAt(), imageUrl);
48+
return new NewsResponseDto(id, title, content, getCreatedAt(), imageUrls);
4549
}
4650

47-
// 수정
48-
public void update(String title, String content, String imageUrl) {
51+
// 수정 메서드
52+
public void update(String title, String content, List<String> imageUrls) {
4953
this.title = title;
5054
this.content = content;
51-
this.imageUrl = imageUrl;
55+
this.imageUrls = imageUrls;
5256
}
5357

5458
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package dmu.dasom.api.domain.news.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.stereotype.Service;
5+
import org.springframework.web.multipart.MultipartFile;
6+
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.UUID;
12+
13+
@Service
14+
@RequiredArgsConstructor
15+
public class NewsImageService {
16+
17+
private static final String IMAGE_DIR = "src/main/resources/static/images/";
18+
19+
public List<String> uploadImages(List<MultipartFile> images) {
20+
21+
List<String> imageUrls = new ArrayList<>();
22+
23+
if (images != null) {
24+
for (MultipartFile image : images) {
25+
try {
26+
String imageUrl = saveImage(image);
27+
imageUrls.add(imageUrl);
28+
} catch (IOException e) {
29+
throw new RuntimeException("이미지 저장 중 오류", e);
30+
}
31+
}
32+
}
33+
34+
return imageUrls;
35+
36+
}
37+
38+
private String saveImage(MultipartFile image) throws IOException {
39+
40+
File uploadDir = new File(IMAGE_DIR);
41+
if (!uploadDir.exists()) {
42+
uploadDir.mkdirs();
43+
}
44+
45+
String fileName = UUID.randomUUID().toString() + "_" + image.getOriginalFilename();
46+
File destFile = new File(IMAGE_DIR + fileName);
47+
image.transferTo(destFile);
48+
49+
return "/images/" + fileName; // 저장된 이미지 URL 반환
50+
}
51+
52+
}

src/main/java/dmu/dasom/api/domain/news/service/NewsService.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@
66
import dmu.dasom.api.domain.news.dto.NewsResponseDto;
77
import dmu.dasom.api.domain.news.entity.NewsEntity;
88
import dmu.dasom.api.domain.news.repository.NewsRepository;
9+
import lombok.RequiredArgsConstructor;
910
import org.springframework.stereotype.Service;
1011
import org.springframework.transaction.annotation.Transactional;
12+
import org.springframework.web.multipart.MultipartFile;
13+
1114
import java.util.List;
1215
import java.util.stream.Collectors;
1316

1417
@Service
18+
@RequiredArgsConstructor
1519
public class NewsService {
1620

1721
private final NewsRepository newsRepository;
18-
19-
public NewsService(NewsRepository newsRepository) {
20-
this.newsRepository = newsRepository;
21-
}
22+
private final NewsImageService newsImageService;
2223

2324
// 전체 조회
2425
public List<NewsResponseDto> getAllNews() {
@@ -36,11 +37,14 @@ public NewsResponseDto getNewsById(Long id) {
3637

3738
// 생성
3839
@Transactional
39-
public NewsResponseDto createNews(NewsRequestDto requestDto) {
40+
public NewsResponseDto createNews(NewsRequestDto requestDto, List<MultipartFile> imageFiles) {
41+
// 이미지 업로드 후 URL 리스트 받아오기
42+
List<String> uploadedImageUrls = newsImageService.uploadImages(imageFiles); // URL 리스트 생성
43+
4044
NewsEntity news = NewsEntity.builder()
4145
.title(requestDto.getTitle())
4246
.content(requestDto.getContent())
43-
.imageUrl(requestDto.getImageUrl())
47+
.imageUrls(uploadedImageUrls) // 이미지 URL 리스트 저장
4448
.build();
4549

4650
NewsEntity savedNews = newsRepository.save(news);
@@ -49,11 +53,16 @@ public NewsResponseDto createNews(NewsRequestDto requestDto) {
4953

5054
// 수정
5155
@Transactional
52-
public NewsResponseDto updateNews(Long id, NewsRequestDto requestDto) {
56+
public NewsResponseDto updateNews(Long id, NewsRequestDto requestDto, List<MultipartFile> imageFiles) {
57+
// 뉴스 엔티티 찾기
5358
NewsEntity news = newsRepository.findById(id)
5459
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND));
5560

56-
news.update(requestDto.getTitle(), requestDto.getContent(), requestDto.getImageUrl());
61+
// 이미지 업로드 후 URL 리스트 받아오기
62+
List<String> uploadedImageUrls = newsImageService.uploadImages(imageFiles);
63+
64+
news.update(requestDto.getTitle(), requestDto.getContent(), uploadedImageUrls);
65+
5766
return news.toResponseDto();
5867
}
5968

0 commit comments

Comments
 (0)