spring:
servlet:
multipart:
max-file-size: 10MB # 파일 1개 당 최대 용량
max-request-size: 100MB # 한 번에 업로드할 수 있는 파일의 총 용량
// 클래스 구현 내용 : 사용자가 http://..../path로 요청을 하면,
// 어떤 위치에 있는 정적 리소르를 제공해주세요!
// 1. @Configuration 을 붙여서 Bean 자동 생성 + Configuration 하는 파일임을 알림
@Configuration
@RequiredArgsConstructor
public class WebResourceConfig implements WebMvcConfigurer {
private final FileUploadConfig fileUploadConfig;
// 클라이언트에 path에 통신 시 제공할 정적 리소스의 위치 매핑
@Override
// 2. addResourceHandlers(ResourceHandler Registry registry) 메소드 오버라이드
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 2-1. ResourceHandlerRegistory.addResourceHandler("path") : 아래 주소로 클라이언트가 요청을 보내면
// -> http://localhost:9000/uuploaders/loopy.jpg로 요청하면
registry.addResourceHandler("/uploads/**")
// ResourceHandlerRegistry.addResourceLocation("경로") : 이 위치에 있는 정작 리소트를 제공해라.
// 실제 로컬에 저장된 C:Users/user/spring/upload/loopy.jpg를 꺼내 주겠다.
.addResourceLocations("file:" + fileUploadConfig.getLocation());
}
}
// 클라이언트가 파일업로드 시, 저장될 폴더를 지정하고 어플리케이션에 실행 시에 그 폴더를 생성해주는 이벤트
@Getter @Setter
@Configuration // spring에서 자동 bean 생성
public class FileUploadConfig {
// @Value : 필드 값을 ()와 같이 주입
@Value("${file.upload.location}")
private String location; // 파일을 저장할 루트 디렉토리 지정
// @PostConsutrct : 서버가 실행되자마자 아래 메소드를 실행해라.
// 아래 메소드 내용 : 폴더 생성
@PostConstruct
public void init() {
File directory = new File(location);
if (!directory.exists()) {
directory.mkdirs();
}
}
@RequestParam, MutlpartFile, MultipartFile. transferTo()
// 1. 클라이언트가 File을 POST하는 경우, (평상시에는 @RequestParam은 쿼리 파라미터를 받지만)
// @RequestParam으로 클라리언트가 Json Body에 보낸 File을 받음
// 2. 이것을 아래에서 Spring framework에서 제공하는 MultipartFile이라는 객체로 받음
// 3. MultipartFile.transferTo(파일 업로드 위치) 로 파일을 전송할 수 있음
@PostMapping("/upload-multi")
public ResponseEntity<?> uploadMultiFile(
// 1. 클라이언트가 File을 POST하는 경우, (평상시네는 @RequestParam은 쿼리 파라미터를 받지만)
// @RequestParam으로 클라리언트가 Json Body에 보낸 File을 받음
// 2. 이것을 아래에서 Spring framework에서 제공하는 MultipartFile이라는 객체로 받음
// 3. MultipartFile.transferTo(파일 업로드 위치) 로 파일을 전송할 수 있음
@RequestParam("profile") List<MultipartFile> fileList
) {
for (MultipartFile file : fileList) {
log.info("uploaded file name: {}", file.getOriginalFilename());
log.info("uploaded file type: {}", file.getContentType());
// 저장 파일 경로 생성
String rootDir = fileUploadConfig.getLocation();
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
File uploadedLocation = new File(rootDir + fileName);
// 파일 저장
try {
// MultipartFile.transferTo(업로드할 경로)
file.transferTo(uploadedLocation);
} catch (IOException e) {
return ResponseEntity.internalServerError().body("파일 저장 실패!");
}
}
return ResponseEntity.ok().body("multiple upload success!");
}
}
@RestController
@RequestMapping("/api/posts")
@Slf4j
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
// 피드 생성 요청
@PostMapping
public ResponseEntity<?> createFeed(
// 피드 내용, 작성자 이름 JSON { "writer": "", "content": "" } -> 검증
@RequestPart("feed") @Valid PostCreate postCreate
// 이미지 파일 목록 multipart-file
, @RequestPart("images") List<MultipartFile> images
) {
images.forEach(image -> {
log.info("uploaded image file name - {}", image.getOriginalFilename());
});
postCreate.setImages(images);
log.info("feed create request: POST - {}", postCreate);
// 이미지와 JSON을 서비스클래스로 전송
Long postId = postService.createFeed(postCreate);
// 응답 메시지 생성
Map<String, Object> response = Map.of(
"id", postId
, "message", "save success"
);
return ResponseEntity
.ok()
.body(response);
}
}
🔹보통은 DB에 바로 저장 요청하지만, 이 경우 DB에는 이미지 자체를 담는 것이 아니라 이미지 저장 경로만 담으므로 먼저 파일을 서버에 요청해야 함
@Service
@Slf4j
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository; // db에 피드내용 저장, 이미지저장
private final FileUploadUtil fileUploadUtil; // 로컬서버에 이미지 저장
// 피드 생성 DB에 가기 전 후 중간처리
public Long createFeed(PostCreate postCreate) {
// entity 변환
Post post = postCreate.toEntity();
// 피드게시물을 posts테이블에 insert
postRepository.saveFeed(post);
// 이미지 관련 처리를 모두 수행
Long postId = post.getId();
processImages(postCreate.getImages(), postId);
// 컨트롤러에게 결과 반환
return postId;
}
@Slf4j
@RequiredArgsConstructor
@Component
public class FileUploadUtil {
private final FileUploadConfig fileUploadConfig;
// 하나의 파일을 로컬 저장 폴더에 저장하고 그 업로드 경로를 리턴
public String saveFile(MultipartFile file) {
if (file.isEmpty()) {
// ... 예외처리
}
// 원본 파일명 불러오기
String originalFilename = file.getOriginalFilename();
// 파일명 랜덤으로 바꾸기
String newFileName = UUID.randomUUID() + "_" + originalFilename;
try {
// 저장할 절대경로 생성
String uploadPath = fileUploadConfig.getLocation() + newFileName;
log.debug("Attempting to save file to : {}", uploadPath);
// 실제 파일 저장
file.transferTo(new File(uploadPath));
// server url : WebRESOURCEconfig에서 바꿔놓은 url
} catch (Exception e) {
// ... 예외처리
log.error("Failed to save file: {}", newFileName, e);
}
return "/uploads/" + newFileName;
}
}