목록 원본 다운로드 진행중...

internalApi
박성영 1 month ago
parent c4f73bc8ff
commit 50de15106f

@ -22,6 +22,8 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
/**
* Controller
@ -83,6 +85,47 @@ public class CarFfnlgTrgtController {
return ApiResponseUtil.successWithGrid(list, paramVO);
}
/**
* (EUC-KR )
* .
* - : EUC-KR ( 2)
* - // docs/-EUC-KR.txt
*/
@GetMapping("/download.do")
@Operation(summary = "과태료 대상 목록 다운로드", description = "EUC-KR 인코딩의 고정폭 텍스트로 목록을 샘플과 동일한 포맷으로 다운로드합니다.")
public void download(
@ModelAttribute CarFfnlgTrgtVO paramVO,
HttpServletResponse response
) {
try {
// 페이징 없이 전체 조회를 위해 페이징 비활성화
paramVO.setPagingYn("N");
// 서비스에서 EUC-KR 텍스트 콘텐츠 생성
byte[] fileBytes = service.generateEucKrDownloadBytes(paramVO);
String fileName = URLEncoder.encode("유효기간경과_과태료부과대상_리스트.txt", "UTF-8");
// 응답 헤더 설정 (텍스트 파일, EUC-KR 인코딩)
response.setContentType("text/plain; charset=EUC-KR");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
response.setContentLength(fileBytes.length);
// 바이트 스트림으로 전송 (본문은 EUC-KR 바이트)
response.getOutputStream().write(fileBytes);
response.getOutputStream().flush();
} catch (Exception e) {
log.error("목록 다운로드 중 오류", e);
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("다운로드 처리 중 오류가 발생했습니다: " + e.getMessage());
} catch (Exception ignored) {
}
}
}
/**
*

@ -60,4 +60,13 @@ public interface CarFfnlgTrgtService {
* @return ( , , )
*/
Map<String, Object> uploadAndParseTxtFile(MultipartFile file, String rgtr);
/**
* EUC-KR
* (docs/-EUC-KR.txt) .
*
* @param vo
* @return EUC-KR
*/
byte[] generateEucKrDownloadBytes(CarFfnlgTrgtVO vo);
}

