fix(calculate): 관리자 정산 엑셀 다운로드를 스트리밍 방식으로 전환한다

This commit is contained in:
2026-03-05 12:21:57 +09:00
parent 07f8d22024
commit 6ac94174c8
8 changed files with 619 additions and 36 deletions

View File

@@ -2,11 +2,17 @@ package kr.co.vividnext.sodalive.admin.calculate
import kr.co.vividnext.sodalive.common.ApiResponse
import org.springframework.data.domain.Pageable
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
@RestController
@PreAuthorize("hasRole('ADMIN')")
@@ -18,12 +24,30 @@ class AdminCalculateController(private val service: AdminCalculateService) {
@RequestParam endDateStr: String
) = ApiResponse.ok(service.getCalculateLive(startDateStr, endDateStr))
@GetMapping("/live/excel")
fun downloadCalculateLiveExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "live.xlsx",
response = service.downloadCalculateLiveExcel(startDateStr, endDateStr)
)
@GetMapping("/content-list")
fun getCalculateContentList(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
) = ApiResponse.ok(service.getCalculateContentList(startDateStr, endDateStr))
@GetMapping("/content-list/excel")
fun downloadCalculateContentListExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "content-list.xlsx",
response = service.downloadCalculateContentListExcel(startDateStr, endDateStr)
)
@GetMapping("/cumulative-sales-by-content")
fun getCumulativeSalesByContent(pageable: Pageable) = ApiResponse.ok(
service.getCumulativeSalesByContent(pageable.offset, pageable.pageSize.toLong())
@@ -35,6 +59,15 @@ class AdminCalculateController(private val service: AdminCalculateService) {
@RequestParam endDateStr: String
) = ApiResponse.ok(service.getCalculateContentDonationList(startDateStr, endDateStr))
@GetMapping("/content-donation-list/excel")
fun downloadCalculateContentDonationListExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "content-donation-list.xlsx",
response = service.downloadCalculateContentDonationListExcel(startDateStr, endDateStr)
)
@GetMapping("/community-post")
fun getCalculateCommunityPost(
@RequestParam startDateStr: String,
@@ -49,6 +82,15 @@ class AdminCalculateController(private val service: AdminCalculateService) {
)
)
@GetMapping("/community-post/excel")
fun downloadCalculateCommunityPostExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "community-post.xlsx",
response = service.downloadCalculateCommunityPostExcel(startDateStr, endDateStr)
)
@GetMapping("/live-by-creator")
fun getCalculateLiveByCreator(
@RequestParam startDateStr: String,
@@ -63,6 +105,15 @@ class AdminCalculateController(private val service: AdminCalculateService) {
)
)
@GetMapping("/live-by-creator/excel")
fun downloadCalculateLiveByCreatorExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "live-by-creator.xlsx",
response = service.downloadCalculateLiveByCreatorExcel(startDateStr, endDateStr)
)
@GetMapping("/content-by-creator")
fun getCalculateContentByCreator(
@RequestParam startDateStr: String,
@@ -77,6 +128,15 @@ class AdminCalculateController(private val service: AdminCalculateService) {
)
)
@GetMapping("/content-by-creator/excel")
fun downloadCalculateContentByCreatorExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "content-by-creator.xlsx",
response = service.downloadCalculateContentByCreatorExcel(startDateStr, endDateStr)
)
@GetMapping("/community-by-creator")
fun getCalculateCommunityByCreator(
@RequestParam startDateStr: String,
@@ -90,4 +150,28 @@ class AdminCalculateController(private val service: AdminCalculateService) {
pageable.pageSize.toLong()
)
)
@GetMapping("/community-by-creator/excel")
fun downloadCalculateCommunityByCreatorExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "community-by-creator.xlsx",
response = service.downloadCalculateCommunityByCreatorExcel(startDateStr, endDateStr)
)
private fun createExcelResponse(
fileName: String,
response: StreamingResponseBody
): ResponseEntity<StreamingResponseBody> {
val encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replace("+", "%20")
val headers = HttpHeaders().apply {
add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFileName")
}
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(response)
}
}

View File

