게시물 작성시 파일을 업로드할 수 있게 하고, 게시물 조회시 업로드한 파일을 보고, 다운로드받을 수 있도록 해보자.
1. 파일 업로드 화면 만들기
<form action="/forum/create" method="post" enctype="multipart/form-data">
...
<!-- 파일첨부용-->
<div class="input-group mb-3">
<input name="file" type="file" class="form-control" id="inputGroupFile02">
<label class="input-group-text" for="inputGroupFile02">Upload</label>
</div>
<button type="submit" class="btn btn-primary">save</button>
</form>
게시물 작성 화면에 파일 업로드 input을 생성한다.
```
```
(대략 이렇게 생김)
2. File 엔티티, FileDto 작성 및 Article 엔티티, ArticleDto 클래스에 fileId 필드 추가하기
@Entity
@Getter
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class File {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String origFilename;
@Column(nullable = false)
private String filename;
@Column(nullable = false)
private String filePath;
}
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FileDto {
private Long id;
private String origFilename;
private String filename;
private String filePath;
public File toEntity() {
File build = File.builder()
.id(id)
.origFilename(origFilename)
.filename(filename)
.filePath(filePath)
.build();
return build;
}
}
그리고 Article, ArticleDto에는
private Long fileId;
위 필드를 추가한다.
(엔티티의 경우 필요하다면 @Column(name="file_id")같은 어노테이션도 부착)
3. MD5Generator
public class MD5Generator {
private String result;
// 생성자: 입력으로 받은 문자열을 MD5 해시로 변환하여 result 변수에 저장
public MD5Generator(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {
// MD5 해시를 생성하기 위해 MessageDigest 클래스의 getInstance 메소드를 이용하여 "MD5" 알고리즘을 사용할 수 있도록 인스턴스를 생성
MessageDigest mdMD5 = MessageDigest.getInstance("MD5");
// MD5 해시를 생성할 데이터로 입력 받은 문자열을 UTF-8 인코딩으로 바이트 배열로 변환하여 MessageDigest에 업데이트
mdMD5.update(input.getBytes("UTF-8"));
// MessageDigest에서 해시 결과를 얻어옴. 해시 결과는 바이트 배열 형태로 저장
byte[] md5Hash = mdMD5.digest();
// 해시 결과를 16진수 형태로 변환하여 저장할 StringBuilder 객체 생성
StringBuilder hexMD5hash = new StringBuilder();
// 해시 결과의 각 바이트를 16진수 문자열로 변환하여 StringBuilder에 추가
for (byte b : md5Hash) {
String hexString = String.format("%02x", b);
hexMD5hash.append(hexString);
}
// 최종 MD5 해시 결과를 문자열로 저장
result = hexMD5hash.toString();
}
// MD5 해시 결과를 문자열 형태로 반환하는 메소드
public String toString() {
return result;
}
}
사용자가 파일을 업로드하면, 해당 파일의 MD5 해시값으로 파일명을 변환하여 서버에 저장할것이다. 이 때 사용되는 클래스가 MD5Generator.
MD5를 사용하는 이유는
1. 파일 업로드 시 원본 파일명을 그대로 사용하면 악의적인 사용자가 해당 파일을 직접 찾거나 악용할 수 있으므로, 보안을 위해
2. MD5 해시를 사용하면 파일의 내용이 동일한지 비교할 수 있으므로 중복된 파일을 식별하여 중복을 제거할 수 있음
4. FileRepository, FileService
public interface FileRepository extends JpaRepository<File,Long> {
}
@Service
@Transactional
@RequiredArgsConstructor
public class FileService {
private final FileRepository fileRepository;
public Long saveFile(FileDto fileDto) {
return fileRepository.save(fileDto.toEntity()).getId();
}
public FileDto getFile(Long id) {
File file = fileRepository.findById(id).get();
FileDto fileDto = FileDto.builder()
.id(id)
.origFilename(file.getOrigFilename())
.filename(file.getFilename())
.filePath(file.getFilePath())
.build();
return fileDto;
}
}
5. Controller
@PostMapping("/forum/create")
public String createArticle(ArticleDto articleDto, @RequestParam("file") MultipartFile files) {
try {
// 업로드된 파일의 원본 파일명을 가져옵니다.
String origFilename = files.getOriginalFilename();
// 파일명을 MD5 해싱하여 중복을 피하고 유일한 파일명을 생성합니다.
String filename = new MD5Generator(origFilename).toString();
// 실행되는 위치의 'files' 폴더에 파일이 저장됩니다.
String savePath = System.getProperty("user.dir") + "\\files";
// 파일이 저장되는 폴더가 없으면 폴더를 생성합니다.
if (!new File(savePath).exists()) {
try {
new File(savePath).mkdir();
} catch (Exception e) {
e.getStackTrace();
}
}
// 파일의 저장 경로를 생성합니다.
String filePath = savePath + "\\" + filename;
// MultipartFile의 데이터를 지정된 파일 경로로 복사하여 저장합니다.
files.transferTo(new File(filePath));
// 업로드된 파일 정보를 담는 FileDto 객체를 생성합니다.
FileDto fileDto = new FileDto();
fileDto.setOrigFilename(origFilename);
fileDto.setFilename(filename);
fileDto.setFilePath(filePath);
// 파일 정보를 데이터베이스에 저장하고, 해당 파일의 ID를 받아옵니다.
Long fileId = fileService.saveFile(fileDto);
// ArticleDto에 파일 ID를 설정합니다.
articleDto.setFileId(fileId);
// ArticleDto와 사용자 정보를 이용하여 게시글을 생성합니다.
articleService.createArticle(articleDto, loggedInUser);
} catch (Exception e) {
e.printStackTrace();
}
// 게시판 목록 페이지로 리다이렉트합니다.
return "redirect:/forum";
}
MultipartFile은 클라이언트로부터 업로드된 파일을 처리하기 위해 사용된다.
컨트롤러 메소드 파라미터로 "@RequestParam("file") MultipartFile files" 와 같이 작성하면, 스프링 프레임워크가 클라이언트로부터 업로드된 파일을 해당 파라미터로 주입해준다.
(이 예시에서 HTML의 파일을 업로드하는 input태그의 type은 "file", name은 "file"이어야 함)
MultipartFile 인터페이스는 getOriginalFilename(), isEmpty(), getSize()등의 메소드들을 가지고 있다.
여기까지 했다면, 파일 업로드는 잘 작동될 것이다.
프로그램을 실행시키고 파일을 업로드해보면, 프로젝트파일 내의 files폴더에 업로드한 파일들이 쌓이는것을 볼 수 있다.
다운로드 기능
@Controller
@RequiredArgsConstructor
public class FileController {
private final FileService fileService;
// 파일 다운로드 요청을 처리하는 메소드
@GetMapping("/download/{fileId}")
public ResponseEntity<Resource> fileDownload(@PathVariable("fileId") Long fileId) throws IOException, IOException {
// 요청으로부터 받은 fileId를 이용하여 FileDto를 가져옴
FileDto fileDto = fileService.getFile(fileId);
// FileDto에서 파일 경로를 가져옴
Path path = Paths.get(fileDto.getFilePath());
// 파일을 InputStream으로 읽는 Resource 객체 생성
Resource resource = new InputStreamResource(Files.newInputStream(path));
// ResponseEntity로 파일을 응답으로 보내기 위해 ok() 메소드를 이용하여 ResponseEntity 객체 생성
return ResponseEntity.ok()
// 파일의 MIME 타입을 설정
.contentType(MediaType.parseMediaType("application/octet-stream"))
// 다운로드 시 파일의 원본 이름을 설정
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileDto.getOrigFilename() + "\"")
// 파일의 Resource를 body에 담아 응답으로 반환
.body(resource);
}
}
/download/{fileId} 경로로 Get요청시 다운로드 받을 수 있게 한다.
이미지 표시 기능
@Controller
@RequiredArgsConstructor
public class FileController {
private final FileService fileService;
...
// 이미지 표시 요청을 처리하는 메소드
@GetMapping("/images/{filename}")
public ResponseEntity<Resource> showImage(@PathVariable("filename") String filename) throws IOException {
// 이미지 파일의 경로 생성
Path imagePath = Paths.get(System.getProperty("user.dir") + "\\files\\" + filename);
// Check if the file exists
// 파일이 존재하지 않거나 디렉토리인 경우에는 404 Not Found 응답을 반환
if (!Files.exists(imagePath) || Files.isDirectory(imagePath)) {
// You can return an appropriate error response here if needed
return ResponseEntity.notFound().build();
}
// 이미지 파일을 InputStream으로 읽는 Resource 객체 생성
Resource resource = new InputStreamResource(Files.newInputStream(imagePath));
// ResponseEntity로 이미지를 응답으로 보내기 위해 ok() 메소드를 이용하여 ResponseEntity 객체 생성
return ResponseEntity.ok()
// 이미지의 MIME 타입을 설정 (여기서는 JPEG 이미지로 설정)
.contentType(MediaType.IMAGE_JPEG)
// 응답 헤더에 파일의 원본 이름을 설정
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + filename + "\"")
// 이미지 파일의 Resource를 body에 담아 응답으로 반환
.body(resource);
}
}
/images/{filename} 으로 Get요청시 이미지를 표시한다.
HTML에서 <img src="/images/{filename}"> 처럼 사용할 수 있게된다.
'웹개발 > SpringBoot' 카테고리의 다른 글
네이버 검색 API를 이용하기 (0) | 2023.07.26 |
---|---|
검색 기능 적용하기 (0) | 2023.07.26 |
페이징 기능 적용하기 (0) | 2023.07.26 |
swagger 작성해보기, Springboot swagger 연동하기 (0) | 2023.07.05 |
MySQL 설치 및 Spring 연동 방법 (0) | 2023.06.09 |
댓글