@ -233,6 +233,161 @@ public class CarFfnlgTrgtServiceImpl implements CarFfnlgTrgtService {
return result;
}
/**
* EUC-KR
* : / ( 2)
*/
@Override
public byte[] generateEucKrDownloadBytes(CarFfnlgTrgtVO vo) {
try {
// 인코딩 및 바이트 규칙 설정 (기본 EUC-KR, 한글 2바이트)
final String encoding = parseConfig.getEncoding() == null || parseConfig.getEncoding().trim().isEmpty()
? "EUC-KR" : parseConfig.getEncoding().trim();
// 1) 데이터 조회 (페이징 비활성화)
List<CarFfnlgTrgtVO> list = mapper.selectList(vo);
StringBuilder sb = new StringBuilder();
// 2) 헤더 구성 (샘플 텍스트와 동일)
sb.append(padLeftBytes("유효기간경과 과태료부과대상 리스트", 46, encoding)).append("\r\n");
sb.append(padLeftBytes("------------------------------------", 48, encoding)).append("\r\n");
sb.append("\r\n ");
sb.append("\r\n ");
sb.append(" * 최종등록일이 검사일자보다 늦는 경우는 소유자 및 사용본거지 주소를 재확인하여 주시기 바랍니다. (재검여부 = *일수)\r\n");
sb.append(" * 전출차량( *차번호)인 경우 전출 전의 주소입니다. 소유자 및 사용본거지 주소를 재확인하여 주시기 바랍니다.\r\n");
sb.append("-------------------------------------------------------------------------------------------------------------------------------------------------\r\n");
sb.append("검사소 검사일자 자동차번호 소유자명 주민등록번호 차 명 차 종 용 도 종료일 일수 과태료\r\n");
sb.append(" 최종등록일 주 소 유효기간만료일 매매상품용\r\n");
sb.append("-------------------------------------------------------------------------------------------------------------------------------------------------\r\n");
// 3) 데이터 라인 생성 (각 항목 2줄)
for (CarFfnlgTrgtVO row : list) {
// 첫째줄: 고정폭 필드들 연결
String firstLine =
padRightBytes(nvl(row.getInspstnCd()), parseConfig.getFirstLineLength("inspstn-cd"), encoding) +
padRightBytes(formatYmd(row.getInspYmd(), true), parseConfig.getFirstLineLength("insp-ymd"), encoding) +
padRightBytes(nvl(row.getVhclno()), parseConfig.getFirstLineLength("vhclno"), encoding) +
padRightBytes(nvl(row.getOwnrNm()), parseConfig.getFirstLineLength("ownr-nm"), encoding) +
padRightBytes(nvl(row.getRrno()), parseConfig.getFirstLineLength("rrno"), encoding) +
padRightBytes(nvl(row.getCarNm()), parseConfig.getFirstLineLength("car-nm"), encoding) +
padRightBytes(nvl(row.getCarKnd()), parseConfig.getFirstLineLength("car-knd"), encoding) +
padRightBytes(nvl(row.getCarUsg()), parseConfig.getFirstLineLength("car-usg"), encoding) +
padRightBytes(formatYmd(row.getInspEndYmd(), true), parseConfig.getFirstLineLength("insp-end-ymd"), encoding) +
padLeftBytes(nvl(row.getDaycnt()), parseConfig.getFirstLineLength("daycnt"), encoding) +
padLeftBytes(formatAmtToManWon(row.getFfnlgAmt()), parseConfig.getFirstLineLength("ffnlg-amt"), encoding);
sb.append(firstLine).append("\r\n");
// 둘째줄: skip + 나머지 필드
String secondLine =
padRightBytes("", parseConfig.getSecondLineLength("skip"), encoding) +
padRightBytes(formatYmd(row.getLastRegYmd(), true), parseConfig.getSecondLineLength("last-reg-ymd"), encoding) +
padRightBytes(nvl(row.getAddr()), parseConfig.getSecondLineLength("addr"), encoding) +
padRightBytes(formatYmd(row.getVldPrdExpryYmd(), true), parseConfig.getSecondLineLength("vld-prd-expry-ymd"), encoding) +
padRightBytes(nvl(row.getTrdGds()), parseConfig.getSecondLineLength("trd-gds"), encoding);
sb.append(secondLine).append("\r\n");
sb.append("\r\n");
}
return sb.toString().getBytes(encoding);
} catch (Exception e) {
throw new RuntimeException("다운로드 파일 생성 중 오류: " + e.getMessage(), e);
}
}
// ================== 내부 유틸 메서드 ==================
/** null 안전 치환 */
private static String nvl(String s) { return s == null ? "" : s; }
/**
* : YYYYMMDD YYYY-MM-DD ( '-' )
* -
*/
private static String formatYmd(String ymd, boolean withHyphen) {
if (ymd == null || ymd.trim().isEmpty()) return "";
String v = ymd.trim();
if (!withHyphen) return v;
if (v.contains("-")) return v; // 이미 하이픈 포함
if (v.length() == 8) {
return v.substring(0,4) + "-" + v.substring(4,6) + "-" + v.substring(6,8);
}
return v;
}
/**
* "만원"
* - () : 300000 30
* - "만원"
*/
private static String formatAmtToManWon(String amt) {
String v = nvl(amt).trim();
if (v.isEmpty()) return "";
if (v.endsWith("만원")) return v;
try {
long won = Long.parseLong(v.replaceAll("[^0-9]", ""));
long man = Math.round(won / 10000.0);
return String.valueOf(man) + "만원";
} catch (NumberFormatException e) {
return v; // 숫자 변환 실패 시 원문 유지
}
}
/** 주어진 문자열을 지정 바이트 길이로 오른쪽 공백 패딩 (EUC-KR 기준 바이트) */
private static String padRightBytes(String s, int byteLen, String encoding) throws Exception {
if (byteLen <= 0) return nvl(s);
String v = nvl(s);
byte[] b = v.getBytes(encoding);
if (b.length == byteLen) return v;
if (b.length > byteLen) {
return truncateToBytes(v, byteLen, encoding);
}
// 패딩
StringBuilder sb = new StringBuilder(v);
while (sb.toString().getBytes(encoding).length < byteLen) {
sb.append(' ');
}
return sb.toString();
}
/** 주어진 문자열을 지정 바이트 길이로 왼쪽 공백 패딩 (주로 숫자용 정렬) */
private static String padLeftBytes(String s, int byteLen, String encoding) throws Exception {
if (byteLen <= 0) return nvl(s);
String v = nvl(s);
byte[] b = v.getBytes(encoding);
if (b.length == byteLen) return v;
if (b.length > byteLen) {
return truncateToBytes(v, byteLen, encoding);
}
StringBuilder sb = new StringBuilder(v);
while (sb.toString().getBytes(encoding).length < byteLen) {
sb.insert(0, ' ');
}
return sb.toString();
}
/** 지정 바이트 길이에 맞게 문자열을 잘라냄 (문자 중간 분리 방지) */
private static String truncateToBytes(String s, int byteLen, String encoding) throws Exception {
if (s == null) return "";
byte[] b = s.getBytes(encoding);
if (b.length <= byteLen) return s;
// 바이트 배열을 직접 잘라 안전한 문자열 생성
byte[] cut = new byte[byteLen];
System.arraycopy(b, 0, cut, 0, byteLen);
// 잘린 바이트가 멀티바이트 문자를 깨뜨렸을 수 있으므로
// 디코딩 실패 시 한 바이트씩 줄이며 복구
for (int len = byteLen; len > 0; len--) {
try {
return new String(cut, 0, len, encoding);
} catch (Exception ignore) {
// len 감소
}
}
return "";
}
/**
*

@ -10,6 +10,7 @@
<div class="sub_title">과태료 대상 목록</div>
<button type="button" id="registerBtn" class="newbtn bg1">등록</button>
<button type="button" id="deleteBtn" class="newbtn bg6">삭제</button>
<button type="button" id="downloadBtn" class="newbtn bg3">목록 다운로드</button>
</section>
</div>
</section>
@ -112,6 +113,21 @@
SEARCH_COND.schTaskPrcsSttsCd = schTaskPrcsSttsCd;
};
// 다운로드 URL 생성 (현재 검색조건을 쿼리스트링으로 부여)
var buildDownloadUrl = function() {
setSearchCond();
var baseUrl = '<c:url value="/carInspectionPenalty/registration/download.do"/>';
var params = [];
if (SEARCH_COND.schRcptYmdStart) params.push('schRcptYmdStart=' + encodeURIComponent(SEARCH_COND.schRcptYmdStart));
if (SEARCH_COND.schRcptYmdEnd) params.push('schRcptYmdEnd=' + encodeURIComponent(SEARCH_COND.schRcptYmdEnd));
if (SEARCH_COND.schInspYmdStart) params.push('schInspYmdStart=' + encodeURIComponent(SEARCH_COND.schInspYmdStart));
if (SEARCH_COND.schInspYmdEnd) params.push('schInspYmdEnd=' + encodeURIComponent(SEARCH_COND.schInspYmdEnd));
if (SEARCH_COND.schVhclno) params.push('schVhclno=' + encodeURIComponent(SEARCH_COND.schVhclno));
if (SEARCH_COND.schOwnrNm) params.push('schOwnrNm=' + encodeURIComponent(SEARCH_COND.schOwnrNm));
if (SEARCH_COND.schTaskPrcsSttsCd) params.push('schTaskPrcsSttsCd=' + encodeURIComponent(SEARCH_COND.schTaskPrcsSttsCd));
return baseUrl + (params.length ? ('?' + params.join('&')) : '');
};
/**
* 과태료 대상 목록 관리 네임스페이스
*/
@ -370,6 +386,13 @@
self.deleteData();
});
// 목록 다운로드 버튼 클릭
$("#downloadBtn").on('click', function() {
var url = buildDownloadUrl();
// 파일 다운로드는 단순 이동으로 처리
window.location.href = url;
});
// 페이지당 건수 변경
$("#perPageSelect").on('change', function() {
var perPage = parseInt($(this).val(), 10);

Loading…
Cancel
Save