@@ -2,9 +2,13 @@ package kr.co.vividnext.sodalive.admin.calculate
import kr.co.vividnext.sodalive.creator.admin.calculate.GetCreatorCalculateCommunityPostResponse
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.xssf.streaming.SXSSFWorkbook
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
import java.time.LocalDateTime
@Service
class AdminCalculateService(private val repository: AdminCalculateQueryRepository) {
@@ -139,4 +143,286 @@ class AdminCalculateService(private val repository: AdminCalculateQueryRepositor
GetCalculateByCreatorResponse(totalCount, items)
}
@Transactional(readOnly = true)
fun downloadCalculateLiveExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val items = repository
.getCalculateLive(startDate, endDate)
.map { it.toGetCalculateLiveResponse() }
return createExcelStream(
sheetName = "라이브 정산",
headers = listOf(
"이메일",
"닉네임",
"날짜",
"라이브 제목",
"입장료(캔)",
"사용구분",
"참여인원",
"총 캔",
"원화",
"결제수수료",
"정산금액",
"원천세",
"입금액"
)
) { sheet ->
items.forEachIndexed { index, item ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(item.email)
row.createCell(1).setCellValue(item.nickname)
row.createCell(2).setCellValue(item.date)
row.createCell(3).setCellValue(item.title)
row.createCell(4).setCellValue(item.entranceFee.toDouble())
row.createCell(5).setCellValue(item.canUsageStr)
row.createCell(6).setCellValue(item.numberOfPeople.toDouble())
row.createCell(7).setCellValue(item.totalAmount.toDouble())
row.createCell(8).setCellValue(item.totalKrw.toDouble())
row.createCell(9).setCellValue(item.paymentFee.toDouble())
row.createCell(10).setCellValue(item.settlementAmount.toDouble())
row.createCell(11).setCellValue(item.tax.toDouble())
row.createCell(12).setCellValue(item.depositAmount.toDouble())
}
}
}
@Transactional(readOnly = true)
fun downloadCalculateContentListExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val items = repository
.getCalculateContentList(startDate, endDate)
.map { it.toGetCalculateContentResponse() }
return createExcelStream(
sheetName = "콘텐츠 정산",
headers = listOf(
"크리에이터",
"콘텐츠 제목",
"등록일",
"판매일",
"구분",
"가격(캔)",
"인원",
"총 캔",
"원화",
"결제수수료",
"정산금액",
"원천세",
"입금액"
)
) { sheet ->
items.forEachIndexed { index, item ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(item.nickname)
row.createCell(1).setCellValue(item.title)
row.createCell(2).setCellValue(item.registrationDate)
row.createCell(3).setCellValue(item.saleDate)
row.createCell(4).setCellValue(item.orderType)
row.createCell(5).setCellValue(item.orderPrice.toDouble())
row.createCell(6).setCellValue(item.numberOfPeople.toDouble())
row.createCell(7).setCellValue(item.totalCan.toDouble())
row.createCell(8).setCellValue(item.totalKrw.toDouble())
row.createCell(9).setCellValue(item.paymentFee.toDouble())
row.createCell(10).setCellValue(item.settlementAmount.toDouble())
row.createCell(11).setCellValue(item.tax.toDouble())
row.createCell(12).setCellValue(item.depositAmount.toDouble())
}
}
}
@Transactional(readOnly = true)
fun downloadCalculateContentDonationListExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val items = repository
.getCalculateContentDonationList(startDate, endDate)
.map { it.toGetCalculateContentDonationResponse() }
return createExcelStream(
sheetName = "콘텐츠 후원 정산",
headers = listOf(
"크리에이터",
"콘텐츠 제목",
"유무료",
"등록일",
"후원일",
"후원건수",
"총 캔",
"원화",
"결제수수료",
"정산금액",
"원천세",
"입금액"
)
) { sheet ->
items.forEachIndexed { index, item ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(item.nickname)
row.createCell(1).setCellValue(item.title)
row.createCell(2).setCellValue(item.paidOrFree)
row.createCell(3).setCellValue(item.registrationDate)
row.createCell(4).setCellValue(item.donationDate)
row.createCell(5).setCellValue(item.numberOfDonation.toDouble())
row.createCell(6).setCellValue(item.totalCan.toDouble())
row.createCell(7).setCellValue(item.totalKrw.toDouble())
row.createCell(8).setCellValue(item.paymentFee.toDouble())
row.createCell(9).setCellValue(item.settlementAmount.toDouble())
row.createCell(10).setCellValue(item.tax.toDouble())
row.createCell(11).setCellValue(item.depositAmount.toDouble())
}
}
}
@Transactional(readOnly = true)
fun downloadCalculateCommunityPostExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val totalCount = repository.getCalculateCommunityPostTotalCount(startDate, endDate)
val items = if (totalCount == 0) {
emptyList()
} else {
repository
.getCalculateCommunityPostList(startDate, endDate, 0L, totalCount.toLong())
.map { it.toGetCalculateCommunityPostResponse() }
}
return createExcelStream(
sheetName = "커뮤니티 정산",
headers = listOf(
"크리에이터",
"게시글",
"날짜",
"가격(캔)",
"구매건수",
"총 캔",
"원화",
"결제수수료",
"정산금액",
"원천세",
"입금액"
)
) { sheet ->
items.forEachIndexed { index, item ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(item.nickname)
row.createCell(1).setCellValue(item.title)
row.createCell(2).setCellValue(item.date)
row.createCell(3).setCellValue(item.can.toDouble())
row.createCell(4).setCellValue(item.numberOfPurchase.toDouble())
row.createCell(5).setCellValue(item.totalCan.toDouble())
row.createCell(6).setCellValue(item.totalKrw.toDouble())
row.createCell(7).setCellValue(item.paymentFee.toDouble())
row.createCell(8).setCellValue(item.settlementAmount.toDouble())
row.createCell(9).setCellValue(item.tax.toDouble())
row.createCell(10).setCellValue(item.depositAmount.toDouble())
}
}
}
@Transactional(readOnly = true)
fun downloadCalculateLiveByCreatorExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val totalCount = repository.getCalculateLiveByCreatorTotalCount(startDate, endDate)
val items = if (totalCount == 0) {
emptyList()
} else {
repository
.getCalculateLiveByCreator(startDate, endDate, 0L, totalCount.toLong())
.map { it.toGetCalculateByCreator() }
}
return createCalculateByCreatorExcel("크리에이터별 라이브 정산", items)
}
@Transactional(readOnly = true)
fun downloadCalculateContentByCreatorExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val totalCount = repository.getCalculateContentByCreatorTotalCount(startDate, endDate)
val items = if (totalCount == 0) {
emptyList()
} else {
repository
.getCalculateContentByCreator(startDate, endDate, 0L, totalCount.toLong())
.map { it.toGetCalculateByCreator() }
}
return createCalculateByCreatorExcel("크리에이터별 콘텐츠 정산", items)
}
@Transactional(readOnly = true)
fun downloadCalculateCommunityByCreatorExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val totalCount = repository.getCalculateCommunityByCreatorTotalCount(startDate, endDate)
val items = if (totalCount == 0) {
emptyList()
} else {
repository
.getCalculateCommunityByCreator(startDate, endDate, 0L, totalCount.toLong())
.map { it.toGetCalculateByCreator() }
}
return createCalculateByCreatorExcel("크리에이터별 커뮤니티 정산", items)
}
private fun createCalculateByCreatorExcel(
sheetName: String,
items: List<GetCalculateByCreatorItem>
): StreamingResponseBody {
return createExcelStream(
sheetName = sheetName,
headers = listOf(
"이메일",
"닉네임",
"총 캔",
"원화",
"결제수수료",
"정산금액",
"원천세",
"입금액"
)
) { sheet ->
items.forEachIndexed { index, item ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(item.email)
row.createCell(1).setCellValue(item.nickname)
row.createCell(2).setCellValue(item.totalCan.toDouble())
row.createCell(3).setCellValue(item.totalKrw.toDouble())
row.createCell(4).setCellValue(item.paymentFee.toDouble())
row.createCell(5).setCellValue(item.settlementAmount.toDouble())
row.createCell(6).setCellValue(item.tax.toDouble())
row.createCell(7).setCellValue(item.depositAmount.toDouble())
}
}
}
private fun createExcelStream(
sheetName: String,
headers: List<String>,
writeRows: (Sheet) -> Unit
): StreamingResponseBody {
return StreamingResponseBody { outputStream ->
val workbook = SXSSFWorkbook(100)
try {
val sheet = workbook.createSheet(sheetName)
val headerRow = sheet.createRow(0)
headers.forEachIndexed { index, value ->
headerRow.createCell(index).setCellValue(value)
}
writeRows(sheet)
workbook.write(outputStream)
outputStream.flush()
} finally {
workbook.dispose()
workbook.close()
}
}
}
private fun toDateRange(startDateStr: String, endDateStr: String): Pair<LocalDateTime, LocalDateTime> {
val startDate = startDateStr.convertLocalDateTime()
val endDate = endDateStr.convertLocalDateTime(hour = 23, minute = 59, second = 59)
return startDate to endDate
}
}

