미필 일단 업로드까진 완료

main
박성영 1 week ago
parent 40d8518a50
commit 20312693c6

@ -13,7 +13,7 @@
670406-1777778 5층(죽전동)
4 33고3355 주식회사 올놀카(상품용) C220 d 4Matic 경기도 용인시 기흥구 중부대로 255 2023-07-12~2025-07-11
134511-0327770 A동 3층 S367호(영덕동, 오토허브)
5 65조4444 정문치 레이 경기도 용인시 기흥구 흥덕4로15번길 23-55 2021-07-12~2025-07-11
5 65조4444 정문치 레이 경기도 용인시 기흥구 흥덕4로15번길 23-55 2021-07-12~2025-07-11
791203-1067771 304호(영덕동)
6 88조8888 고영유 그랜저(GRANDEUR) 경기도 용인시 수지구 성복2로 555 2023-07-12~2025-07-11
900426-1087772 2층 204호(성복동)

@ -570,17 +570,38 @@ public class CarFfnlgTrgtIncmpServiceImpl extends EgovAbstractServiceImpl implem
byte[] firstBytes = firstLine.getBytes(encoding);
int pos = 0;
// 디버깅: 첫 30바이트 출력
log.debug("=== 바이트 디버깅 ===");
log.debug("firstLine 원본: [{}]", firstLine);
log.debug("firstBytes 길이: {}", firstBytes.length);
int debugLen = Math.min(30, firstBytes.length);
byte[] debugBytes = new byte[debugLen];
System.arraycopy(firstBytes, 0, debugBytes, 0, debugLen);
log.debug("첫 30바이트: [{}]", new String(debugBytes, encoding));
for (int k = 0; k < debugLen; k++) {
log.debug(" [{}] byte={}, char=[{}]", k, firstBytes[k], new String(new byte[]{firstBytes[k]}, encoding));
}
// 번호
log.debug("parseConfig 정보: encoding={}, hangulByteSize={}", parseConfig.getEncoding(), parseConfig.getHangulByteSize());
log.debug("byteSize2={}, byteSize3={}", parseConfig.getByteSize2(), parseConfig.getByteSize3());
if (parseConfig.getByteSize2() != null) {
log.debug("byteSize2.firstLine={}", parseConfig.getByteSize2().getFirstLine());
}
int len = parseConfig.getFirstLineLength("no");
log.debug("번호 읽기: pos={}, len={}", pos, len);
String no = extractByteLength(firstBytes, pos, len, encoding).trim();
log.debug("번호 결과: [{}] (trim 전: [{}])", no, extractByteLength(firstBytes, pos, len, encoding));
pos += len;
// 차량번호
len = parseConfig.getFirstLineLength("vhclno");
log.debug("차량번호 읽기: pos={}, len={}", pos, len);
String vhclno = extractByteLength(firstBytes, pos, len, encoding).trim();
log.debug("차량번호 결과: [{}] (trim 전: [{}])", vhclno, extractByteLength(firstBytes, pos, len, encoding));
pos += len;
// 소유자명 (주민번호 빈칸 포함 32바이트)
// 소유자명 (주민번호 빈칸 포함 35바이트)
len = parseConfig.getFirstLineLength("ownr-nm");
String ownrNm = extractByteLength(firstBytes, pos, len, encoding).trim();
pos += len;

@ -159,26 +159,26 @@ car-ffnlg-prn-parse:
# ===== 2바이트 환경 설정 (EUC-KR, MS949) =====
byte-size-2:
first-line: # 첫째줄 필드별 바이트 길이 (2바이트 기준)
no: 6 # 번호
vhclno: 14 # 차량번호
"no": 6 # 번호 (따옴표 필수: YAML에서 no는 false로 해석됨)
vhclno: 16 # 차량번호
ownr-nm: 32 # 소유자명 (주민번호 빈칸 포함)
car-nm: 22 # 자동차명
use-strhld-addr: 62 # 사용본거지주소
insp-vld-prd: 23 # 검사유효기간
use-strhld-addr: 61 # 사용본거지주소
insp-vld-prd: 21 # 검사유효기간
second-line: # 둘째줄 필드별 바이트 길이 (2바이트 기준)
skip: 38 # 공백 (스킵)
rrno: 16 # 주민등록번호
use-strhld-addr: -1 # 사용본거지주소 (나머지 전체)
# ===== 3바이트 환경 설정 (UTF-8) =====
byte-size-3:
first-line: # 첫째줄 필드별 바이트 길이 (3바이트 기준)
no: 6 # 번호
vhclno: 14 # 차량번호
"no": 6 # 번호 (따옴표 필수: YAML에서 no는 false로 해석됨)
vhclno: 16 # 차량번호
ownr-nm: 32 # 소유자명 (주민번호 빈칸 포함)
car-nm: 22 # 자동차명
use-strhld-addr: 62 # 사용본거지주소
insp-vld-prd: 23 # 검사유효기간
use-strhld-addr: 61 # 사용본거지주소
insp-vld-prd: 21 # 검사유효기간
second-line: # 둘째줄 필드별 바이트 길이 (3바이트 기준)
skip: 38 # 공백 (스킵)
rrno: 16 # 주민등록번호

