티스토리 뷰

Kotlin & JPA

[Kotlin] CSV 다운로드

Jane Kwon 2023. 5. 9. 11:41
반응형

 

사용자가 보유한 도메인들에 대한 정보들을 CSV 파일로 다운로드 받는 기능을 만들고자 한다.

CSV 파일로 만들 것이기 때문에 build.gradle 파일 dependencies에 아래와 같이 추가해준다.

dependencies {
	implementation("com.opencsv:opencsv:4.4")
}

 

 

 

우선 컨트롤러를 만들고, 파일을 다운로드 할 것이니 때문에 따로 응답을 주는 것은 없다.

@RestController
@RequestMapping("/domains")
class Controller(
	private val service: Service
) {

	@GetMapping("/{userId}/csv")
	fun download(
		@PathVariable userId: String
	): ResponseEntity<Unit> {
		service.download(userId)
		return ResponseEntity
			.noContent()
			.build()
	}

}

 

 

 

서비스에서는 우선 도메인들에 대한 정보를 불러오고

파일 이름에 날짜를 표기할 것이기 때문에 UTC 형식으로 날짜 데이터를 가져왔다.

 

그런 다음에 Array로 첫 줄에는 컬럼명을 넣어주고,

그 뒤로 도메인들의 정보를 forEach를 이용해서 넣어주었다.

import com.opencsv.CSVWriter
import org.springframework.core.io.InputStreamResource
import java.io.*
import java.net.InetSocketAddress
import java.text.SimpleDateFormat
import java.util.*

@Service
class Service(
	private val repository: Repository
) {

	fun download(userId: String) {
		val list = repository.findAllById(userId)

		val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
		format.timeZone = TimeZone.getTimeZone("UTC")

		val data = mutableListOf<Array<String>>().apply {
			add(arrayOf(
				"Domain", "Nameserver1", "Nameserver2", "Nameserver3", "Nameserver4",
				"Nameserver5", "Registration Date", "Expiration Date"))
		}

		list?.forEach {
			data.add(arrayOf(it.domain, it.nameserver1, it.nameserver2, it.nameserver3,
				it.nameserver4, it.nameserver5, it.createdAt, it.expiredAt))
		}

		val date = Date()
		val new = format.format(date)

		try {
			FileWriter(File(".Domains-$new.csv")).use { fw ->
				CSVWriter(fw).use {
					it.writeAll(data)
				}
			}
		} catch (e: Exception) {
			e.printStackTrace()
		}
	}

}

우선 제대로 파일이 생성되는지 보기 위해

FileWriter를 이용해 CSV 파일을 생성해보았다.

여기서 핵심은 CSVWrite에 있다.

 

CSVWrite를 이용해서 파일을 생성해주면 CSV 파일 형식으로 떨굴 수 있다.

 

 

 

 

 

그런데 여기서 이제 일반 웹사이트를 통해 다운로드를 하면

기본 다운로드 폴더로 다운되도록 구현하고자 한다.

그럴 땐 이제 HttpServletResponse를 통해 가능하다.

 

컨트롤러에서 HttpServletResponse를 받아서

@RestController
@RequestMapping("/domains")
class Controller(
	private val service: Service
) {

	@GetMapping("/{userId}/csv")
	fun download(
		@PathVariable userId: String,
		response: HttpServletResponse
	): ResponseEntity<Unit> {
		service.download(response, userId)
		return ResponseEntity
			.noContent()
			.build()
	}

}

아래처럼 Header에 설정 후 output 해주면 된다. 

  • response.contentType = "text/csv;charset=utf-8"
  • response.setHeader("Content-Disposition", "attachment;filename=Domains-$new.csv")
  • response.setHeader("Content-Transfer-Encoding", "binary")
import com.opencsv.CSVWriter
import org.springframework.core.io.InputStreamResource
import java.io.*
import java.net.InetSocketAddress
import java.text.SimpleDateFormat
import java.util.*