View File

@@ -1,7 +1,6 @@
package kr.co.vividnext.sodalive.admin.calculate.channelDonation
import kr.co.vividnext.sodalive.common.ApiResponse
import org.springframework.core.io.InputStreamResource
import org.springframework.data.domain.Pageable
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
@@ -11,6 +10,7 @@ import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
@@ -34,6 +34,15 @@ class AdminChannelDonationCalculateController(
)
)
@GetMapping("/channel-donation-by-date/excel")
fun downloadChannelDonationByDateExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "channel-donation-by-date.xlsx",
response = service.downloadChannelDonationByDateExcel(startDateStr, endDateStr)
)
@GetMapping("/channel-donation-by-creator")
fun getChannelDonationByCreator(
@RequestParam startDateStr: String,
@@ -52,23 +61,23 @@ class AdminChannelDonationCalculateController(
fun downloadChannelDonationByCreatorExcel(
@RequestParam startDateStr: String,
@RequestParam endDateStr: String
): ResponseEntity<InputStreamResource> {
val encodedFileName = URLEncoder.encode(
"channel-donation-by-creator.xlsx",
StandardCharsets.UTF_8.toString()
).replace("+", "%20")
): ResponseEntity<StreamingResponseBody> = createExcelResponse(
fileName = "channel-donation-by-creator.xlsx",
response = service.downloadChannelDonationByCreatorExcel(startDateStr, endDateStr)
)
private fun createExcelResponse(
fileName: String,
response: StreamingResponseBody
): ResponseEntity<StreamingResponseBody> {
val encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replace("+", "%20")
val headers = HttpHeaders().apply {
add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''$encodedFileName")
}
val response = service.downloadChannelDonationByCreatorExcel(
startDateStr = startDateStr,
endDateStr = endDateStr
)
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
.body(InputStreamResource(response))
.body(response)
}
}

View File

@@ -1,11 +1,12 @@
package kr.co.vividnext.sodalive.admin.calculate.channelDonation
import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.xssf.streaming.SXSSFWorkbook
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
import java.time.LocalDateTime
@Service
class AdminChannelDonationCalculateService(
@@ -50,17 +51,21 @@ class AdminChannelDonationCalculateService(
}
@Transactional(readOnly = true)
fun downloadChannelDonationByCreatorExcel(startDateStr: String, endDateStr: String): ByteArrayInputStream {
val startDate = startDateStr.convertLocalDateTime()
val endDate = endDateStr.convertLocalDateTime(hour = 23, minute = 59, second = 59)
val items = repository
.getChannelDonationByCreatorForExcel(startDate, endDate)
.map { it.toResponseItem() }
val byteArrayOutputStream = ByteArrayOutputStream()
fun downloadChannelDonationByDateExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val totalCount = repository.getChannelDonationByDateTotalCount(startDate, endDate)
val items = if (totalCount == 0) {
emptyList()
} else {
repository
.getChannelDonationByDate(startDate, endDate, 0L, totalCount.toLong())
.map { it.toResponseItem() }
}
XSSFWorkbook().use { workbook ->
val sheet = workbook.createSheet("크리에이터별 채널후원 정산")
val header = listOf(
return createExcelStream(
sheetName = "채널후원 정산",
headers = listOf(
"날짜",
"크리에이터",
"건수",
"총 받은 캔 수",
@@ -70,11 +75,42 @@ class AdminChannelDonationCalculateService(
"원천세",
"입금액"
)
val headerRow = sheet.createRow(0)
header.forEachIndexed { index, value ->
headerRow.createCell(index).setCellValue(value)
) { sheet ->
items.forEachIndexed { index, item ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(item.date)
row.createCell(1).setCellValue(item.creator)
row.createCell(2).setCellValue(item.count.toDouble())
row.createCell(3).setCellValue(item.totalCan.toDouble())
row.createCell(4).setCellValue(item.krw.toDouble())
row.createCell(5).setCellValue(item.fee.toDouble())
row.createCell(6).setCellValue(item.settlementAmount.toDouble())
row.createCell(7).setCellValue(item.withholdingTax.toDouble())
row.createCell(8).setCellValue(item.depositAmount.toDouble())
}
}
}
@Transactional(readOnly = true)
fun downloadChannelDonationByCreatorExcel(startDateStr: String, endDateStr: String): StreamingResponseBody {
val (startDate, endDate) = toDateRange(startDateStr, endDateStr)
val items = repository
.getChannelDonationByCreatorForExcel(startDate, endDate)
.map { it.toResponseItem() }
return createExcelStream(
sheetName = "크리에이터별 채널후원 정산",
headers = listOf(
"크리에이터",
"건수",
"총 받은 캔 수",
"원화",
"수수료",
"정산금액",
"원천세",
"입금액"
)
) { sheet ->
items.forEachIndexed { index, item ->
val row = sheet.createRow(index + 1)
row.createCell(0).setCellValue(item.creator)
@@ -86,10 +122,37 @@ class AdminChannelDonationCalculateService(
row.createCell(6).setCellValue(item.withholdingTax.toDouble())
row.createCell(7).setCellValue(item.depositAmount.toDouble())
}
workbook.write(byteArrayOutputStream)
}
}
return ByteArrayInputStream(byteArrayOutputStream.toByteArray())
private fun createExcelStream(
sheetName: String,
headers: List<String>,
writeRows: (Sheet) -> Unit
): StreamingResponseBody {
return StreamingResponseBody { outputStream ->
val workbook = SXSSFWorkbook(100)
try {
val sheet = workbook.createSheet(sheetName)
val headerRow = sheet.createRow(0)
headers.forEachIndexed { index, value ->
headerRow.createCell(index).setCellValue(value)
}
writeRows(sheet)
workbook.write(outputStream)
outputStream.flush()
} finally {
workbook.dispose()
workbook.close()
}
}
}
private fun toDateRange(startDateStr: String, endDateStr: String): Pair<LocalDateTime, LocalDateTime> {
val startDate = startDateStr.convertLocalDateTime()
val endDate = endDateStr.convertLocalDateTime(hour = 23, minute = 59, second = 59)
return startDate to endDate
}
}

View File

@@ -6,10 +6,9 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.springframework.core.io.InputStreamResource
import org.springframework.data.domain.PageRequest
import org.springframework.http.HttpHeaders
import java.io.ByteArrayInputStream
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
class AdminChannelDonationCalculateControllerTest {
private lateinit var service: AdminChannelDonationCalculateService
@@ -135,6 +134,38 @@ class AdminChannelDonationCalculateControllerTest {
)
}
@Test
@DisplayName("관리자 컨트롤러는 날짜별 정산 엑셀을 다운로드한다")
fun shouldDownloadDateSettlementExcel() {
Mockito.`when`(
service.downloadChannelDonationByDateExcel(
startDateStr = "2026-02-01",
endDateStr = "2026-02-29"
)
).thenReturn(StreamingResponseBody { outputStream -> outputStream.write(byteArrayOf(1, 2, 3)) })
val response = controller.downloadChannelDonationByDateExcel(
startDateStr = "2026-02-01",
endDateStr = "2026-02-29"
)
assertEquals(200, response.statusCode.value())
val contentDispositionHeader = response.headers.getFirst(HttpHeaders.CONTENT_DISPOSITION)
assertNotNull(contentDispositionHeader)
assertEquals(true, contentDispositionHeader?.contains("attachment; filename*="))
assertEquals(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
response.headers.contentType.toString()
)
assertNotNull(response.body)
assertEquals(true, response.body is StreamingResponseBody)
Mockito.verify(service).downloadChannelDonationByDateExcel(
startDateStr = "2026-02-01",
endDateStr = "2026-02-29"
)
}
@Test
@DisplayName("관리자 컨트롤러는 크리에이터별 정산 엑셀을 다운로드한다")
fun shouldDownloadCreatorSettlementExcel() {
@@ -143,7 +174,7 @@ class AdminChannelDonationCalculateControllerTest {
startDateStr = "2026-02-01",
endDateStr = "2026-02-29"
)
).thenReturn(ByteArrayInputStream(byteArrayOf(1, 2, 3)))
).thenReturn(StreamingResponseBody { outputStream -> outputStream.write(byteArrayOf(1, 2, 3)) })
val response = controller.downloadChannelDonationByCreatorExcel(
startDateStr = "2026-02-01",
@@ -159,7 +190,7 @@ class AdminChannelDonationCalculateControllerTest {
response.headers.contentType.toString()
)
assertNotNull(response.body)
assertEquals(true, response.body is InputStreamResource)
assertEquals(true, response.body is StreamingResponseBody)
Mockito.verify(service).downloadChannelDonationByCreatorExcel(
startDateStr = "2026-02-01",

View File

@@ -7,6 +7,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.mockito.Mockito
import java.io.ByteArrayOutputStream
class AdminChannelDonationCalculateServiceTest {
private lateinit var repository: AdminChannelDonationCalculateQueryRepository
@@ -153,6 +154,54 @@ class AdminChannelDonationCalculateServiceTest {
)
}
@Test
@DisplayName("관리자 날짜별 정산 엑셀 다운로드는 xlsx 바이트를 생성한다")
fun shouldGenerateDateSettlementExcelBytes() {
Mockito.`when`(
repository.getChannelDonationByDateTotalCount(
"2026-02-20".convertLocalDateTime(),
"2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59)
)
).thenReturn(1)
Mockito.`when`(
repository.getChannelDonationByDate(
"2026-02-20".convertLocalDateTime(),
"2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
0L,
1L
)
).thenReturn(
listOf(
GetAdminChannelDonationSettlementQueryData(
date = "2026-02-20",
creator = "creator-a",
count = 3L,
totalCan = 100
)
)
)
val response = service.downloadChannelDonationByDateExcel(
startDateStr = "2026-02-20",
endDateStr = "2026-02-21"
)
val outputStream = ByteArrayOutputStream()
response.writeTo(outputStream)
assertTrue(outputStream.toByteArray().isNotEmpty())
Mockito.verify(repository).getChannelDonationByDateTotalCount(
"2026-02-20".convertLocalDateTime(),
"2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59)
)
Mockito.verify(repository).getChannelDonationByDate(
"2026-02-20".convertLocalDateTime(),
"2026-02-21".convertLocalDateTime(hour = 23, minute = 59, second = 59),
0L,
1L
)
}
@Test
@DisplayName("관리자 크리에이터별 정산 엑셀 다운로드는 xlsx 바이트를 생성한다")
fun shouldGenerateCreatorSettlementExcelBytes() {
@@ -175,8 +224,10 @@ class AdminChannelDonationCalculateServiceTest {
startDateStr = "2026-02-20",
endDateStr = "2026-02-21"
)
val outputStream = ByteArrayOutputStream()
response.writeTo(outputStream)
assertTrue(response.readAllBytes().isNotEmpty())
assertTrue(outputStream.toByteArray().isNotEmpty())
Mockito.verify(repository).getChannelDonationByCreatorForExcel(
"2026-02-20".convertLocalDateTime(),