@ -1,163 +1,352 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- 팝업 전용 CSS -->
<link rel="stylesheet" type="text/css" href="<c:url value='/resources/xit/xit-popup.css'/>" />
<!-- TXT 파일 업로드 팝업 -->
<div class="popup_wrap">
<div class="popup_inner">
<div class="popup_tit">
<h2 class="tit">과태료 대상(미필) PRN 파일 업로드</h2>
<a href="#" class="pop-x-btn modalclose" id="btnCloseTop"></a>
</div>
<div class="popup_con">
<form id="uploadForm" name="uploadForm">
<div class="forms_table_non">
<table>
<colgroup>
<col style="width: 20%;" />
<col style="width: 80%;" />
</colgroup>
<tr>
<th class="th"><span class="required">*</span> PRN 파일</th>
<td>
<div class="file-input-row">
<input type="file" id="txtFile" name="txtFile" accept=".txt,.prn" class="file_input" />
<span class="file-input-text">파일을 선택하세요</span>
<button type="button" id="btnClearFile" class="btn_delete_file" style="display: none;" title="파일 삭제">
<i class="material-icons">close</i>
</button>
</div>
<div class="file-info" style="margin-top: 10px; color: #666; font-size: 13px;">
※ TXT, PRN 파일만 업로드 가능합니다. (최대 50MB)
</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="file-format-info" style="padding: 15px; background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 4px;">
<ul style="margin: 0; padding-left: 15px; color: #856404; font-size: 13px;">
<li>● 7~10 번째 줄은 헤더 및 라인구분 기호로 간주되어 스킵됩니다.</li>
<li>● UTF-8 -> EUC-KR 변환 후 진행.</li>
<li>● 데이터는 EUC-KR 한글 2Byte으로 구분됩니다.</li>
<li>● 컬럼 순서: 접수일자, 프로그램ID, 차량번호, 소유자명, 주민등록번호, 자동차명, 사용본거지주소, 검사유효기간</li>
<li>● 필수 항목: 접수일자, 차량번호, 소유자명, 검사유효기간</li>
<li>● 날짜 형식: YYYY-MM-DD (예: 2023-11-13)</li>
<li>● 차량번호 형식: 12가3456 또는 123가4567</li>
<li>● 검사유효기간: YYYY-MM-DD ~ YYYY-MM-DD</li>
</ul>
</div>
</td>
</tr>
</table>
</div>
</form>
<!-- 중요로직: 로딩 표시 영역 -->
<div class="loading" id="loading" style="display: none; text-align: center; padding: 20px;">
<div class="spinner" style="border: 4px solid #f3f3f3; border-top: 4px solid #4CAF50; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 10px;"></div>
<div>파일 업로드 중입니다. 잠시만 기다려주세요...</div>
</div>
</div>
<div class="popup_foot">
<button type="button" id="btnUpload" class="newbtns bg1">파일 업로드</button>
<button type="button" id="btnClose" class="newbtns bg2 modalclose">닫기</button>
</div>
</div>
</div>
<style>
.upload-area {
padding: 20px;
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.upload-form {
margin-bottom: 20px;
.loading.active {
display: block !important;
}
.file-input-wrapper {
margin-bottom: 15px;
/* 파일 입력 스타일 */
.file-input-row {
position: relative;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 15px;
background-color: #f8f9fa;
border: 2px dashed #dee2e6;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.file-info {
margin-top: 10px;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
.file-input-row:hover {
background-color: #e9ecef;
border-color: #4CAF50;
}
.file-input-row.has-file {
background-color: #e7f3ff;
border-color: #4CAF50;
border-style: solid;
}
.file_input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.file-input-text {
flex: 1;
color: #666;
font-size: 14px;
pointer-events: none;
display: flex;
align-items: center;
}
.btn-area {
text-align: center;
margin-top: 20px;
.file-input-text i.material-icons {
font-size: 18px;
margin-right: 8px;
color: #4CAF50;
}
.upload-result {
margin-top: 15px;
padding: 10px;
.btn_delete_file {
padding: 4px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s ease;
z-index: 10;
position: relative;
}
.upload-result.success {
background: #dff0d8;
color: #3c763d;
.btn_delete_file:hover {
background-color: #c82333;
}
.upload-result.error {
background: #f2dede;
color: #a94442;
.btn_delete_file i.material-icons {
font-size: 18px;
}
</style>
<div class="upload-area">
<h4><i class="fa fa-upload"></i> 미필 과태료 대상 PRN 파일 업로드</h4>
<hr>
<form id="uploadForm" enctype="multipart/form-data">
<div class="file-input-wrapper">
<label for="file">PRN 파일 선택</label>
<input type="file" class="form-control" id="file" name="file" accept=".prn,.txt">
<p class="help-block">* PRN 또는 TXT 파일만 업로드 가능합니다.</p>
<p class="help-block">* 업로드 시 UTF-8 → EUC-KR 변환됩니다.</p>
</div>
<div id="fileInfo" class="file-info" style="display: none;">
<strong>선택된 파일:</strong> <span id="fileName"></span><br>
<strong>파일 크기:</strong> <span id="fileSize"></span>
</div>
</form>
<div id="uploadResult" class="upload-result" style="display: none;"></div>
<div class="btn-area">
<button type="button" class="btn btn-primary" id="btnUpload" disabled>
<i class="fa fa-upload"></i> 업로드
</button>
<button type="button" class="btn btn-default" id="btnClose">
<i class="fa fa-times"></i> 닫기
</button>
</div>
</div>
<script type="text/javascript">
/**
* TXT 파일 업로드 팝업 JavaScript
* 중요로직: 과태료 대상(미필) TXT 파일을 업로드하고 검증한다.
*/
$(document).ready(function() {
<script>
var contextPath = '${pageContext.request.contextPath}';
$(document).ready(function() {
// 파일 선택 이벤트
$('#file').change(function() {
var file = this.files[0];
if (file) {
$('#fileName').text(file.name);
$('#fileSize').text(formatFileSize(file.size));
$('#fileInfo').show();
$('#btnUpload').prop('disabled', false);
$('#uploadResult').hide();
} else {
$('#fileInfo').hide();
$('#btnUpload').prop('disabled', true);
}
});
// 업로드 버튼 클릭
$('#btnUpload').click(function() {
uploadFile();
});
// 닫기 버튼 클릭
$('#btnClose').click(function() {
window.close();
});
/**
* 파일 업로드 버튼 클릭 이벤트
* 중요로직: 파일 선택 후 검증을 거쳐 서버로 업로드한다.
*/
$("#btnUpload").on('click', function() {
uploadFile();
});
function uploadFile() {
var file = $('#file')[0].files[0];
if (!file) {
alert('파일을 선택해주세요.');
return;
}
// 파일 확장자 검사
var ext = file.name.split('.').pop().toLowerCase();
if (ext !== 'prn' && ext !== 'txt') {
alert('PRN 또는 TXT 파일만 업로드 가능합니다.');
return;
}
var formData = new FormData();
formData.append('file', file);
$('#btnUpload').prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> 업로드 중...');
$.ajax({
url: contextPath + '/carInspectionPenalty/registration-om/upload.ajax',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if (response.success) {
$('#uploadResult')
.removeClass('error')
.addClass('success')
.html('<i class="fa fa-check"></i> ' + response.message.replace(/\n/g, '<br>'))
.show();
// 성공 시 파일 입력 초기화
$('#file').val('');
$('#fileInfo').hide();
} else {
$('#uploadResult')
.removeClass('success')
.addClass('error')
.html('<i class="fa fa-times"></i> ' + response.message.replace(/\n/g, '<br>'))
.show();
/**
* 닫기 버튼 클릭 이벤트
*/
$("#btnClose, #btnCloseTop").on('click', function() {
closePopup();
});
/**
* 파일 선택 시 파일 정보 표시
*/
$("#txtFile").on('change', function() {
handleFileSelect(this);
});
/**
* 파일 삭제 버튼 클릭 이벤트
*/
$("#btnClearFile").on('click', function(e) {
e.stopPropagation();
e.preventDefault();
clearFileInput();
});
});
/**
* 파일 업로드 함수
* 중요로직: 파일 검증 후 FormData를 사용하여 파일을 서버로 전송한다.
*/
function uploadFile() {
var fileInput = document.getElementById('txtFile');
var file = fileInput.files[0];
// 중요로직: 파일 선택 여부 검증
if (!file) {
alert("파일을 선택해주세요.");
return;
}
// 중요로직: 파일 확장자 검증
var fileName = file.name;
var fileExt = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
if (fileExt !== 'txt' && fileExt !== 'prn') {
alert("TXT, PRN 파일만 업로드 가능합니다.");
return;
}
// 중요로직: 파일 크기 검증 (50MB)
if (file.size > 50 * 1024 * 1024) {
alert("파일 크기는 50MB를 초과할 수 없습니다.");
return;
}
// 확인 메시지
if (!confirm("선택한 파일을 업로드하시겠습니까?")) {
return;
}
// 중요로직: FormData를 사용하여 파일 데이터 구성
var formData = new FormData();
formData.append('file', file);
// 로딩 표시
$("#loading").addClass('active');
$("#btnUpload").prop('disabled', true);
// 중요로직: AJAX로 파일 업로드
$.ajax({
url: '<c:url value="/carInspectionPenalty/registration-om/upload.ajax"/>',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
// 로딩 숨김
$("#loading").removeClass('active');
$("#btnUpload").prop('disabled', false);
if (response.success) {
alert(response.message);
// 중요로직: 부모 창의 목록 새로고침
if (window.opener && window.opener.CarFfnlgTrgtIncmpList) {
window.opener.CarFfnlgTrgtIncmpList.grid.reload();
}
},
error: function(xhr, status, error) {
$('#uploadResult')
.removeClass('success')
.addClass('error')
.html('<i class="fa fa-times"></i> 업로드 중 오류가 발생했습니다.')
.show();
},
complete: function() {
$('#btnUpload').prop('disabled', false).html('<i class="fa fa-upload"></i> 업로드');
closePopup();
} else {
alert(response.message || '파일 업로드에 실패했습니다.');
}
});
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
var k = 1024;
var sizes = ['Bytes', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
error: function(xhr, status, error) {
// 로딩 숨김
$("#loading").removeClass('active');
$("#btnUpload").prop('disabled', false);
console.error("업로드 오류:", error);
}
});
}
/**
* 파일 선택 처리 함수
* 선택된 파일명을 화면에 표시합니다.
*/
function handleFileSelect(fileInput) {
var $fileRow = $('.file-input-row');
var $fileText = $('.file-input-text');
var $btnClear = $('#btnClearFile');
if (fileInput.files && fileInput.files.length > 0) {
var file = fileInput.files[0];
var fileName = file.name;
var fileSize = file.size;
// 파일 크기를 읽기 쉬운 형태로 변환
var fileSizeText = formatFileSize(fileSize);
// 파일명과 크기를 표시
$fileText.html(
'<i class="material-icons" style="font-size: 18px; margin-right: 8px;">insert_drive_file</i>' +
'<span style="color: #333; font-weight: 500;">' + fileName + '</span>' +
'<span style="color: #666; font-size: 12px; margin-left: 8px;">(' + fileSizeText + ')</span>'
);
$fileText.attr('title', fileName);
// 파일이 선택된 상태 표시
$fileRow.addClass('has-file');
$btnClear.show();
// 파일 정보도 업데이트
$('.file-info').html(
/*'선택된 파일: <strong>' + fileName + '</strong> ' +*/
'파일 크기: ' + fileSizeText
);
} else {
clearFileInput();
}
}
/**
* 파일 크기를 읽기 쉬운 형태로 변환
*/
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
var k = 1024;
var sizes = ['Bytes', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* 파일 입력 초기화
*/
function clearFileInput() {
var $fileInput = $('#txtFile');
var $fileRow = $('.file-input-row');
var $fileText = $('.file-input-text');
var $btnClear = $('#btnClearFile');
// 파일 입력 초기화
$fileInput.val('');
// 기본 텍스트 표시
$fileText.html('파일을 선택하세요');
$fileText.removeAttr('title');
// 파일 선택 상태 제거
$fileRow.removeClass('has-file');
$btnClear.hide();
// 파일 정보 초기화
$('.file-info').html('※ TXT, PRN 파일만 업로드 가능합니다. (최대 50MB)');
}
/**
* 팝업 닫기 함수
*/
function closePopup() {
window.close();
}
</script>

Loading…
Cancel
Save