excel download 구현
This commit is contained in:
@@ -407,6 +407,124 @@
|
|||||||
fetchChartData();
|
fetchChartData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportToExcel() {
|
||||||
|
console.log('[Excel Export] Starting...');
|
||||||
|
|
||||||
|
var postData = {
|
||||||
|
cmd: 'EXCEL_EXPORT',
|
||||||
|
searchApiName: $("input[name=searchApiName]").val(),
|
||||||
|
searchGwInstanceId: $("input[name=searchGwInstanceId]").val(),
|
||||||
|
searchBizDivCode: $("input[name=searchBizDivCode]").val(),
|
||||||
|
searchClientId: $("input[name=searchClientId]").val(),
|
||||||
|
searchInboundAdapter: $("input[name=searchInboundAdapter]").val(),
|
||||||
|
searchOutboundAdapter: $("input[name=searchOutboundAdapter]").val(),
|
||||||
|
serviceType: '${param.serviceType}'
|
||||||
|
};
|
||||||
|
|
||||||
|
var searchDate = $("input[name=searchDate]").val().replace(/-/g, "");
|
||||||
|
if (searchDate) {
|
||||||
|
postData.searchStartDateTime = searchDate + "00";
|
||||||
|
postData.searchEndDateTime = searchDate + "23";
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Excel Export] Request data:', postData);
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.responseType = 'blob';
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
|
||||||
|
|
||||||
|
xhr.onload = function() {
|
||||||
|
console.log('[Excel Export] Response received - Status:', xhr.status);
|
||||||
|
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
var blob = xhr.response;
|
||||||
|
var contentType = xhr.getResponseHeader('Content-Type');
|
||||||
|
console.log('[Excel Export] Blob size:', blob.size, 'Content-Type:', contentType);
|
||||||
|
|
||||||
|
// JSON 에러 응답인지 확인
|
||||||
|
if (contentType && contentType.indexOf('application/json') !== -1) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function() {
|
||||||
|
try {
|
||||||
|
var errorObj = JSON.parse(reader.result);
|
||||||
|
alert(errorObj.message || 'Excel 파일 생성에 실패했습니다.');
|
||||||
|
} catch (e) {
|
||||||
|
alert('Excel 파일 생성에 실패했습니다.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(blob);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blob 크기가 0이면 에러
|
||||||
|
if (blob.size === 0) {
|
||||||
|
alert('Excel 파일 생성에 실패했습니다. (빈 응답)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 파일명 추출
|
||||||
|
var filename = 'api_stats.xlsx';
|
||||||
|
var disposition = xhr.getResponseHeader('Content-Disposition');
|
||||||
|
console.log('[Excel Export] Content-Disposition:', disposition);
|
||||||
|
|
||||||
|
if (disposition && disposition.indexOf('filename=') !== -1) {
|
||||||
|
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
|
||||||
|
var matches = filenameRegex.exec(disposition);
|
||||||
|
if (matches != null && matches[1]) {
|
||||||
|
filename = matches[1].replace(/['"]/g, '');
|
||||||
|
filename = decodeURIComponent(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Excel Export] Downloading as:', filename);
|
||||||
|
|
||||||
|
// 파일 다운로드
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(link.href);
|
||||||
|
|
||||||
|
console.log('[Excel Export] Download triggered');
|
||||||
|
|
||||||
|
} else if (xhr.status === 204) {
|
||||||
|
alert('조회된 데이터가 없습니다.');
|
||||||
|
} else {
|
||||||
|
// 에러 응답 처리
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function() {
|
||||||
|
var message = 'Excel 다운로드 중 오류가 발생했습니다.';
|
||||||
|
try {
|
||||||
|
var errorObj = JSON.parse(reader.result);
|
||||||
|
if (errorObj.message) message = errorObj.message;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[Excel Export] Error parsing error response:', e);
|
||||||
|
}
|
||||||
|
alert(message);
|
||||||
|
};
|
||||||
|
reader.readAsText(xhr.response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function() {
|
||||||
|
console.error('[Excel Export] Network error');
|
||||||
|
alert('네트워크 오류가 발생했습니다.');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Form data 생성
|
||||||
|
var formData = [];
|
||||||
|
for (var key in postData) {
|
||||||
|
if (postData.hasOwnProperty(key) && postData[key] != null && postData[key] !== '') {
|
||||||
|
formData.push(encodeURIComponent(key) + '=' + encodeURIComponent(postData[key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send(formData.join('&'));
|
||||||
|
}
|
||||||
|
|
||||||
function list() {
|
function list() {
|
||||||
var gridPostData = getSearchForJqgrid("cmd", "LIST");
|
var gridPostData = getSearchForJqgrid("cmd", "LIST");
|
||||||
$('#grid').jqGrid({
|
$('#grid').jqGrid({
|
||||||
@@ -480,6 +598,10 @@
|
|||||||
search();
|
search();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#btn_excel_export").click(function() {
|
||||||
|
exportToExcel();
|
||||||
|
});
|
||||||
|
|
||||||
$("input[name^=search]").keydown(function(key) {
|
$("input[name^=search]").keydown(function(key) {
|
||||||
if (key.keyCode == 13) {
|
if (key.keyCode == 13) {
|
||||||
$("#btn_search").click();
|
$("#btn_search").click();
|
||||||
@@ -510,6 +632,9 @@
|
|||||||
<button type="button" class="cssbtn" id="btn_search" level="R">
|
<button type="button" class="cssbtn" id="btn_search" level="R">
|
||||||
<i class="material-icons">search</i> <%= localeMessage.getString("button.search") %>
|
<i class="material-icons">search</i> <%= localeMessage.getString("button.search") %>
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="cssbtn" id="btn_excel_export" level="R">
|
||||||
|
<i class="material-icons">file_download</i> Excel 다운로드
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" id="title">API 시간별 통계</div>
|
<div class="title" id="title">API 시간별 통계</div>
|
||||||
<table class="search_condition" cellspacing="0">
|
<table class="search_condition" cellspacing="0">
|
||||||
|
|||||||
@@ -415,6 +415,127 @@
|
|||||||
fetchChartData(range);
|
fetchChartData(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportToExcel() {
|
||||||
|
console.log('[Excel Export] Starting...');
|
||||||
|
|
||||||
|
var postData = {
|
||||||
|
cmd: 'EXCEL_EXPORT',
|
||||||
|
searchApiName: $("input[name=searchApiName]").val(),
|
||||||
|
searchGwInstanceId: $("input[name=searchGwInstanceId]").val(),
|
||||||
|
searchBizDivCode: $("input[name=searchBizDivCode]").val(),
|
||||||
|
searchClientId: $("input[name=searchClientId]").val(),
|
||||||
|
searchInboundAdapter: $("input[name=searchInboundAdapter]").val(),
|
||||||
|
searchOutboundAdapter: $("input[name=searchOutboundAdapter]").val(),
|
||||||
|
serviceType: '${param.serviceType}'
|
||||||
|
};
|
||||||
|
|
||||||
|
var startDate = $("input[name=searchStartDate]").val().replace(/-/g, "");
|
||||||
|
var startTime = $("input[name=searchStartTime]").val().replace(/:/g, "");
|
||||||
|
var endDate = $("input[name=searchEndDate]").val().replace(/-/g, "");
|
||||||
|
var endTime = $("input[name=searchEndTime]").val().replace(/:/g, "");
|
||||||
|
if (startDate && startTime) {
|
||||||
|
postData.searchStartDateTime = startDate + startTime;
|
||||||
|
postData.searchEndDateTime = endDate + endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Excel Export] Request data:', postData);
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.responseType = 'blob';
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
|
||||||
|
|
||||||
|
xhr.onload = function() {
|
||||||
|
console.log('[Excel Export] Response received - Status:', xhr.status);
|
||||||
|
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
var blob = xhr.response;
|
||||||
|
var contentType = xhr.getResponseHeader('Content-Type');
|
||||||
|
console.log('[Excel Export] Blob size:', blob.size, 'Content-Type:', contentType);
|
||||||
|
|
||||||
|
// JSON 에러 응답인지 확인
|
||||||
|
if (contentType && contentType.indexOf('application/json') !== -1) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function() {
|
||||||
|
try {
|
||||||
|
var errorObj = JSON.parse(reader.result);
|
||||||
|
alert(errorObj.message || 'Excel 파일 생성에 실패했습니다.');
|
||||||
|
} catch (e) {
|
||||||
|
alert('Excel 파일 생성에 실패했습니다.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(blob);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blob 크기가 0이면 에러
|
||||||
|
if (blob.size === 0) {
|
||||||
|
alert('Excel 파일 생성에 실패했습니다. (빈 응답)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 파일명 추출
|
||||||
|
var filename = 'api_stats.xlsx';
|
||||||
|
var disposition = xhr.getResponseHeader('Content-Disposition');
|
||||||
|
console.log('[Excel Export] Content-Disposition:', disposition);
|
||||||
|
|
||||||
|
if (disposition && disposition.indexOf('filename=') !== -1) {
|
||||||
|
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
|
||||||
|
var matches = filenameRegex.exec(disposition);
|
||||||
|
if (matches != null && matches[1]) {
|
||||||
|
filename = matches[1].replace(/['"]/g, '');
|
||||||
|
filename = decodeURIComponent(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Excel Export] Downloading as:', filename);
|
||||||
|
|
||||||
|
// 파일 다운로드
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(link.href);
|
||||||
|
|
||||||
|
console.log('[Excel Export] Download triggered');
|
||||||
|
|
||||||
|
} else if (xhr.status === 204) {
|
||||||
|
alert('조회된 데이터가 없습니다.');
|
||||||
|
} else {
|
||||||
|
// 에러 응답 처리
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function() {
|
||||||
|
var message = 'Excel 다운로드 중 오류가 발생했습니다.';
|
||||||
|
try {
|
||||||
|
var errorObj = JSON.parse(reader.result);
|
||||||
|
if (errorObj.message) message = errorObj.message;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[Excel Export] Error parsing error response:', e);
|
||||||
|
}
|
||||||
|
alert(message);
|
||||||
|
};
|
||||||
|
reader.readAsText(xhr.response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function() {
|
||||||
|
console.error('[Excel Export] Network error');
|
||||||
|
alert('네트워크 오류가 발생했습니다.');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Form data 생성
|
||||||
|
var formData = [];
|
||||||
|
for (var key in postData) {
|
||||||
|
if (postData.hasOwnProperty(key) && postData[key] != null && postData[key] !== '') {
|
||||||
|
formData.push(encodeURIComponent(key) + '=' + encodeURIComponent(postData[key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send(formData.join('&'));
|
||||||
|
}
|
||||||
|
|
||||||
function list() {
|
function list() {
|
||||||
var gridPostData = getSearchForJqgrid("cmd", "LIST");
|
var gridPostData = getSearchForJqgrid("cmd", "LIST");
|
||||||
$('#grid').jqGrid({
|
$('#grid').jqGrid({
|
||||||
@@ -514,6 +635,10 @@
|
|||||||
search();
|
search();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#btn_excel_export").click(function() {
|
||||||
|
exportToExcel();
|
||||||
|
});
|
||||||
|
|
||||||
$("input[name^=search]").keydown(function(key) {
|
$("input[name^=search]").keydown(function(key) {
|
||||||
if (key.keyCode == 13) {
|
if (key.keyCode == 13) {
|
||||||
$("#btn_search").click();
|
$("#btn_search").click();
|
||||||
@@ -549,6 +674,9 @@
|
|||||||
<button type="button" class="cssbtn" id="btn_search" level="R">
|
<button type="button" class="cssbtn" id="btn_search" level="R">
|
||||||
<i class="material-icons">search</i> <%= localeMessage.getString("button.search") %>
|
<i class="material-icons">search</i> <%= localeMessage.getString("button.search") %>
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="cssbtn" id="btn_excel_export" level="R">
|
||||||
|
<i class="material-icons">file_download</i> Excel 다운로드
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" id="title">API 분단위 통계</div>
|
<div class="title" id="title">API 분단위 통계</div>
|
||||||
<table class="search_condition" cellspacing="0">
|
<table class="search_condition" cellspacing="0">
|
||||||
|
|||||||
+41
@@ -60,6 +60,47 @@ public class ApiStatsHourService
|
|||||||
return repository.findAll(builder, pageable);
|
return repository.findAll(builder, pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ApiStatsHour> selectListForExcel(ApiStatsSearch search) {
|
||||||
|
QApiStatsHour q = QApiStatsHour.apiStatsHour;
|
||||||
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
|
|
||||||
|
calculateDateRange(search);
|
||||||
|
if (search.getParsedStartDateTime() != null) {
|
||||||
|
builder.and(q.statTime.goe(search.getParsedStartDateTime()));
|
||||||
|
builder.and(q.statTime.loe(search.getParsedEndDateTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchApiName())) {
|
||||||
|
builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
|
||||||
|
builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
|
||||||
|
builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchClientId())) {
|
||||||
|
builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchInboundAdapter())) {
|
||||||
|
builder.and(q.inboundAdapter.containsIgnoreCase(search.getSearchInboundAdapter()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchOutboundAdapter())) {
|
||||||
|
builder.and(q.outboundAdapter.containsIgnoreCase(search.getSearchOutboundAdapter()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return getJPAQueryFactory()
|
||||||
|
.selectFrom(q)
|
||||||
|
.where(builder)
|
||||||
|
.orderBy(q.statTime.desc())
|
||||||
|
.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
public List<ApiStatsChartUI> selectChartData(ApiStatsSearch search) {
|
public List<ApiStatsChartUI> selectChartData(ApiStatsSearch search) {
|
||||||
QApiStatsHour q = QApiStatsHour.apiStatsHour;
|
QApiStatsHour q = QApiStatsHour.apiStatsHour;
|
||||||
BooleanBuilder builder = new BooleanBuilder();
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
|
|||||||
+41
@@ -62,6 +62,47 @@ public class ApiStatsMinuteService
|
|||||||
return repository.findAll(builder, pageable);
|
return repository.findAll(builder, pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ApiStatsMinute> selectListForExcel(ApiStatsSearch search) {
|
||||||
|
QApiStatsMinute q = QApiStatsMinute.apiStatsMinute;
|
||||||
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
|
|
||||||
|
calculateDateRange(search);
|
||||||
|
if (search.getParsedStartDateTime() != null) {
|
||||||
|
builder.and(q.statTime.goe(search.getParsedStartDateTime()));
|
||||||
|
builder.and(q.statTime.loe(search.getParsedEndDateTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchApiName())) {
|
||||||
|
builder.and(q.apiName.containsIgnoreCase(search.getSearchApiName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchGwInstanceId())) {
|
||||||
|
builder.and(q.gwInstanceId.containsIgnoreCase(search.getSearchGwInstanceId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchBizDivCode())) {
|
||||||
|
builder.and(q.bizDivCode.containsIgnoreCase(search.getSearchBizDivCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchClientId())) {
|
||||||
|
builder.and(q.clientId.containsIgnoreCase(search.getSearchClientId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchInboundAdapter())) {
|
||||||
|
builder.and(q.inboundAdapter.containsIgnoreCase(search.getSearchInboundAdapter()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(search.getSearchOutboundAdapter())) {
|
||||||
|
builder.and(q.outboundAdapter.containsIgnoreCase(search.getSearchOutboundAdapter()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return getJPAQueryFactory()
|
||||||
|
.selectFrom(q)
|
||||||
|
.where(builder)
|
||||||
|
.orderBy(q.statTime.desc())
|
||||||
|
.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
public List<ApiStatsChartUI> selectChartData(ApiStatsSearch search) {
|
public List<ApiStatsChartUI> selectChartData(ApiStatsSearch search) {
|
||||||
QApiStatsMinute q = QApiStatsMinute.apiStatsMinute;
|
QApiStatsMinute q = QApiStatsMinute.apiStatsMinute;
|
||||||
BooleanBuilder builder = new BooleanBuilder();
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
package com.eactive.ext.kjb.statistics;
|
package com.eactive.ext.kjb.statistics;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
@@ -15,6 +22,7 @@ import com.eactive.eai.rms.common.vo.GridResponse;
|
|||||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHour;
|
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHour;
|
||||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService;
|
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsHourService;
|
||||||
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
|
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
|
||||||
|
import com.eactive.ext.kjb.statistics.service.ApiStatsExcelExportService;
|
||||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
|
import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
|
||||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
|
import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
|
||||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
||||||
@@ -29,6 +37,7 @@ public class ApiStatsHourController {
|
|||||||
|
|
||||||
private final ApiStatsHourService service;
|
private final ApiStatsHourService service;
|
||||||
private final ApiStatsUIMapper mapper;
|
private final ApiStatsUIMapper mapper;
|
||||||
|
private final ApiStatsExcelExportService excelExportService;
|
||||||
|
|
||||||
@GetMapping(value = "/onl/kjb/statistics/apiStatsHourMan.view")
|
@GetMapping(value = "/onl/kjb/statistics/apiStatsHourMan.view")
|
||||||
public String view() {
|
public String view() {
|
||||||
@@ -53,4 +62,54 @@ public class ApiStatsHourController {
|
|||||||
List<ApiStatsChartUI> chartData = service.selectChartData(search);
|
List<ApiStatsChartUI> chartData = service.selectChartData(search);
|
||||||
return ResponseEntity.ok(chartData);
|
return ResponseEntity.ok(chartData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/onl/kjb/statistics/apiStatsHourMan.json", params = "cmd=EXCEL_EXPORT")
|
||||||
|
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
|
||||||
|
log.info("Excel export started - search: {}", search);
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<ApiStatsHour> dataList = service.selectListForExcel(search);
|
||||||
|
log.info("Data retrieved - count: {}", dataList.size());
|
||||||
|
|
||||||
|
if (dataList.isEmpty()) {
|
||||||
|
log.warn("No data found for export");
|
||||||
|
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||||
|
response.setContentType("application/json; charset=UTF-8");
|
||||||
|
response.getWriter().write("{\"message\":\"조회된 데이터가 없습니다.\"}");
|
||||||
|
response.getWriter().flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ApiStatsUI> uiList = dataList.stream()
|
||||||
|
.map(mapper::toVo)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
log.info("Data converted to UI list - count: {}", uiList.size());
|
||||||
|
|
||||||
|
String fileName = generateFileName(search);
|
||||||
|
log.info("Generating Excel file: {}", fileName);
|
||||||
|
|
||||||
|
excelExportService.exportApiStats(uiList, fileName, response);
|
||||||
|
log.info("Excel export completed successfully");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Excel export failed - error: {}", e.getMessage(), e);
|
||||||
|
|
||||||
|
if (!response.isCommitted()) {
|
||||||
|
response.reset();
|
||||||
|
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
|
response.setContentType("application/json; charset=UTF-8");
|
||||||
|
response.getWriter().write("{\"message\":\"Excel 파일 생성 중 오류가 발생했습니다: " + e.getMessage() + "\"}");
|
||||||
|
response.getWriter().flush();
|
||||||
|
} else {
|
||||||
|
log.error("Cannot send error response - response already committed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateFileName(ApiStatsSearch search) {
|
||||||
|
String timeRange = StringUtils.isNotBlank(search.getSearchStartDateTime())
|
||||||
|
? search.getSearchStartDateTime()
|
||||||
|
: LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHH"));
|
||||||
|
return "API시간별통계_" + timeRange + ".xlsx";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
package com.eactive.ext.kjb.statistics;
|
package com.eactive.ext.kjb.statistics;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
@@ -15,6 +22,7 @@ import com.eactive.eai.rms.common.vo.GridResponse;
|
|||||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMinute;
|
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMinute;
|
||||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMinuteService;
|
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsMinuteService;
|
||||||
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
|
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
|
||||||
|
import com.eactive.ext.kjb.statistics.service.ApiStatsExcelExportService;
|
||||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
|
import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
|
||||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
|
import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
|
||||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
||||||
@@ -29,6 +37,7 @@ public class ApiStatsMinuteController {
|
|||||||
|
|
||||||
private final ApiStatsMinuteService service;
|
private final ApiStatsMinuteService service;
|
||||||
private final ApiStatsUIMapper mapper;
|
private final ApiStatsUIMapper mapper;
|
||||||
|
private final ApiStatsExcelExportService excelExportService;
|
||||||
|
|
||||||
@GetMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.view")
|
@GetMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.view")
|
||||||
public String view() {
|
public String view() {
|
||||||
@@ -53,4 +62,54 @@ public class ApiStatsMinuteController {
|
|||||||
List<ApiStatsChartUI> chartData = service.selectChartData(search);
|
List<ApiStatsChartUI> chartData = service.selectChartData(search);
|
||||||
return ResponseEntity.ok(chartData);
|
return ResponseEntity.ok(chartData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/onl/kjb/statistics/apiStatsMinuteMan.json", params = "cmd=EXCEL_EXPORT")
|
||||||
|
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
|
||||||
|
log.info("Excel export started - search: {}", search);
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<ApiStatsMinute> dataList = service.selectListForExcel(search);
|
||||||
|
log.info("Data retrieved - count: {}", dataList.size());
|
||||||
|
|
||||||
|
if (dataList.isEmpty()) {
|
||||||
|
log.warn("No data found for export");
|
||||||
|
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||||
|
response.setContentType("application/json; charset=UTF-8");
|
||||||
|
response.getWriter().write("{\"message\":\"조회된 데이터가 없습니다.\"}");
|
||||||
|
response.getWriter().flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ApiStatsUI> uiList = dataList.stream()
|
||||||
|
.map(mapper::toVo)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
log.info("Data converted to UI list - count: {}", uiList.size());
|
||||||
|
|
||||||
|
String fileName = generateFileName(search);
|
||||||
|
log.info("Generating Excel file: {}", fileName);
|
||||||
|
|
||||||
|
excelExportService.exportApiStats(uiList, fileName, response);
|
||||||
|
log.info("Excel export completed successfully");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Excel export failed - error: {}", e.getMessage(), e);
|
||||||
|
|
||||||
|
if (!response.isCommitted()) {
|
||||||
|
response.reset();
|
||||||
|
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
|
response.setContentType("application/json; charset=UTF-8");
|
||||||
|
response.getWriter().write("{\"message\":\"Excel 파일 생성 중 오류가 발생했습니다: " + e.getMessage() + "\"}");
|
||||||
|
response.getWriter().flush();
|
||||||
|
} else {
|
||||||
|
log.error("Cannot send error response - response already committed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateFileName(ApiStatsSearch search) {
|
||||||
|
String timeRange = StringUtils.isNotBlank(search.getSearchStartDateTime())
|
||||||
|
? search.getSearchStartDateTime()
|
||||||
|
: LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
|
||||||
|
return "API분단위통계_" + timeRange + ".xlsx";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package com.eactive.ext.kjb.statistics.service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.CellStyle;
|
||||||
|
import org.apache.poi.ss.usermodel.FillPatternType;
|
||||||
|
import org.apache.poi.ss.usermodel.Font;
|
||||||
|
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||||
|
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 통계 Excel Export 서비스
|
||||||
|
* Hour/Minute 통계 데이터를 Excel 파일로 생성
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class ApiStatsExcelExportService {
|
||||||
|
|
||||||
|
private static final String[] COLUMN_HEADERS = {
|
||||||
|
"통계시간", "API명", "인스턴스", "업무구분", "클라이언트ID",
|
||||||
|
"Inbound Adapter", "Outbound Adapter",
|
||||||
|
"총건수", "성공건수", "Timeout건수", "시스템오류건수", "업무오류건수",
|
||||||
|
"Seq900 Timeout", "Seq900 시스템오류", "Seq900 업무오류",
|
||||||
|
"평균응답시간", "최소응답시간", "최대응답시간", "P50응답시간", "P95응답시간"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 통계 데이터를 Excel 파일로 생성하여 HTTP 응답으로 전송
|
||||||
|
*
|
||||||
|
* @param dataList API 통계 데이터 리스트
|
||||||
|
* @param fileName 다운로드 파일명
|
||||||
|
* @param response HTTP 응답 객체
|
||||||
|
* @throws IOException 파일 생성 실패 시
|
||||||
|
*/
|
||||||
|
public void exportApiStats(List<ApiStatsUI> dataList, String fileName, HttpServletResponse response)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
log.info("Starting Excel export - rows: {}, filename: {}", dataList.size(), fileName);
|
||||||
|
|
||||||
|
try (Workbook workbook = new XSSFWorkbook()) {
|
||||||
|
Sheet sheet = workbook.createSheet("API통계");
|
||||||
|
log.debug("Excel sheet created");
|
||||||
|
|
||||||
|
// 스타일 생성
|
||||||
|
CellStyle headerStyle = createHeaderStyle(workbook);
|
||||||
|
CellStyle stringStyle = createStringStyle(workbook);
|
||||||
|
CellStyle numberStyle = createNumberStyle(workbook);
|
||||||
|
CellStyle decimalStyle = createDecimalStyle(workbook);
|
||||||
|
log.debug("Excel styles created");
|
||||||
|
|
||||||
|
// 헤더 행 생성
|
||||||
|
createHeaderRow(sheet, headerStyle);
|
||||||
|
log.debug("Header row created");
|
||||||
|
|
||||||
|
// 데이터 행 생성
|
||||||
|
int rowNum = 1;
|
||||||
|
for (ApiStatsUI data : dataList) {
|
||||||
|
createDataRow(sheet, rowNum++, data, stringStyle, numberStyle, decimalStyle);
|
||||||
|
}
|
||||||
|
log.info("Data rows created - count: {}", dataList.size());
|
||||||
|
|
||||||
|
// 컬럼 너비 자동 조정
|
||||||
|
autoSizeColumns(sheet);
|
||||||
|
log.debug("Column widths adjusted");
|
||||||
|
|
||||||
|
// HTTP 응답 설정
|
||||||
|
setHttpResponse(response, fileName, workbook);
|
||||||
|
log.info("Excel file sent to response");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error creating Excel file", e);
|
||||||
|
throw new IOException("Excel file creation failed: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 헤더 스타일 생성 (회색 배경, 볼드, 테두리)
|
||||||
|
*/
|
||||||
|
private CellStyle createHeaderStyle(Workbook workbook) {
|
||||||
|
CellStyle style = workbook.createCellStyle();
|
||||||
|
|
||||||
|
// 배경색
|
||||||
|
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
||||||
|
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||||
|
|
||||||
|
// 테두리
|
||||||
|
style.setBorderTop(BorderStyle.THIN);
|
||||||
|
style.setBorderBottom(BorderStyle.THIN);
|
||||||
|
style.setBorderLeft(BorderStyle.THIN);
|
||||||
|
style.setBorderRight(BorderStyle.THIN);
|
||||||
|
|
||||||
|
// 정렬
|
||||||
|
style.setAlignment(HorizontalAlignment.CENTER);
|
||||||
|
|
||||||
|
// 폰트 (볼드)
|
||||||
|
Font font = workbook.createFont();
|
||||||
|
font.setBold(true);
|
||||||
|
style.setFont(font);
|
||||||
|
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문자열 데이터 스타일 생성 (기본 테두리)
|
||||||
|
*/
|
||||||
|
private CellStyle createStringStyle(Workbook workbook) {
|
||||||
|
CellStyle style = workbook.createCellStyle();
|
||||||
|
style.setBorderTop(BorderStyle.THIN);
|
||||||
|
style.setBorderBottom(BorderStyle.THIN);
|
||||||
|
style.setBorderLeft(BorderStyle.THIN);
|
||||||
|
style.setBorderRight(BorderStyle.THIN);
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 숫자 데이터 스타일 생성 (우측정렬 + 쉼표 구분)
|
||||||
|
*/
|
||||||
|
private CellStyle createNumberStyle(Workbook workbook) {
|
||||||
|
CellStyle style = workbook.createCellStyle();
|
||||||
|
style.setBorderTop(BorderStyle.THIN);
|
||||||
|
style.setBorderBottom(BorderStyle.THIN);
|
||||||
|
style.setBorderLeft(BorderStyle.THIN);
|
||||||
|
style.setBorderRight(BorderStyle.THIN);
|
||||||
|
style.setAlignment(HorizontalAlignment.RIGHT);
|
||||||
|
style.setDataFormat(workbook.createDataFormat().getFormat("#,##0"));
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 소수점 데이터 스타일 생성 (우측정렬 + 소수점 3자리)
|
||||||
|
*/
|
||||||
|
private CellStyle createDecimalStyle(Workbook workbook) {
|
||||||
|
CellStyle style = workbook.createCellStyle();
|
||||||
|
style.setBorderTop(BorderStyle.THIN);
|
||||||
|
style.setBorderBottom(BorderStyle.THIN);
|
||||||
|
style.setBorderLeft(BorderStyle.THIN);
|
||||||
|
style.setBorderRight(BorderStyle.THIN);
|
||||||
|
style.setAlignment(HorizontalAlignment.RIGHT);
|
||||||
|
style.setDataFormat(workbook.createDataFormat().getFormat("0.000"));
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 헤더 행 생성
|
||||||
|
*/
|
||||||
|
private void createHeaderRow(Sheet sheet, CellStyle headerStyle) {
|
||||||
|
Row headerRow = sheet.createRow(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < COLUMN_HEADERS.length; i++) {
|
||||||
|
Cell cell = headerRow.createCell(i);
|
||||||
|
cell.setCellValue(COLUMN_HEADERS[i]);
|
||||||
|
cell.setCellStyle(headerStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 데이터 행 생성
|
||||||
|
*/
|
||||||
|
private void createDataRow(Sheet sheet, int rowNum, ApiStatsUI data,
|
||||||
|
CellStyle stringStyle, CellStyle numberStyle, CellStyle decimalStyle) {
|
||||||
|
|
||||||
|
Row row = sheet.createRow(rowNum);
|
||||||
|
int colNum = 0;
|
||||||
|
|
||||||
|
// 문자열 컬럼 (0-6)
|
||||||
|
createStringCell(row, colNum++, data.getStatTime(), stringStyle);
|
||||||
|
createStringCell(row, colNum++, data.getApiName(), stringStyle);
|
||||||
|
createStringCell(row, colNum++, data.getGwInstanceId(), stringStyle);
|
||||||
|
createStringCell(row, colNum++, data.getBizDivCode(), stringStyle);
|
||||||
|
createStringCell(row, colNum++, data.getClientId(), stringStyle);
|
||||||
|
createStringCell(row, colNum++, data.getInboundAdapter(), stringStyle);
|
||||||
|
createStringCell(row, colNum++, data.getOutboundAdapter(), stringStyle);
|
||||||
|
|
||||||
|
// 숫자 컬럼 (7-14)
|
||||||
|
createLongCell(row, colNum++, data.getTotalCnt(), numberStyle);
|
||||||
|
createLongCell(row, colNum++, data.getSuccessCnt(), numberStyle);
|
||||||
|
createLongCell(row, colNum++, data.getTimeoutCnt(), numberStyle);
|
||||||
|
createLongCell(row, colNum++, data.getSystemErrCnt(), numberStyle);
|
||||||
|
createLongCell(row, colNum++, data.getBizErrCnt(), numberStyle);
|
||||||
|
createLongCell(row, colNum++, data.getSeq900TimeoutCnt(), numberStyle);
|
||||||
|
createLongCell(row, colNum++, data.getSeq900SystemErrCnt(), numberStyle);
|
||||||
|
createLongCell(row, colNum++, data.getSeq900BizErrCnt(), numberStyle);
|
||||||
|
|
||||||
|
// 소수점 컬럼 (15-19)
|
||||||
|
createDecimalCell(row, colNum++, data.getAvgRespTime(), decimalStyle);
|
||||||
|
createDecimalCell(row, colNum++, data.getMinRespTime(), decimalStyle);
|
||||||
|
createDecimalCell(row, colNum++, data.getMaxRespTime(), decimalStyle);
|
||||||
|
createDecimalCell(row, colNum++, data.getP50RespTime(), decimalStyle);
|
||||||
|
createDecimalCell(row, colNum++, data.getP95RespTime(), decimalStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문자열 셀 생성
|
||||||
|
*/
|
||||||
|
private void createStringCell(Row row, int colNum, String value, CellStyle style) {
|
||||||
|
Cell cell = row.createCell(colNum);
|
||||||
|
cell.setCellValue(value != null ? value : "");
|
||||||
|
cell.setCellStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Long 타입 숫자 셀 생성
|
||||||
|
*/
|
||||||
|
private void createLongCell(Row row, int colNum, Long value, CellStyle style) {
|
||||||
|
Cell cell = row.createCell(colNum);
|
||||||
|
if (value != null) {
|
||||||
|
cell.setCellValue(value.doubleValue());
|
||||||
|
} else {
|
||||||
|
cell.setCellValue(0);
|
||||||
|
}
|
||||||
|
cell.setCellStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BigDecimal 타입 소수점 셀 생성
|
||||||
|
*/
|
||||||
|
private void createDecimalCell(Row row, int colNum, BigDecimal value, CellStyle style) {
|
||||||
|
Cell cell = row.createCell(colNum);
|
||||||
|
if (value != null) {
|
||||||
|
cell.setCellValue(value.doubleValue());
|
||||||
|
} else {
|
||||||
|
cell.setCellValue(0.0);
|
||||||
|
}
|
||||||
|
cell.setCellStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 컬럼 너비 자동 조정
|
||||||
|
*/
|
||||||
|
private void autoSizeColumns(Sheet sheet) {
|
||||||
|
for (int i = 0; i < COLUMN_HEADERS.length; i++) {
|
||||||
|
sheet.autoSizeColumn(i);
|
||||||
|
// 한글 문자 고려하여 약간 여유 공간 추가
|
||||||
|
sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 512);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 응답 설정 및 파일 전송
|
||||||
|
*/
|
||||||
|
private void setHttpResponse(HttpServletResponse response, String fileName, Workbook workbook)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
// Content-Type 설정
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||||
|
|
||||||
|
// Content-Disposition 설정 (파일명 UTF-8 인코딩)
|
||||||
|
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString())
|
||||||
|
.replaceAll("\\+", "%20");
|
||||||
|
response.setHeader("Content-Disposition",
|
||||||
|
"attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
|
||||||
|
|
||||||
|
// Workbook을 응답 스트림으로 전송
|
||||||
|
workbook.write(response.getOutputStream());
|
||||||
|
response.getOutputStream().flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user