@Service
class Service(
	private val repository: Repository
) {

	fun download(response: HttpServletResponse, userId: String) {
		val list = repository.findAllById(userId)

		val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
		format.timeZone = TimeZone.getTimeZone("UTC")

		val data = mutableListOf<Array<String>>().apply {
			add(arrayOf(
				"Domain", "Nameserver1", "Nameserver2", "Nameserver3", "Nameserver4",
				"Nameserver5", "Registration Date", "Expiration Date"))
		}

		list?.forEach {
			data.add(arrayOf(it.domain, it.nameserver1, it.nameserver2, it.nameserver3,
				it.nameserver4, it.nameserver5, it.createdAt, it.expiredAt))
		}

		val date = Date()
		val new = format.format(date)

		response.contentType = "text/csv;charset=utf-8"
		response.setHeader("Content-Disposition", "attachment;filename=Domains-$new.csv")
		response.setHeader("Content-Transfer-Encoding", "binary")

		var outputStream: OutputStream = response.outputStream

		try {
			outputStream = BufferedOutputStream(outputStream)
            
			val outputStreamWriter = OutputStreamWriter(outputStream, "utf-8")
            
			val csvWriter = CSVWriter(outputStreamWriter)
			csvWriter.writeAll(data)
            
			outputStreamWriter.flush()
		} catch (e: Exception) {
			e.printStackTrace()
		} finally {
			outputStream.flush()
			outputStream.close()
		}
	}

}

 

 

 

 

 

그런데 문제가 생겼다.

CSV 파일을 엑셀에서 열면 한글이 깨진다.

 

utf-8로도, euc-kr로도 해봤는데 엑셀이 제대로 열리면 CSV 파일로 열었을 때 깨지고,

CSV 파일로 멀쩡하면 엑셀에서 깨지는 현상이 발생했다.

 

열심히 방법을 찾아도 며칠 동안 해결이 안됐었는데 겨우 찾은 정보로,

문서를 UTF-8-BOM 형식으로 저장하면 정상적으로 열 수 있다는 글을 발견했다.

 

파일 처음에 "\uFEFF"를 추가해주면 해당 파일이 UTF-8-BOM 형식으로 저장된다고 한다.

 

 

 

 

 

그렇게 최종적으로 작업된 내용은 아래와 같다.

import com.opencsv.CSVWriter
import org.springframework.core.io.InputStreamResource
import java.io.*
import java.net.InetSocketAddress
import java.text.SimpleDateFormat
import java.util.*

@Service
class Service(
	private val repository: Repository
) {

	fun download(domain: String) {
		val list = repository.findAllById(domain)

		val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
		format.timeZone = TimeZone.getTimeZone("UTC")

		val data = mutableListOf<Array<String>>().apply {
			add(arrayOf(
				"Domain", "Nameserver1", "Nameserver2", "Nameserver3", "Nameserver4",
				"Nameserver5", "Registration Date", "Expiration Date"))
		}

		list?.forEach {
			data.add(arrayOf(it.domain, it.nameserver1, it.nameserver2, it.nameserver3,
				it.nameserver4, it.nameserver5, it.createdAt, it.expiredAt))
		}

		val date = Date()
		val new = format.format(date)

		response.contentType = "text/csv;charset=utf-8"
		response.setHeader("Content-Disposition", "attachment;filename=Domains-$new.csv")
		response.setHeader("Content-Transfer-Encoding", "binary")

		var outputStream: OutputStream = response.outputStream

		try {
			outputStream = BufferedOutputStream(outputStream)
            
			val outputStreamWriter = OutputStreamWriter(outputStream, "utf-8")
            outputStreamWriter.write("\uFEFF")
            
			val csvWriter = CSVWriter(outputStreamWriter)
			csvWriter.writeAll(data)
            
			outputStreamWriter.flush()
		} catch (e: Exception) {
			e.printStackTrace()
		} finally {
			outputStream.flush()
			outputStream.close()
		}
	}

}

 

 

 

 

 

이제 구현한 API를 프론트단에 붙이고 다운로드하면 이렇게 다운로드 창에 뜨고,

 

해당 파일을 CSV 파일로 열어봐도 한글도 제대로 나오고,

엑셀로도 정상 출력되는 것을 알 수 있다.

(참고 : https://jhdroid.tistory.com/11, https://velog.io/@oyeon/%ED%8C%8C%EC%9D%BC-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-%EA%B5%AC%ED%98%84, https://pygmalion0220.tistory.com/entry/Spring-boot-%ED%8C%8C%EC%9D%BC-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-%EB%8B%A4%EC%9A%B4, https://alakjkj.tistory.com/68)

 

 

 

 

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함