티스토리 뷰

JAVA

[JAVA 1.8] 컨트롤러 파일 다운로드

페루나쵸 2024. 3. 22. 11:57

🩵컨트롤러에서 파일 다운로드 (a 태그로 클릭하여)

배경
A서버에서 API요청하여 B서버에 있는 파일을 읽어서 A서버쪽 UI(Blod 객체 사용)로 byte로 출력하려고 하니 자꾸 인코딩이 제멋대로 되며, 안의 글자들이 깨지게 되어 파일을 사용할 수 없게 됨. A,B가 같은 리눅스 환경이라 그 점을 이용해 A태그를 이용해 UI쪽 컨트롤러에서 바로 다운받을 수 있게끔 구현

+ HTTPS가 아니면 파일이 다운이 안된다고 뜨고 찾아보니 그렇다길래 HTTPS로 변경했는데 개인으로 받은거라 그런가 자꾸 인증서 문제가 생기고 제대로 돌아가지도 않아서 다시 HTTP로 변경 (우리는 총 3개의 서버를 연결해서 그런지 문제가 조금씩 있었다. 두 개의 서버는 HTTPS 괜찮을 것 같다.)  엣지에서는 또 잘 되고 그래서 그냥 HTTP로 위와 같이 구현!  

 

 

내가 원하는 건 a href 태그 클릭 시 바로 다운받는 그런거였는데

내가 검색을 잘 못하는건지 내가 원하는게 잘 나오지 않아서 챗 GPT, 그리고 다른 분들 파일 다운로드 부분을 보며 구현

 

@Controller
@RequestMapping(value = "/api")
public class Controller {

	@Value("${path}")
	private String path;
    
	@RequestMapping(value = "/endpoint", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
	public ResponseEntity<?> download() {

			try {
			// Runtime 객체를 사용하여 쉘 스크립트 실행
			Process process = Runtime.getRuntime().exec(path);

			// 외부 명령어 실행이 완료될 때까지 기다림
			int exitCode = process.waitFor();

			// 외부 명령어 실행이 정상적으로 완료되었는지 확인
			if (exitCode == 0) {
				return ResponseEntity.ok("파일 이동이 성공적으로 수행되었습니다.");
			} else {
				return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
						.body("파일 이동 중 오류가 발생하였습니다.");
			}
		} catch (IOException | InterruptedException e) {
			return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
					.body("파일 이동 중 오류가 발생하였습니다.");
		}
}

 

이건 내가 처음에 생각을 잘 못하고 만든건데 (B서버로 파일을 옮기려고 A서버의 컨트롤러)

같은 리눅스 환경인데도 B서버로 파일을 옮겨야징! 하고 만들었다..

생각해보니 그냥 경로만 넣어줘도 되는데..

저 path는 application.properties 파일에 선언해서 사용하였다 - 스프링 프레임워크 - 

쉘 스크립트 path이며 쉘 스크립트 안에는 cp 명령어를 넣어 두었다.

 

 

-- 실제로 구현하고 사용

 

 

나는 저 부분을 사용하지 않았지만, 혹시 필요한 분들은 스크립트 자바에서 실행하는 방법이니 알고 있으면 좋을 것 같다.

 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.*;

@Controller
@RequestMapping(value = "/api")
public class FileDownloadController {

	@Autowired
	private Service service; 

	@Value("${download.path}")
	private String downloadPath;

	@GetMapping(path = "/download")
	public ResponseEntity<byte[]> fileDownload() {

		// 가장 최근에 생성된 파일을 다운받기 위해 넣어둠
		String fileName = service.filePath();
        
        // 파일 이름이 비어있는 경우 not_found
		if (fileName.isEmpty()) {
			return new ResponseEntity<>(HttpStatus.NOT_FOUND);
		}
		
        // properties 파일에 넣어둔 path 와 서비스에서 가져온 Name 합침
		String filePath = downloadPath + fileName;
		File file = new File(filePath);

		// 파일 없거나 읽을 수 없는 경우 error
		if (!file.exists() || !file.canRead()) {
			log.error("File not found or not readable: " + filePath);
			return new ResponseEntity<>(HttpStatus.NOT_FOUND);
		}

		// byte 단위로 읽어옴 / 주어진 파일의 내용을 읽어와서 byte 배열에 저장
		try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
			byte[] fileContent = FileCopyUtils.copyToByteArray(is);

			HttpHeaders headers = new HttpHeaders();
            // 이건 여기서는 잘 붙여서 가는 것 같은데
            // UI쪽에서 Blod 객체로 하게 되면 header에 붙질 않더라 - cors정책때문인가?
			headers.setContentDispositionFormData("attachment", fileName);
			headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
			headers.setContentLength(file.length());

			return ResponseEntity.ok().headers(headers).body(fileContent);
		} catch (IOException e) {
			log.error("Error reading file: " + filePath, e);
			return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
		}
	}


}

 

 

mport org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

@Service
public class Service {

	@Value("${download.path}")
	private String downloadPath;

	public String FilePath() {

		// 디렉토리 내의 파일 리스트를 가져오기
		File directory = new File(downloadPath);
		File[] files = directory.listFiles();

		// 파일 리스트가 없거나 디렉토리가 아닌 경우 빈 문자열 반환
		if (files == null || files.length == 0) {
			return "";
		}

		// 우리는 파일이 하나가 아니라 두 개씩 생성되었는데
        // 하나는 생성날짜, 하나는 문자열로 생성이 되어 문자열 파일은 제외하기 위함
		List<File> validFiles = new ArrayList<>();

		for (File file : files) {
			if (!file.getName().equals("문자열")) {
				validFiles.add(file);
			}
		}

		// 파일 리스트를 최신 수정일자순으로 정렬
		validFiles.sort(Comparator.comparingLong(File::lastModified).reversed());

		// 가장 첫 번째 파일(가장 최신 파일)의 파일명 반환
		return validFiles.get(0).getName();

	}
}

 

<a download="filename" href="`${pageContext.request.contextPath}/api/download`" >
다운로드 </a>

 

 

컨트롤러와 서비스를 적절한 이름과 적절한 path를 넣어 구현하면 된다 + a 태그사용해서 저런식으로 구현하면

클릭하면 컨트롤러를 타고 로직이 실행이 되며 이쁘게 다운이 된다. 

<a href="${pageContext.request.contextPath}/api/download">

이런식으로 사용하는 것 같기도 하고 우리는 일반 jsp가 아니라 위처럼 구현했는데

맞게 조금 수정하여 구현하면 된다.

 

* pageContext.request.contextPath 웹에서 상대적인 경로를 나타냄 *

 

'JAVA' 카테고리의 다른 글

[java] UUID(Universally Unique Identifier)  (0) 2023.11.14
Java 객체 (mapper.convertValue)  (0) 2023.10.26