럭키엑셀에 파일 인젝션 성공
This commit is contained in:
5
.cursor/rules/luckyexcel-api.mdc
Normal file
5
.cursor/rules/luckyexcel-api.mdc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
@@ -4,8 +4,8 @@ import type { SheetData, FileUploadResult } from "../types/sheet";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 파일 처리 관련 유틸리티 - 개선된 버전
|
* 파일 처리 관련 유틸리티 - 개선된 버전
|
||||||
* - 모든 파일 형식 (CSV, XLS, XLSX)을 SheetJS를 통해 처리
|
* - 모든 파일 형식을 SheetJS를 통해 읽은 후 XLSX로 변환
|
||||||
* - LuckyExcel 우선 시도, 실패 시 SheetJS Fallback 사용
|
* - 변환된 XLSX 파일을 LuckyExcel로 전달
|
||||||
* - 안정적인 한글 지원 및 에러 처리
|
* - 안정적인 한글 지원 및 에러 처리
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -122,10 +122,6 @@ function validateWorkbook(workbook: any): { isValid: boolean; error?: string } {
|
|||||||
return { isValid: false, error: "워크북에 시트가 없습니다" };
|
return { isValid: false, error: "워크북에 시트가 없습니다" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!workbook.Sheets) {
|
|
||||||
return { isValid: false, error: "워크북에 Sheets 속성이 없습니다" };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { isValid: true };
|
return { isValid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,60 +417,270 @@ function convertSheetJSToLuckyExcel(workbook: any): SheetData[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SheetJS를 사용한 Fallback 처리
|
* SheetJS로 파일을 읽고 XLSX로 변환한 뒤 LuckyExcel로 처리
|
||||||
*/
|
*/
|
||||||
async function processWithSheetJSFallback(file: File): Promise<SheetData[]> {
|
async function processFileWithSheetJSToXLSX(file: File): Promise<SheetData[]> {
|
||||||
console.log("🔄 SheetJS Fallback 처리 시작...");
|
console.log("📊 SheetJS → XLSX → LuckyExcel 파이프라인 시작...");
|
||||||
|
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
const fileName = file.name.toLowerCase();
|
||||||
|
const isCSV = fileName.endsWith(".csv");
|
||||||
|
const isXLS = fileName.endsWith(".xls");
|
||||||
|
const isXLSX = fileName.endsWith(".xlsx");
|
||||||
|
|
||||||
// 단순한 SheetJS 옵션 사용 (이전 코드 방식)
|
// 1단계: SheetJS로 파일 읽기
|
||||||
const workbook = XLSX.read(arrayBuffer, {
|
let workbook: any;
|
||||||
type: "array",
|
|
||||||
cellDates: true,
|
try {
|
||||||
cellNF: false,
|
if (isCSV) {
|
||||||
cellText: false,
|
// CSV 파일 처리 - UTF-8 디코딩 후 읽기
|
||||||
|
console.log("📄 CSV 파일을 SheetJS로 읽는 중...");
|
||||||
|
const text = new TextDecoder("utf-8").decode(arrayBuffer);
|
||||||
|
workbook = XLSX.read(text, {
|
||||||
|
type: "string",
|
||||||
codepage: 65001, // UTF-8
|
codepage: 65001, // UTF-8
|
||||||
dense: false,
|
raw: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// XLS/XLSX 파일 처리 - 관대한 옵션으로 읽기
|
||||||
|
console.log(`📊 ${isXLS ? "XLS" : "XLSX"} 파일을 SheetJS로 읽는 중...`);
|
||||||
|
workbook = XLSX.read(arrayBuffer, {
|
||||||
|
type: "array",
|
||||||
|
cellText: true,
|
||||||
sheetStubs: true,
|
sheetStubs: true,
|
||||||
|
WTF: true,
|
||||||
|
bookSheets: false,
|
||||||
|
codepage: 65001,
|
||||||
raw: false,
|
raw: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("📊 SheetJS Fallback 워크북 정보:", {
|
// Sheets가 없고 SheetNames만 있는 경우 재시도
|
||||||
|
if (workbook.SheetNames?.length > 0 && !workbook.Sheets) {
|
||||||
|
console.log("⚠️ Sheets 속성이 없어서 재읽기 시도...");
|
||||||
|
workbook = XLSX.read(arrayBuffer, {
|
||||||
|
type: "array",
|
||||||
|
cellText: true,
|
||||||
|
sheetStubs: true,
|
||||||
|
WTF: true,
|
||||||
|
bookSheets: true, // 강제로 시트 읽기
|
||||||
|
codepage: 65001,
|
||||||
|
raw: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 여전히 실패하면 수동으로 빈 시트 생성
|
||||||
|
if (!workbook.Sheets && workbook.SheetNames?.length > 0) {
|
||||||
|
console.log("⚠️ 수동으로 빈 시트 생성...");
|
||||||
|
workbook.Sheets = {};
|
||||||
|
workbook.SheetNames.forEach((sheetName: string) => {
|
||||||
|
workbook.Sheets[sheetName] = {
|
||||||
|
"!ref": "A1:A1",
|
||||||
|
A1: { v: "", t: "s" },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (readError) {
|
||||||
|
console.error("❌ SheetJS 파일 읽기 실패:", readError);
|
||||||
|
throw new Error(
|
||||||
|
`파일을 읽을 수 없습니다: ${readError instanceof Error ? readError.message : readError}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 파일 버퍼 크기 검증
|
||||||
|
if (arrayBuffer.byteLength === 0) {
|
||||||
|
throw new Error("파일이 비어있습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 워크북 null 체크
|
||||||
|
if (!workbook) {
|
||||||
|
throw new Error("워크북을 생성할 수 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// workbook.Sheets 존재 및 타입 검증
|
||||||
|
if (!workbook.Sheets || typeof workbook.Sheets !== "object") {
|
||||||
|
throw new Error("유효한 시트가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// workbook.SheetNames 배열 검증
|
||||||
|
if (!Array.isArray(workbook.SheetNames) || workbook.SheetNames.length === 0) {
|
||||||
|
throw new Error("시트 이름 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ SheetJS 워크북 읽기 성공:", {
|
||||||
sheetNames: workbook.SheetNames,
|
sheetNames: workbook.SheetNames,
|
||||||
sheetCount: workbook.SheetNames.length,
|
sheetCount: workbook.SheetNames.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (workbook.SheetNames.length === 0) {
|
// 2단계: 워크북을 XLSX ArrayBuffer로 변환
|
||||||
throw new Error("파일에 워크시트가 없습니다.");
|
let xlsxArrayBuffer: ArrayBuffer;
|
||||||
|
try {
|
||||||
|
console.log("🔄 XLSX 형식으로 변환 중...");
|
||||||
|
const xlsxData = XLSX.write(workbook, {
|
||||||
|
type: "array",
|
||||||
|
bookType: "xlsx",
|
||||||
|
compression: true,
|
||||||
|
});
|
||||||
|
// xlsxData는 Uint8Array이므로 ArrayBuffer로 변환
|
||||||
|
if (xlsxData instanceof Uint8Array) {
|
||||||
|
xlsxArrayBuffer = xlsxData.buffer.slice(
|
||||||
|
xlsxData.byteOffset,
|
||||||
|
xlsxData.byteOffset + xlsxData.byteLength,
|
||||||
|
);
|
||||||
|
} else if (xlsxData instanceof ArrayBuffer) {
|
||||||
|
xlsxArrayBuffer = xlsxData;
|
||||||
|
} else {
|
||||||
|
// 다른 타입의 경우 새 ArrayBuffer 생성
|
||||||
|
xlsxArrayBuffer = new ArrayBuffer(xlsxData.length);
|
||||||
|
const view = new Uint8Array(xlsxArrayBuffer);
|
||||||
|
for (let i = 0; i < xlsxData.length; i++) {
|
||||||
|
view[i] = xlsxData[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return convertSheetJSToLuckyExcel(workbook);
|
console.log(`✅ XLSX 변환 완료: ${xlsxArrayBuffer.byteLength} bytes`);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// ⏱️ ArrayBuffer 변환 완료 확인 및 검증
|
||||||
* LuckyExcel 처리 함수 (개선된 버전)
|
console.log("⏱️ ArrayBuffer 변환 검증 중...");
|
||||||
*/
|
|
||||||
function processWithLuckyExcel(file: File): Promise<SheetData[]> {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
console.log("🍀 LuckyExcel 처리 시작...");
|
|
||||||
|
|
||||||
console.log("🔍 LuckyExcel 변환 시작:", {
|
// ArrayBuffer 무결성 검증
|
||||||
hasFile: !!file,
|
if (!xlsxArrayBuffer || xlsxArrayBuffer.byteLength === 0) {
|
||||||
fileName: file.name,
|
throw new Error("ArrayBuffer 변환 실패: 빈 버퍼");
|
||||||
fileSize: file.size,
|
}
|
||||||
fileType: file.type,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// XLSX 파일 시그니처 사전 검증
|
||||||
|
const uint8Check = new Uint8Array(xlsxArrayBuffer);
|
||||||
|
const signatureCheck = Array.from(uint8Check.slice(0, 4))
|
||||||
|
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
if (signatureCheck !== "50 4b 03 04") {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ 잘못된 XLSX 시그니처: ${signatureCheck} (예상: 50 4b 03 04)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`✅ ArrayBuffer 검증 완료: ${xlsxArrayBuffer.byteLength} bytes, 시그니처: ${signatureCheck}`,
|
||||||
|
);
|
||||||
|
} catch (writeError) {
|
||||||
|
console.error("❌ XLSX 변환 실패:", writeError);
|
||||||
|
throw new Error(
|
||||||
|
`XLSX 변환 실패: ${writeError instanceof Error ? writeError.message : writeError}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3단계: ArrayBuffer가 완전히 준비된 후 LuckyExcel로 처리
|
||||||
|
console.log("🍀 LuckyExcel로 변환된 XLSX 처리 중...");
|
||||||
|
|
||||||
|
// ArrayBuffer 최종 검증
|
||||||
|
if (!xlsxArrayBuffer) {
|
||||||
|
throw new Error("ArrayBuffer가 생성되지 않았습니다");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xlsxArrayBuffer.byteLength === 0) {
|
||||||
|
throw new Error("ArrayBuffer 크기가 0입니다");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 원본 파일명에서 확장자를 .xlsx로 변경
|
||||||
|
const xlsxFileName = file.name.replace(/\.(csv|xls|xlsx)$/i, ".xlsx");
|
||||||
|
|
||||||
|
// 🔍 LuckyExcel로 전달되는 파일 정보 출력
|
||||||
|
console.log("📋 =================================");
|
||||||
|
console.log("📋 LuckyExcel로 전달되는 파일 정보:");
|
||||||
|
console.log("📋 =================================");
|
||||||
|
console.log("📋 타이밍:", new Date().toISOString());
|
||||||
|
console.log("📋 원본 파일명:", file.name);
|
||||||
|
console.log("📋 변환된 파일명:", xlsxFileName);
|
||||||
|
console.log("📋 ArrayBuffer 크기:", xlsxArrayBuffer.byteLength, "bytes");
|
||||||
|
console.log("📋 ArrayBuffer 타입:", xlsxArrayBuffer.constructor.name);
|
||||||
|
|
||||||
|
// ArrayBuffer의 처음 100바이트를 16진수로 출력 (헥스 덤프)
|
||||||
|
const uint8View = new Uint8Array(xlsxArrayBuffer);
|
||||||
|
const firstBytes = Array.from(
|
||||||
|
uint8View.slice(0, Math.min(100, uint8View.length)),
|
||||||
|
)
|
||||||
|
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||||||
|
.join(" ");
|
||||||
|
console.log("📋 ArrayBuffer 처음 100바이트 (hex):", firstBytes);
|
||||||
|
|
||||||
|
// XLSX 파일 시그니처 확인 (PK\x03\x04 또는 50 4B 03 04)
|
||||||
|
const signature = Array.from(uint8View.slice(0, 4))
|
||||||
|
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||||||
|
.join(" ");
|
||||||
|
console.log(
|
||||||
|
"📋 파일 시그니처:",
|
||||||
|
signature,
|
||||||
|
signature === "50 4b 03 04" ? "(✅ 유효한 XLSX)" : "(❌ 잘못된 시그니처)",
|
||||||
|
);
|
||||||
|
console.log("📋 =================================");
|
||||||
|
|
||||||
|
// 🚀 LuckyExcel 호출 직전 최종 검증
|
||||||
|
console.log("🚀 LuckyExcel 호출 직전 최종 검증:");
|
||||||
|
console.log("🚀 ArrayBuffer 타입:", typeof xlsxArrayBuffer);
|
||||||
|
console.log("🚀 ArrayBuffer 생성자 확인:", xlsxArrayBuffer.constructor.name);
|
||||||
|
console.log("🚀 ArrayBuffer 크기:", xlsxArrayBuffer.byteLength);
|
||||||
|
console.log("🚀 ArrayBuffer.isView:", ArrayBuffer.isView(xlsxArrayBuffer));
|
||||||
|
console.log("🚀 fileName:", xlsxFileName, "타입:", typeof xlsxFileName);
|
||||||
|
|
||||||
|
console.log("🚀 LuckyExcel 객체:", typeof LuckyExcel);
|
||||||
|
console.log(
|
||||||
|
"🚀 transformExcelToLucky 함수:",
|
||||||
|
typeof (LuckyExcel as any).transformExcelToLucky,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("🚀 LuckyExcel 호출 시작...");
|
||||||
|
|
||||||
|
// Promise를 사용한 LuckyExcel 처리
|
||||||
|
return new Promise<SheetData[]>((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
// File을 ArrayBuffer로 변환
|
// LuckyExcel API는 (arrayBuffer, successCallback, errorCallback) 형태로 호출
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
// 공식 문서: LuckyExcel.transformExcelToLucky(file, successCallback, errorCallback)
|
||||||
|
(LuckyExcel as any).transformExcelToLucky(
|
||||||
LuckyExcel.transformExcelToLucky(
|
xlsxArrayBuffer,
|
||||||
arrayBuffer, // ArrayBuffer 전달
|
// 성공 콜백 함수 (두 번째 매개변수)
|
||||||
file.name, // 파일명 전달
|
|
||||||
(exportJson: any, luckysheetfile: any) => {
|
(exportJson: any, luckysheetfile: any) => {
|
||||||
try {
|
try {
|
||||||
|
console.log("🍀 =================================");
|
||||||
|
console.log("🍀 LuckyExcel 변환 결과 상세 정보:");
|
||||||
|
console.log("🍀 =================================");
|
||||||
|
console.log("🍀 원본 파일명:", xlsxFileName);
|
||||||
|
console.log("🍀 exportJson 존재:", !!exportJson);
|
||||||
|
console.log("🍀 exportJson 타입:", typeof exportJson);
|
||||||
|
|
||||||
|
if (exportJson) {
|
||||||
|
console.log("🍀 exportJson 전체 구조:", exportJson);
|
||||||
|
console.log("🍀 exportJson.sheets 존재:", !!exportJson.sheets);
|
||||||
|
console.log(
|
||||||
|
"🍀 exportJson.sheets 타입:",
|
||||||
|
typeof exportJson.sheets,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"🍀 exportJson.sheets 배열 여부:",
|
||||||
|
Array.isArray(exportJson.sheets),
|
||||||
|
);
|
||||||
|
console.log("🍀 시트 개수:", exportJson?.sheets?.length || 0);
|
||||||
|
|
||||||
|
if (exportJson.sheets && Array.isArray(exportJson.sheets)) {
|
||||||
|
exportJson.sheets.forEach((sheet: any, index: number) => {
|
||||||
|
console.log(`🍀 시트 ${index + 1}:`, {
|
||||||
|
name: sheet.name,
|
||||||
|
row: sheet.row,
|
||||||
|
column: sheet.column,
|
||||||
|
celldata길이: sheet.celldata?.length || 0,
|
||||||
|
키목록: Object.keys(sheet),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🍀 luckysheetfile 존재:", !!luckysheetfile);
|
||||||
|
console.log("🍀 luckysheetfile 타입:", typeof luckysheetfile);
|
||||||
|
if (luckysheetfile) {
|
||||||
|
console.log("🍀 luckysheetfile 구조:", luckysheetfile);
|
||||||
|
}
|
||||||
|
console.log("🍀 =================================");
|
||||||
|
|
||||||
console.log("🔍 LuckyExcel 변환 결과:", {
|
console.log("🔍 LuckyExcel 변환 결과:", {
|
||||||
hasExportJson: !!exportJson,
|
hasExportJson: !!exportJson,
|
||||||
hasSheets: !!exportJson?.sheets,
|
hasSheets: !!exportJson?.sheets,
|
||||||
@@ -485,41 +691,16 @@ function processWithLuckyExcel(file: File): Promise<SheetData[]> {
|
|||||||
if (
|
if (
|
||||||
!exportJson ||
|
!exportJson ||
|
||||||
!exportJson.sheets ||
|
!exportJson.sheets ||
|
||||||
!Array.isArray(exportJson.sheets)
|
!Array.isArray(exportJson.sheets) ||
|
||||||
|
exportJson.sheets.length === 0
|
||||||
) {
|
) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"⚠️ LuckyExcel 결과가 유효하지 않습니다. SheetJS Fallback을 시도합니다.",
|
"⚠️ LuckyExcel 결과가 유효하지 않습니다. SheetJS 방식으로 대체 처리합니다.",
|
||||||
);
|
);
|
||||||
|
|
||||||
// SheetJS Fallback 처리
|
// LuckyExcel 실패 시 SheetJS 데이터를 직접 변환
|
||||||
processWithSheetJSFallback(file)
|
const fallbackSheets = convertSheetJSToLuckyExcel(workbook);
|
||||||
.then(resolve)
|
resolve(fallbackSheets);
|
||||||
.catch((fallbackError) => {
|
|
||||||
console.error("❌ SheetJS Fallback도 실패:", fallbackError);
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
`파일 처리 실패: ${fallbackError instanceof Error ? fallbackError.message : fallbackError}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exportJson.sheets.length === 0) {
|
|
||||||
console.warn(
|
|
||||||
"⚠️ LuckyExcel이 빈 시트를 반환했습니다. SheetJS Fallback을 시도합니다.",
|
|
||||||
);
|
|
||||||
|
|
||||||
processWithSheetJSFallback(file)
|
|
||||||
.then(resolve)
|
|
||||||
.catch((fallbackError) => {
|
|
||||||
console.error("❌ SheetJS Fallback도 실패:", fallbackError);
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
`파일 처리 실패: ${fallbackError instanceof Error ? fallbackError.message : fallbackError}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -596,24 +777,56 @@ function processWithLuckyExcel(file: File): Promise<SheetData[]> {
|
|||||||
|
|
||||||
console.log("✅ LuckyExcel 처리 성공:", sheets.length, "개 시트");
|
console.log("✅ LuckyExcel 처리 성공:", sheets.length, "개 시트");
|
||||||
resolve(sheets);
|
resolve(sheets);
|
||||||
} catch (e) {
|
} catch (processError) {
|
||||||
console.error("❌ LuckyExcel 후처리 중 오류:", e);
|
console.error("❌ LuckyExcel 후처리 중 오류:", processError);
|
||||||
reject(e);
|
|
||||||
|
// LuckyExcel 후처리 실패 시 SheetJS 방식으로 대체
|
||||||
|
try {
|
||||||
|
console.log("🔄 SheetJS 방식으로 대체 처리...");
|
||||||
|
const fallbackSheets = convertSheetJSToLuckyExcel(workbook);
|
||||||
|
resolve(fallbackSheets);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("❌ SheetJS 대체 처리도 실패:", fallbackError);
|
||||||
|
reject(fallbackError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 오류 콜백 함수 (세 번째 매개변수)
|
||||||
|
(error: any) => {
|
||||||
|
console.error("❌ LuckyExcel 변환 오류:", error);
|
||||||
|
|
||||||
|
// LuckyExcel 오류 시 SheetJS 방식으로 대체
|
||||||
|
try {
|
||||||
|
console.log("🔄 SheetJS 방식으로 대체 처리...");
|
||||||
|
const fallbackSheets = convertSheetJSToLuckyExcel(workbook);
|
||||||
|
resolve(fallbackSheets);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("❌ SheetJS 대체 처리도 실패:", fallbackError);
|
||||||
|
reject(fallbackError);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (luckyError) {
|
||||||
console.error("❌ LuckyExcel 처리 중 오류:", error);
|
console.error("❌ LuckyExcel 호출 중 오류:", luckyError);
|
||||||
reject(error);
|
|
||||||
|
// LuckyExcel 호출 실패 시 SheetJS 방식으로 대체
|
||||||
|
try {
|
||||||
|
console.log("🔄 SheetJS 방식으로 대체 처리...");
|
||||||
|
const fallbackSheets = convertSheetJSToLuckyExcel(workbook);
|
||||||
|
resolve(fallbackSheets);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error("❌ SheetJS 대체 처리도 실패:", fallbackError);
|
||||||
|
reject(fallbackError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 엑셀 파일을 SheetData 배열로 변환 (개선된 버전)
|
* 엑셀 파일을 SheetData 배열로 변환 (개선된 버전)
|
||||||
* - 우선 LuckyExcel로 처리 시도 (XLSX)
|
* - 모든 파일을 SheetJS로 읽은 후 XLSX로 변환
|
||||||
* - CSV, XLS는 SheetJS로 XLSX 변환 후 LuckyExcel 처리
|
* - 변환된 XLSX를 LuckyExcel로 처리
|
||||||
* - 실패 시 SheetJS Fallback 사용
|
* - 실패 시 SheetJS 직접 변환으로 Fallback
|
||||||
*/
|
*/
|
||||||
export async function processExcelFile(file: File): Promise<FileUploadResult> {
|
export async function processExcelFile(file: File): Promise<FileUploadResult> {
|
||||||
try {
|
try {
|
||||||
@@ -647,68 +860,8 @@ export async function processExcelFile(file: File): Promise<FileUploadResult> {
|
|||||||
`📁 파일 처리 시작: ${file.name} (${isCSV ? "CSV" : isXLS ? "XLS" : "XLSX"})`,
|
`📁 파일 처리 시작: ${file.name} (${isCSV ? "CSV" : isXLS ? "XLS" : "XLSX"})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
let sheets: SheetData[];
|
// 통합된 처리 방식: SheetJS → XLSX → LuckyExcel
|
||||||
|
const sheets = await processFileWithSheetJSToXLSX(file);
|
||||||
try {
|
|
||||||
// 1차 시도: LuckyExcel 직접 처리 (XLSX만)
|
|
||||||
if (isXLSX) {
|
|
||||||
console.log("🍀 XLSX 파일 - LuckyExcel 직접 처리 시도");
|
|
||||||
sheets = await processWithLuckyExcel(file);
|
|
||||||
} else {
|
|
||||||
// CSV, XLS는 SheetJS로 XLSX 변환 후 LuckyExcel 처리
|
|
||||||
console.log(
|
|
||||||
`📊 ${isCSV ? "CSV" : "XLS"} 파일 - SheetJS 변환 후 LuckyExcel 처리`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
|
||||||
|
|
||||||
// SheetJS로 읽기 (단순한 옵션 사용)
|
|
||||||
let workbook: any;
|
|
||||||
|
|
||||||
if (isCSV) {
|
|
||||||
// CSV 처리
|
|
||||||
const text = new TextDecoder("utf-8").decode(arrayBuffer);
|
|
||||||
workbook = XLSX.read(text, {
|
|
||||||
type: "string",
|
|
||||||
codepage: 65001,
|
|
||||||
raw: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// XLS 처리
|
|
||||||
workbook = XLSX.read(arrayBuffer, {
|
|
||||||
type: "array",
|
|
||||||
codepage: 65001,
|
|
||||||
raw: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// XLSX로 변환
|
|
||||||
const xlsxBuffer = XLSX.write(workbook, {
|
|
||||||
type: "array",
|
|
||||||
bookType: "xlsx",
|
|
||||||
compression: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// File 객체로 변환하여 LuckyExcel 처리
|
|
||||||
const xlsxFile = new File(
|
|
||||||
[xlsxBuffer],
|
|
||||||
file.name.replace(/\.(csv|xls)$/i, ".xlsx"),
|
|
||||||
{
|
|
||||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
sheets = await processWithLuckyExcel(xlsxFile);
|
|
||||||
}
|
|
||||||
} catch (luckyExcelError) {
|
|
||||||
console.warn(
|
|
||||||
"🔄 LuckyExcel 처리 실패, SheetJS Fallback 시도:",
|
|
||||||
luckyExcelError,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2차 시도: SheetJS Fallback
|
|
||||||
sheets = await processWithSheetJSFallback(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sheets || sheets.length === 0) {
|
if (!sheets || sheets.length === 0) {
|
||||||
return {
|
return {
|
||||||
@@ -736,7 +889,14 @@ export async function processExcelFile(file: File): Promise<FileUploadResult> {
|
|||||||
if (
|
if (
|
||||||
error.message.includes("파일에 워크시트가 없습니다") ||
|
error.message.includes("파일에 워크시트가 없습니다") ||
|
||||||
error.message.includes("워크북 구조 오류") ||
|
error.message.includes("워크북 구조 오류") ||
|
||||||
error.message.includes("파일 처리 실패")
|
error.message.includes("파일 처리 실패") ||
|
||||||
|
error.message.includes("파일 읽기 실패") ||
|
||||||
|
error.message.includes("XLSX 변환 실패") ||
|
||||||
|
error.message.includes("파일이 비어있습니다") ||
|
||||||
|
error.message.includes("워크북을 생성할 수 없습니다") ||
|
||||||
|
error.message.includes("유효한 시트가 없습니다") ||
|
||||||
|
error.message.includes("시트 이름 정보가 없습니다") ||
|
||||||
|
error.message.includes("파일을 읽을 수 없습니다")
|
||||||
) {
|
) {
|
||||||
errorMessage = error.message;
|
errorMessage = error.message;
|
||||||
} else if (error.message.includes("transformExcelToLucky")) {
|
} else if (error.message.includes("transformExcelToLucky")) {
|
||||||
|
|||||||
Reference in New Issue
Block a user