diff --git a/WebContent/WEB-INF/dispatcher-servlet.xml b/WebContent/WEB-INF/dispatcher-servlet.xml
index 0769b0a..e5406f5 100644
--- a/WebContent/WEB-INF/dispatcher-servlet.xml
+++ b/WebContent/WEB-INF/dispatcher-servlet.xml
@@ -43,16 +43,6 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp
index bf768b7..5d0bfa1 100644
--- a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp
+++ b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp
@@ -744,35 +744,61 @@ function fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType){
try {
var iframeWindow = this.contentWindow;
- // iframe 내의 PDF 생성 함수 호출
- if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){
- iframeWindow.fn_generateAndUploadPdf(function(pdfBlob){
- // iframe 제거
+ // 데이터 로딩 완료를 기다림 (최대 10초)
+ var checkDataLoaded = function(attempts) {
+ if(attempts > 100) { // 10초 (100ms * 100)
$('#pdfGeneratorFrame').remove();
+ Swal.close();
+ Swal.fire({
+ title: '타임아웃',
+ text: '견적서 데이터 로딩 시간이 초과되었습니다.',
+ icon: 'error'
+ });
+ return;
+ }
+
+ if(iframeWindow.dataLoaded === true) {
+ console.log('데이터 로딩 완료 확인, PDF 생성 시작');
- if(pdfBlob){
- // PDF Blob과 함께 메일 발송 요청
- fn_sendMailWithPdf(contractObjId, pdfBlob);
+ // iframe 내의 PDF 생성 함수 호출
+ if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){
+ iframeWindow.fn_generateAndUploadPdf(function(pdfBase64){
+ // iframe 제거
+ $('#pdfGeneratorFrame').remove();
+
+ if(pdfBase64){
+ // PDF Base64와 함께 메일 발송 요청
+ fn_sendMailWithPdf(contractObjId, pdfBase64);
+ } else {
+ Swal.close();
+ Swal.fire({
+ title: '오류',
+ text: 'PDF 생성에 실패했습니다.',
+ icon: 'error'
+ });
+ }
+ });
} else {
+ // iframe 제거
+ $('#pdfGeneratorFrame').remove();
+
Swal.close();
Swal.fire({
title: '오류',
- text: 'PDF 생성에 실패했습니다.',
+ text: '견적서 페이지 로드에 실패했습니다.',
icon: 'error'
});
}
- });
- } else {
- // iframe 제거
- $('#pdfGeneratorFrame').remove();
-
- Swal.close();
- Swal.fire({
- title: '오류',
- text: '견적서 페이지 로드에 실패했습니다.',
- icon: 'error'
- });
- }
+ } else {
+ // 아직 로딩 중이면 100ms 후 다시 확인
+ setTimeout(function() {
+ checkDataLoaded(attempts + 1);
+ }, 100);
+ }
+ };
+
+ // 데이터 로딩 체크 시작
+ checkDataLoaded(0);
} catch(e) {
console.error('PDF 생성 오류:', e);
$('#pdfGeneratorFrame').remove();
@@ -800,35 +826,111 @@ function fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType){
}, 30000);
}
-// PDF Blob과 함께 메일 발송 (FormData 사용)
-function fn_sendMailWithPdf(contractObjId, pdfBlob){
+// PDF Base64와 함께 메일 발송 (청크 방식)
+function fn_sendMailWithPdf(contractObjId, pdfBase64){
console.log('===== 메일 발송 시작 =====');
console.log('contractObjId:', contractObjId);
- console.log('PDF Blob 크기:', pdfBlob ? pdfBlob.size : 0, 'bytes');
+ console.log('PDF Base64 길이:', pdfBase64 ? pdfBase64.length : 0);
console.log('========================');
Swal.fire({
title: '메일 발송 중...',
- text: '잠시만 기다려주세요.',
+ text: 'PDF 업로드 중...',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
- // FormData 생성
- var formData = new FormData();
- formData.append('objId', contractObjId);
- formData.append('pdfFile', pdfBlob, 'estimate.pdf');
+ // 청크 크기: 100KB (Base64) - POST 크기 제한 고려
+ var chunkSize = 100 * 1024;
+ var totalChunks = Math.ceil(pdfBase64.length / chunkSize);
+ var uploadedChunks = 0;
+ var sessionId = 'pdf_' + contractObjId + '_' + new Date().getTime();
+
+ console.log('PDF Base64 전체 길이:', pdfBase64.length);
+ console.log('청크 크기:', chunkSize);
+ console.log('총 청크 수:', totalChunks);
+
+ // 청크 업로드 함수
+ function uploadChunk(chunkIndex) {
+ var start = chunkIndex * chunkSize;
+ var end = Math.min(start + chunkSize, pdfBase64.length);
+ var chunk = pdfBase64.substring(start, end);
+
+ console.log('청크 ' + (chunkIndex + 1) + '/' + totalChunks + ' 업로드 중...');
+
+ $.ajax({
+ url: "/contractMgmt/uploadPdfChunk.do",
+ type: "POST",
+ data: {
+ sessionId: sessionId,
+ chunkIndex: chunkIndex,
+ totalChunks: totalChunks,
+ chunk: chunk
+ },
+ dataType: "json",
+ timeout: 30000,
+ success: function(data){
+ if(data.result === "success"){
+ uploadedChunks++;
+
+ // 진행률 업데이트
+ var progress = Math.round((uploadedChunks / totalChunks) * 100);
+ Swal.update({
+ text: 'PDF 업로드 중... ' + progress + '%'
+ });
+
+ // 다음 청크 업로드
+ if(chunkIndex + 1 < totalChunks){
+ uploadChunk(chunkIndex + 1);
+ } else {
+ // 모든 청크 업로드 완료, 메일 발송 요청
+ console.log('모든 청크 업로드 완료, 메일 발송 시작');
+ sendMailWithUploadedPdf(contractObjId, sessionId);
+ }
+ } else {
+ Swal.close();
+ Swal.fire({
+ title: '업로드 실패',
+ text: 'PDF 업로드 중 오류가 발생했습니다.',
+ icon: 'error',
+ confirmButtonText: '확인'
+ });
+ }
+ },
+ error: function(xhr, status, error){
+ Swal.close();
+ console.error("청크 업로드 오류:", xhr, status, error);
+ Swal.fire({
+ title: '오류',
+ text: 'PDF 업로드 중 시스템 오류가 발생했습니다.',
+ icon: 'error',
+ confirmButtonText: '확인'
+ });
+ }
+ });
+ }
+
+ // 첫 번째 청크부터 시작
+ uploadChunk(0);
+}
+
+// 업로드된 PDF로 메일 발송
+function sendMailWithUploadedPdf(contractObjId, sessionId){
+ Swal.update({
+ text: '메일 발송 중...'
+ });
$.ajax({
url: "/contractMgmt/sendEstimateMail.do",
type: "POST",
- data: formData,
- processData: false, // FormData 사용 시 필수
- contentType: false, // FormData 사용 시 필수
+ data: {
+ objId: contractObjId,
+ pdfSessionId: sessionId
+ },
dataType: "json",
- timeout: 60000, // 60초 타임아웃
+ timeout: 60000,
success: function(data){
console.log('메일 발송 응답:', data);
Swal.close();
@@ -839,7 +941,6 @@ function fn_sendMailWithPdf(contractObjId, pdfBlob){
icon: 'success',
confirmButtonText: '확인'
}).then(() => {
- // 메일 발송 상태 업데이트를 위해 목록 새로고침
fn_search();
});
} else {
@@ -854,10 +955,9 @@ function fn_sendMailWithPdf(contractObjId, pdfBlob){
error: function(xhr, status, error){
Swal.close();
console.error("메일 발송 오류:", xhr, status, error);
- console.error("xhr.responseText:", xhr.responseText);
Swal.fire({
title: '오류',
- text: '메일 발송 중 시스템 오류가 발생했습니다: ' + status,
+ text: '메일 발송 중 시스템 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp
index 66c3bd7..fe38b37 100644
--- a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp
+++ b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp
@@ -819,32 +819,36 @@ function fn_loadTemplateData(templateObjId){
// 결재상태 저장
g_apprStatus = template.appr_status || template.APPR_STATUS || template.apprStatus || "작성중";
- // 대문자/소문자 모두 지원
- var executor = template.EXECUTOR || template.executor || "";
- var recipient = template.RECIPIENT || template.recipient || "";
- var estimateNo = template.ESTIMATE_NO || template.estimate_no || template.estimateNo || "";
- var contactPerson = template.CONTACT_PERSON || template.contact_person || template.contactPerson || "";
- var greetingText = template.GREETING_TEXT || template.greeting_text || template.greetingText || "";
- var note1 = template.NOTE1 || template.note1 || "";
- var note2 = template.NOTE2 || template.note2 || "";
- var note3 = template.NOTE3 || template.note3 || "";
- var note4 = template.NOTE4 || template.note4 || "";
- var managerName = template.MANAGER_NAME || template.manager_name || template.managerName || "영업부";
- var managerContact = template.MANAGER_CONTACT || template.manager_contact || template.managerContact || "";
-
- // 기본 정보 채우기
- $("#executor").val(executor);
+ // 대문자/소문자 모두 지원
+ var executor = template.EXECUTOR || template.executor || "";
+ var recipient = template.RECIPIENT || template.recipient || "";
+ var recipientName = template.RECIPIENT_NAME || template.recipient_name || template.recipientName || "";
+ var estimateNo = template.ESTIMATE_NO || template.estimate_no || template.estimateNo || "";
+ var contactPerson = template.CONTACT_PERSON || template.contact_person || template.contactPerson || "";
+ var greetingText = template.GREETING_TEXT || template.greeting_text || template.greetingText || "";
+ var note1 = template.NOTE1 || template.note1 || "";
+ var note2 = template.NOTE2 || template.note2 || "";
+ var note3 = template.NOTE3 || template.note3 || "";
+ var note4 = template.NOTE4 || template.note4 || "";
+ var managerName = template.MANAGER_NAME || template.manager_name || template.managerName || "영업부";
+ var managerContact = template.MANAGER_CONTACT || template.manager_contact || template.managerContact || "";
- // 데이터 로드 중 플래그 설정 (수신인 자동 로드 방지)
- window.isLoadingData = true;
-
- // 수신처 설정
+ // 기본 정보 채우기
+ $("#executor").val(executor);
+
+ // 데이터 로드 중 플래그 설정 (수신인 자동 로드 방지)
+ window.isLoadingData = true;
+
+ // 수신처 설정
+ if(recipient && recipient !== "") {
+ // OBJID로 셀렉트박스 선택
$("#recipient").val(recipient).trigger('change');
-
- // 플래그 해제
- setTimeout(function() {
- window.isLoadingData = false;
- }, 100);
+ }
+
+ // 플래그 해제
+ setTimeout(function() {
+ window.isLoadingData = false;
+ }, 100);
$("#estimate_no").val(estimateNo);
$("#contact_person").val(contactPerson);
@@ -1004,9 +1008,14 @@ function fn_loadTemplateData(templateObjId){
// 결재상태에 따라 버튼 제어
fn_controlButtons();
+
+ // 데이터 로딩 완료 플래그 설정
+ window.dataLoaded = true;
+ console.log("견적서 데이터 로딩 완료");
}
} else {
console.error("데이터 로드 실패:", data);
+ window.dataLoaded = false;
Swal.fire("데이터를 불러오는데 실패했습니다.");
}
},
@@ -1340,19 +1349,43 @@ function fn_generatePdf() {
// 버튼 영역 임시 숨김
$('.btn-area').hide();
+ // PDF 생성을 위해 스타일 조정
+ var container = $('.estimate-container');
+ var originalBg = container.css('background');
+ var originalShadow = container.css('box-shadow');
+ var originalPadding = container.css('padding');
+
+ // 깔끔한 PDF를 위해 배경, 그림자, 패딩 제거
+ container.css({
+ 'background': 'white',
+ 'box-shadow': 'none',
+ 'padding': '10mm'
+ });
+
+ // body 배경색도 흰색으로
+ var originalBodyBg = $('body').css('background-color');
+ $('body').css('background-color', 'white');
+
// 견적서 컨테이너 캡처
html2canvas(document.querySelector('.estimate-container'), {
- scale: 2, // 고해상도
+ scale: 2, // 적절한 해상도 (파일 크기 최적화)
useCORS: true,
- logging: true, // 디버깅을 위해 true로 변경
+ logging: false,
backgroundColor: '#ffffff'
}).then(function(canvas) {
+ // 스타일 복원
+ container.css({
+ 'background': originalBg,
+ 'box-shadow': originalShadow,
+ 'padding': originalPadding
+ });
+ $('body').css('background-color', originalBodyBg);
// 버튼 영역 다시 표시
$('.btn-area').show();
try {
- // Canvas를 이미지로 변환
- var imgData = canvas.toDataURL('image/png');
+ // Canvas를 JPEG 이미지로 변환 (PNG보다 파일 크기 작음)
+ var imgData = canvas.toDataURL('image/jpeg', 0.85); // 85% 품질
// PDF 생성 (A4 크기)
var pdf = new jsPDF('p', 'mm', 'a4');
@@ -1362,15 +1395,15 @@ function fn_generatePdf() {
var heightLeft = imgHeight;
var position = 0;
- // 첫 페이지 추가
- pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
+ // 첫 페이지 추가 (JPEG 압축)
+ pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
heightLeft -= pageHeight;
// 페이지가 넘어가면 추가 페이지 생성
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
- pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
+ pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
heightLeft -= pageHeight;
}
@@ -1409,7 +1442,7 @@ function fn_generatePdf() {
});
}
-// PDF를 Blob으로 생성하여 서버로 전송하는 함수
+// PDF를 Base64로 생성하여 서버로 전송하는 함수
function fn_generateAndUploadPdf(callback) {
console.log('fn_generateAndUploadPdf 호출됨');
@@ -1425,21 +1458,46 @@ function fn_generateAndUploadPdf(callback) {
// 버튼 영역 임시 숨김
$('.btn-area').hide();
+ // PDF 생성을 위해 스타일 조정
+ var container = $('.estimate-container');
+ var originalBg = container.css('background');
+ var originalShadow = container.css('box-shadow');
+ var originalPadding = container.css('padding');
+
+ // 깔끔한 PDF를 위해 배경, 그림자, 패딩 제거
+ container.css({
+ 'background': 'white',
+ 'box-shadow': 'none',
+ 'padding': '10mm'
+ });
+
+ // body 배경색도 흰색으로
+ var originalBodyBg = $('body').css('background-color');
+ $('body').css('background-color', 'white');
+
// 견적서 컨테이너 캡처
html2canvas(document.querySelector('.estimate-container'), {
- scale: 2,
+ scale: 2, // 적절한 해상도 (파일 크기 최적화)
useCORS: true,
- logging: true,
+ logging: false,
backgroundColor: '#ffffff'
}).then(function(canvas) {
console.log('Canvas 캡처 완료');
+ // 스타일 복원
+ container.css({
+ 'background': originalBg,
+ 'box-shadow': originalShadow,
+ 'padding': originalPadding
+ });
+ $('body').css('background-color', originalBodyBg);
+
// 버튼 영역 다시 표시
$('.btn-area').show();
try {
- // Canvas를 이미지로 변환
- var imgData = canvas.toDataURL('image/png');
+ // Canvas를 JPEG 이미지로 변환 (PNG보다 파일 크기 작음)
+ var imgData = canvas.toDataURL('image/jpeg', 0.85); // 85% 품질
console.log('이미지 변환 완료');
// PDF 생성
@@ -1450,25 +1508,26 @@ function fn_generateAndUploadPdf(callback) {
var heightLeft = imgHeight;
var position = 0;
- pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
+ // JPEG 이미지 추가 (압축됨)
+ pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
- pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
+ pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
heightLeft -= pageHeight;
}
console.log('PDF 생성 완료');
- // PDF를 Blob으로 변환 (Base64 대신 바이너리)
- var pdfBlob = pdf.output('blob');
- console.log('PDF Blob 생성 완료, 크기:', pdfBlob.size, 'bytes');
+ // PDF를 Base64로 변환
+ var pdfBase64 = pdf.output('dataurlstring').split(',')[1];
+ console.log('PDF Base64 생성 완료, 길이:', pdfBase64.length);
// 콜백 함수 호출 (메일 발송 등에 사용)
if(callback && typeof callback === 'function') {
- callback(pdfBlob);
+ callback(pdfBase64);
}
} catch(pdfError) {
console.error('PDF 생성 중 오류:', pdfError);
diff --git a/src/com/pms/common/utils/MailUtil.java b/src/com/pms/common/utils/MailUtil.java
index 2207f14..021e215 100644
--- a/src/com/pms/common/utils/MailUtil.java
+++ b/src/com/pms/common/utils/MailUtil.java
@@ -773,6 +773,142 @@ public class MailUtil {
return sendMailNew("", toUserEmail, subject, contents, null);
}
+ /**
+ * UTF-8 인코딩으로 메일 발송 (견적서 등 한글 내용이 많은 경우)
+ * 기존 sendMailWithAttachFile과 동일하지만 UTF-8 인코딩 사용
+ */
+ public static boolean sendMailWithAttachFileUTF8(String fromUserId, String fromEmail
+ , ArrayList toUserIdList, ArrayList toEmailList, ArrayList ccEmailList, ArrayList bccEmailList
+ , String important, String subject, String contents
+ , ArrayList attachFileList
+ , String mailType
+ ) {
+
+ if(Constants.Mail.sendMailSwitch == false){
+ System.out.println("MailUtil.sendMailWithAttachFileUTF8 ::: Constants.Mail.sendMailSwitch is FALSE");
+ return false;
+ }
+
+ try {
+ /*변수 초기화*/
+ fromUserId = CommonUtils.checkNull(fromUserId, "admin");
+ fromEmail = Constants.Mail.SMTP_USER;
+ important = CommonUtils.checkNull(important);
+ subject = CommonUtils.checkNull(subject , "empty subject" );
+ contents = CommonUtils.checkNull(contents, "empty contents");
+ mailType = CommonUtils.checkNull(mailType, "empty mailType");
+
+ System.out.println("MailUtil.sendMailWithAttachFileUTF8()..");
+ System.out.println("MailUtil.sendMailWithAttachFileUTF8(fromUserId ):"+fromUserId);
+ System.out.println("MailUtil.sendMailWithAttachFileUTF8(fromEmail ):"+fromEmail);
+ System.out.println("MailUtil.sendMailWithAttachFileUTF8(toEmailList ):"+toEmailList);
+ System.out.println("MailUtil.sendMailWithAttachFileUTF8(ccEmailList ):"+ccEmailList);
+ System.out.println("MailUtil.sendMailWithAttachFileUTF8(subject ):"+subject);
+
+ if(toEmailList == null){ return false; }
+
+ //◆◆◆ 1. mail session ◆◆◆
+ Properties prop = new Properties();
+ prop.put("mail.smtp.host", Constants.Mail.SMTP_HOST);
+ prop.put("mail.smtp.port", Constants.Mail.SMTP_PORT);
+ prop.put("mail.smtp.auth" , "true");
+ prop.put("mail.smtp.starttls.enable" , "true");
+ prop.put("mail.smtps.checkserveridentity", "true");
+ prop.put("mail.smtps.ssl.trust" , "*");
+ prop.put("mail.debug" , "true");
+ prop.put("mail.smtp.socketFactory.class" , "javax.net.ssl.SSLSocketFactory");
+ prop.put("mail.smtp.ssl.enable" , "true");
+ prop.put("mail.smtp.socketFactory.port" , Constants.Mail.SMTP_PORT);
+ prop.put("mail.smtp.ssl.trust" , "smtp.gmail.com");
+ prop.put("mail.transport.protocol", "smtp");
+ prop.put("mail.smtp.ssl.protocols", "TLSv1.2");
+
+ Session mailSession = Session.getDefaultInstance(prop, new javax.mail.Authenticator() {
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(Constants.Mail.SMTP_USER, Constants.Mail.SMTP_USER_PW);
+ }
+ });
+ mailSession.setDebug(true);
+ mailSession.setDebugOut(new PrintStream(new ByteArrayOutputStream()));
+
+ //◆◆◆ 2. message ◆◆◆
+ MimeMessage message = new MimeMessage(mailSession);
+ message.setFrom(new InternetAddress( fromEmail ));
+
+ // 수신자
+ if(toEmailList != null){
+ for (String _mail : toEmailList) {
+ if(CommonUtils.checkNull(_mail).equals("")){
+ continue;
+ }
+ message.addRecipient(Message.RecipientType.TO, new InternetAddress(_mail));
+ }
+ }
+ // 참조
+ if(ccEmailList != null){
+ for (String _mail : ccEmailList) {
+ if(CommonUtils.checkNull(_mail).equals("")){
+ continue;
+ }
+ message.addRecipient(Message.RecipientType.CC, new InternetAddress(_mail));
+ }
+ }
+ // 숨은참조
+ if(bccEmailList != null){
+ for (String _mail : bccEmailList) {
+ if(CommonUtils.checkNull(_mail).equals("")){
+ continue;
+ }
+ message.addRecipient(Message.RecipientType.BCC, new InternetAddress(_mail));
+ }
+ }
+ // 중요도
+ if("".equals(important)){
+ message.setHeader("Importance", "Normal");
+ }else{
+ message.setHeader("Importance", important);
+ }
+ // 제목 (UTF-8)
+ message.setSubject(subject, "UTF-8");
+
+ Multipart multipart = new MimeMultipart();
+ // 내용 (UTF-8)
+ MimeBodyPart mbp_content = new MimeBodyPart();
+ mbp_content.setContent(contents, "text/html;charset=UTF-8");
+ mbp_content.setHeader("Content-Transfer-Encoding", "base64");
+ multipart.addBodyPart(mbp_content);
+
+ // 첨부파일
+ if(attachFileList != null){
+ for(HashMap hmFile : attachFileList){
+ String filepath = CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_PATH)) + File.separator + CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_SAVED_NAME));
+ File attach_file = new File(filepath);
+ String attach_fileName = CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_REAL_NAME));
+
+ MimeBodyPart mbp_attachFile = new MimeBodyPart();
+ mbp_attachFile.setFileName(MimeUtility.encodeText(attach_fileName, "UTF-8", "B"));
+ mbp_attachFile.setDataHandler(new DataHandler(new FileDataSource(attach_file)));
+ mbp_attachFile.setHeader("Content-Type", Files.probeContentType(Paths.get(attach_file.getCanonicalPath())));
+ mbp_attachFile.setDescription(attach_fileName.split("\\.")[0], "UTF-8");
+ multipart.addBodyPart(mbp_attachFile);
+ }
+ }
+ message.setContent(multipart);
+ message.setSentDate(new Date());
+
+ // 메일 발송
+ Transport.send(message);
+ System.out.println("메일 발송 성공 (UTF-8)");
+
+ return true;
+
+ } catch (Exception e) {
+ System.out.println("메일 발송 실패 (UTF-8): " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
/**
* 첨부파일 목록을 파일하나로 압축해서 반환
* @param zipFileName 압축파일명
diff --git a/src/com/pms/salesmgmt/controller/ContractMgmtController.java b/src/com/pms/salesmgmt/controller/ContractMgmtController.java
index 13f13e9..0026744 100644
--- a/src/com/pms/salesmgmt/controller/ContractMgmtController.java
+++ b/src/com/pms/salesmgmt/controller/ContractMgmtController.java
@@ -2071,34 +2071,49 @@ public class ContractMgmtController {
}
/**
- * 견적서 메일 발송 (AJAX) - PDF 파일 업로드 방식
+ * PDF 청크 업로드 (AJAX)
* @param request
- * @param paramMap - objId (CONTRACT_OBJID)
- * @param pdfFile - PDF 파일 (MultipartFile)
+ * @param paramMap - sessionId, chunkIndex, totalChunks, chunk
+ * @return
+ */
+ @ResponseBody
+ @RequestMapping(value="/contractMgmt/uploadPdfChunk.do", method=RequestMethod.POST)
+ public Map uploadPdfChunk(HttpServletRequest request,
+ @RequestParam Map paramMap){
+ Map resultMap = new HashMap();
+
+ try {
+ resultMap = contractMgmtService.uploadPdfChunk(request, paramMap);
+ } catch (Exception e) {
+ e.printStackTrace();
+ resultMap.put("result", "error");
+ resultMap.put("message", "청크 업로드 중 오류가 발생했습니다: " + e.getMessage());
+ }
+
+ return resultMap;
+ }
+
+ /**
+ * 견적서 메일 발송 (AJAX) - PDF 세션 ID 방식
+ * @param request
+ * @param paramMap - objId (CONTRACT_OBJID), pdfSessionId (업로드된 PDF 세션 ID)
* @return
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/sendEstimateMail.do", method=RequestMethod.POST)
public Map sendEstimateMail(HttpServletRequest request,
- @RequestParam Map paramMap,
- @RequestParam(value="pdfFile", required=false) org.springframework.web.multipart.MultipartFile pdfFile){
+ @RequestParam Map paramMap){
Map resultMap = new HashMap();
try {
String objId = CommonUtils.checkNull(paramMap.get("objId"));
-
if("".equals(objId) || "-1".equals(objId)){
resultMap.put("result", "error");
resultMap.put("message", "잘못된 요청입니다.");
return resultMap;
}
- // PDF 파일을 paramMap에 추가
- if(pdfFile != null && !pdfFile.isEmpty()) {
- paramMap.put("pdfFile", pdfFile);
- }
-
// 메일 발송 서비스 호출
resultMap = contractMgmtService.sendEstimateMail(request, paramMap);
diff --git a/src/com/pms/salesmgmt/service/ContractMgmtService.java b/src/com/pms/salesmgmt/service/ContractMgmtService.java
index 4a5ba99..99c6c34 100644
--- a/src/com/pms/salesmgmt/service/ContractMgmtService.java
+++ b/src/com/pms/salesmgmt/service/ContractMgmtService.java
@@ -1572,62 +1572,23 @@ public class ContractMgmtService {
ccEmailList.add(writerEmail);
}
- // 7-1. PDF 파일 처리 (MultipartFile로 전송된 경우)
+ // 7-1. PDF 파일 처리 (세션 ID로 업로드된 경우)
ArrayList attachFileList = new ArrayList();
- org.springframework.web.multipart.MultipartFile pdfFile =
- (org.springframework.web.multipart.MultipartFile) paramMap.get("pdfFile");
+ String pdfSessionId = CommonUtils.checkNull(paramMap.get("pdfSessionId"));
- System.out.println("===== PDF 파일 수신 확인 =====");
- System.out.println("pdfFile 존재 여부: " + (pdfFile != null));
- if(pdfFile != null) {
- System.out.println("pdfFile 크기: " + pdfFile.getSize() + " bytes");
- System.out.println("pdfFile 이름: " + pdfFile.getOriginalFilename());
- }
- System.out.println("==============================");
-
- if(pdfFile != null && !pdfFile.isEmpty()) {
- // MultipartFile을 임시 파일로 저장
- try {
- System.out.println("PDF 파일 저장 시작...");
-
- // 견적번호를 파일명으로 사용
- String estimateNo = CommonUtils.checkNull(estimateTemplate.get("estimate_no"));
- if("".equals(estimateNo)) estimateNo = CommonUtils.checkNull(estimateTemplate.get("ESTIMATE_NO"));
- if("".equals(estimateNo)) estimateNo = "견적서";
-
- System.out.println("견적번호: " + estimateNo);
-
- // 임시 디렉토리에 파일 저장
- String tempDir = System.getProperty("java.io.tmpdir");
- java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMddHHmmss");
- String timestamp = sdf.format(new java.util.Date());
- String fileName = estimateNo + "_" + timestamp + ".pdf";
- String filePath = tempDir + File.separator + fileName;
-
- File tempFile = new File(filePath);
- pdfFile.transferTo(tempFile);
-
- // JVM 종료 시 자동 삭제
- tempFile.deleteOnExit();
-
- System.out.println("PDF 파일 저장 성공: " + tempFile.getAbsolutePath());
-
- // File 객체를 HashMap으로 변환 (MailUtil이 요구하는 형식)
+ if(!"".equals(pdfSessionId)) {
+ // 세션 ID로 저장된 PDF 파일 가져오기
+ File pdfFile = getPdfFromSession(pdfSessionId, estimateTemplate);
+ if(pdfFile != null && pdfFile.exists()) {
HashMap fileMap = new HashMap();
- fileMap.put(Constants.Db.COL_FILE_REAL_NAME, tempFile.getName());
- fileMap.put(Constants.Db.COL_FILE_SAVED_NAME, tempFile.getName());
- fileMap.put(Constants.Db.COL_FILE_PATH, tempFile.getParent());
+ fileMap.put(Constants.Db.COL_FILE_REAL_NAME, pdfFile.getName());
+ fileMap.put(Constants.Db.COL_FILE_SAVED_NAME, pdfFile.getName());
+ fileMap.put(Constants.Db.COL_FILE_PATH, pdfFile.getParent());
attachFileList.add(fileMap);
- System.out.println("===== PDF 파일 처리 완료 =====");
- System.out.println("PDF 파일 경로: " + tempFile.getAbsolutePath());
- System.out.println("PDF 파일 크기: " + tempFile.length() + " bytes");
- System.out.println("=============================");
-
- } catch(Exception pdfEx) {
- System.out.println("PDF 처리 중 오류 발생: " + pdfEx.getMessage());
- pdfEx.printStackTrace();
- // PDF 처리 실패해도 메일은 발송 (첨부 없이)
+ System.out.println("PDF 파일 첨부 완료: " + pdfFile.getAbsolutePath());
+ } else {
+ System.out.println("PDF 파일을 찾을 수 없습니다: " + pdfSessionId);
}
}
@@ -1645,7 +1606,8 @@ public class ContractMgmtService {
boolean mailSent = false;
try {
- mailSent = MailUtil.sendMailWithAttachFile(
+ // UTF-8 인코딩 메서드 사용 (견적서는 한글 내용이 많음)
+ mailSent = MailUtil.sendMailWithAttachFileUTF8(
fromUserId,
null, // fromEmail (자동으로 SMTP 설정 사용)
new ArrayList(), // toUserIdList (빈 리스트)
@@ -2487,4 +2449,143 @@ private String encodeImageToBase64(String imagePath) {
return itemList;
}
+
+ /**
+ * PDF 청크 업로드 처리
+ * @param request
+ * @param paramMap - sessionId, chunkIndex, totalChunks, chunk
+ * @return
+ */
+ public Map uploadPdfChunk(HttpServletRequest request, Map paramMap) {
+ Map resultMap = new HashMap();
+
+ try {
+ System.out.println("===== uploadPdfChunk 호출 =====");
+ System.out.println("전달된 파라미터: " + paramMap);
+ System.out.println("==============================");
+
+ String sessionId = CommonUtils.checkNull(paramMap.get("sessionId"));
+ String chunkIndexStr = CommonUtils.checkNull(paramMap.get("chunkIndex"));
+ String totalChunksStr = CommonUtils.checkNull(paramMap.get("totalChunks"));
+ String chunk = CommonUtils.checkNull(paramMap.get("chunk"));
+
+ if("".equals(sessionId) || "".equals(chunkIndexStr) || "".equals(totalChunksStr) || "".equals(chunk)) {
+ System.out.println("필수 파라미터 누락");
+ resultMap.put("result", "error");
+ resultMap.put("message", "필수 파라미터가 누락되었습니다.");
+ return resultMap;
+ }
+
+ int chunkIndex = Integer.parseInt(chunkIndexStr);
+ int totalChunks = Integer.parseInt(totalChunksStr);
+
+ System.out.println("청크 업로드: " + sessionId + " [" + (chunkIndex + 1) + "/" + totalChunks + "]");
+ System.out.println("청크 크기: " + chunk.length() + " bytes");
+
+ // 임시 디렉토리에 청크 저장
+ String tempDir = System.getProperty("java.io.tmpdir");
+ String chunkDir = tempDir + File.separator + "pdf_chunks" + File.separator + sessionId;
+ File chunkDirFile = new File(chunkDir);
+ if(!chunkDirFile.exists()) {
+ chunkDirFile.mkdirs();
+ }
+
+ // 청크 파일 저장
+ String chunkFileName = "chunk_" + chunkIndex + ".txt";
+ File chunkFile = new File(chunkDir + File.separator + chunkFileName);
+ java.io.FileWriter fw = new java.io.FileWriter(chunkFile);
+ fw.write(chunk);
+ fw.close();
+
+ // 마지막 청크인 경우 모든 청크를 합쳐서 PDF 파일 생성
+ if(chunkIndex == totalChunks - 1) {
+ System.out.println("모든 청크 수신 완료, PDF 파일 생성 시작");
+
+ // 모든 청크 읽어서 합치기
+ StringBuilder fullBase64 = new StringBuilder();
+ for(int i = 0; i < totalChunks; i++) {
+ File cf = new File(chunkDir + File.separator + "chunk_" + i + ".txt");
+ java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(cf));
+ String line;
+ while((line = br.readLine()) != null) {
+ fullBase64.append(line);
+ }
+ br.close();
+ }
+
+ // Base64 디코딩하여 PDF 파일 생성 (Apache Commons Codec 사용)
+ byte[] pdfBytes = org.apache.commons.codec.binary.Base64.decodeBase64(fullBase64.toString());
+
+ String pdfFileName = sessionId + ".pdf";
+ File pdfFile = new File(tempDir + File.separator + pdfFileName);
+ java.io.FileOutputStream fos = new java.io.FileOutputStream(pdfFile);
+ fos.write(pdfBytes);
+ fos.close();
+
+ pdfFile.deleteOnExit();
+
+ System.out.println("PDF 파일 생성 완료: " + pdfFile.getAbsolutePath());
+
+ // 청크 파일들 삭제
+ for(int i = 0; i < totalChunks; i++) {
+ File cf = new File(chunkDir + File.separator + "chunk_" + i + ".txt");
+ cf.delete();
+ }
+ chunkDirFile.delete();
+ }
+
+ resultMap.put("result", "success");
+
+ } catch(Exception e) {
+ System.out.println("청크 업로드 중 오류: " + e.getMessage());
+ e.printStackTrace();
+ resultMap.put("result", "error");
+ resultMap.put("message", e.getMessage());
+ }
+
+ return resultMap;
+ }
+
+ /**
+ * 세션 ID로 저장된 PDF 파일 가져오기
+ * @param sessionId
+ * @param estimateTemplate
+ * @return
+ */
+ private File getPdfFromSession(String sessionId, Map estimateTemplate) {
+ try {
+ String tempDir = System.getProperty("java.io.tmpdir");
+ File pdfFile = new File(tempDir + File.separator + sessionId + ".pdf");
+
+ if(pdfFile.exists()) {
+ // 견적번호로 파일명 변경
+ String estimateNo = CommonUtils.checkNull(estimateTemplate.get("estimate_no"));
+ if("".equals(estimateNo)) estimateNo = CommonUtils.checkNull(estimateTemplate.get("ESTIMATE_NO"));
+ if("".equals(estimateNo)) estimateNo = "견적서";
+
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMddHHmmss");
+ String timestamp = sdf.format(new java.util.Date());
+ String newFileName = estimateNo + "_" + timestamp + ".pdf";
+ File renamedFile = new File(tempDir + File.separator + newFileName);
+
+ // 파일 복사
+ java.nio.file.Files.copy(pdfFile.toPath(), renamedFile.toPath(),
+ java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+
+ renamedFile.deleteOnExit();
+
+ // 원본 파일 삭제
+ pdfFile.delete();
+
+ return renamedFile;
+ }
+
+ return null;
+
+ } catch(Exception e) {
+ System.out.println("PDF 파일 가져오기 중 오류: " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
}
diff --git a/tomcat-conf/server.xml b/tomcat-conf/server.xml
index c069afd..c7d1748 100644
--- a/tomcat-conf/server.xml
+++ b/tomcat-conf/server.xml
@@ -15,14 +15,14 @@
-
+