200 lines
7.8 KiB
JavaScript
200 lines
7.8 KiB
JavaScript
/**
|
|
* PM ver4.0 - Local PDF Conversion Worker
|
|
*
|
|
* 이 스크립트는 로컬 개발 환경에서 PDF 변환기(BullMQ Worker) 역할을 수행합니다.
|
|
* 백엔드 서버가 'convert-pdf' 큐에 등록한 작업을 가져와, programs/ 하위의 .exe 변환기를 실행하고
|
|
* 변환된 PDF 파일을 MinIO 스토리지에 업로드한 후 DB를 업데이트합니다.
|
|
*
|
|
* [실행 전 요구사항]
|
|
* 1. .NET 8 런타임 설치 필요 (https://dotnet.microsoft.com/ko-kr/download/dotnet/8.0)
|
|
* 2. 한글 파일(.hwp/.hwpx) 변환을 테스트하려면 한컴오피스 한글이 로컬에 설치되어 있어야 합니다.
|
|
*
|
|
* [실행 방법]
|
|
* node local_pdf_worker.js
|
|
*/
|
|
|
|
const { Worker } = require('bullmq');
|
|
const Redis = require('ioredis');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execFile } = require('child_process');
|
|
const axios = require('axios');
|
|
const { PutObjectCommand } = require('@aws-sdk/client-s3');
|
|
|
|
// 로컬 환경설정 로드
|
|
require('dotenv').config();
|
|
|
|
const pool = require('./db/pool.js');
|
|
const onPremiseClient = require('./config/onPremiseClient.js');
|
|
const cloudClient = require('./config/cloudClient.js');
|
|
|
|
console.log('=============================================');
|
|
console.log(' PM ver4.0 로컬 PDF 변환 워커 시작 중... ');
|
|
console.log('=============================================');
|
|
console.log('Redis Host:', process.env.REDIS_HOST || 'localhost');
|
|
console.log('Redis Port:', process.env.REDIS_PORT || 6379);
|
|
console.log('Database Host:', process.env.ONPREMISE_POSTGRES_HOST || 'localhost');
|
|
console.log('=============================================');
|
|
|
|
const connection = new Redis({
|
|
host: process.env.REDIS_HOST || 'localhost',
|
|
port: +(process.env.REDIS_PORT || 6379),
|
|
maxRetriesPerRequest: null,
|
|
password: process.env.REDIS_PASSWORD || undefined
|
|
});
|
|
|
|
// 임시 디렉토리 생성
|
|
const tempDir = path.join(__dirname, 'temp_convert');
|
|
if (!fs.existsSync(tempDir)) {
|
|
fs.mkdirSync(tempDir);
|
|
}
|
|
|
|
const worker = new Worker('convert-pdf', async (job) => {
|
|
const { resourcePath, url, objectKey, bucket, storageType, dataId, projectId, userInfoString, userIp, initiator, type } = job.data;
|
|
console.log(`\n[Job ${job.id}] 📥 PDF 변환 작업 감지: ${resourcePath} (ID: ${dataId}, Type: ${type})`);
|
|
|
|
// 1. MinIO/S3 클라이언트 결정
|
|
const s3 = storageType === 'Cloud' ? cloudClient : onPremiseClient;
|
|
|
|
// 2. Presigned URL을 사용하여 원본 파일 다운로드
|
|
console.log(`[Job ${job.id}] 🌐 원본 파일 다운로드 중...`);
|
|
const response = await axios.get(url, { responseType: 'arraybuffer' });
|
|
const buffer = Buffer.from(response.data);
|
|
|
|
// 3. 로컬 임시 폴더에 원본 파일 저장
|
|
const origFileName = path.basename(objectKey).split('__')[0];
|
|
const ext = path.extname(origFileName).toLowerCase().replace('.', '');
|
|
const tempInputPath = path.join(tempDir, `input_${job.id}_${Date.now()}.${ext}`);
|
|
fs.writeFileSync(tempInputPath, buffer);
|
|
console.log(`[Job ${job.id}] 💾 원본 임시 저장 완료: ${tempInputPath}`);
|
|
|
|
// 4. 실행할 변환기 프로그램 선정
|
|
let exePath = '';
|
|
if (['hwp', 'hwpx'].includes(ext)) {
|
|
exePath = path.join(__dirname, 'programs/hwpConverter/HwpToPdfConverter.exe');
|
|
} else if (['docx', 'xlsx', 'xlsm', 'xls', 'doc', 'ppt', 'pptx'].includes(ext)) {
|
|
exePath = path.join(__dirname, 'programs/msofficeConverter/OfficeToPDFConverter.exe');
|
|
} else if (['dwg', 'dxf'].includes(ext)) {
|
|
exePath = path.join(__dirname, 'programs/dwgToPdfConverter/DwgToPdfSwigConverter.exe');
|
|
} else {
|
|
throw new Error(`지원하지 않는 파일 형식입니다: ${ext}`);
|
|
}
|
|
|
|
if (!fs.existsSync(exePath)) {
|
|
throw new Error(`변환기 실행 파일을 찾을 수 없습니다: ${exePath}`);
|
|
}
|
|
|
|
const tempOutputPath = tempInputPath.replace(`.${ext}`, '.pdf');
|
|
console.log(`[Job ${job.id}] ⚙️ CLI 변환 실행: ${path.basename(exePath)}`);
|
|
|
|
// 5. CLI 실행을 통해 PDF 변환 수행
|
|
await new Promise((resolve, reject) => {
|
|
execFile(exePath, [tempInputPath, tempOutputPath], (err, stdout, stderr) => {
|
|
if (err) {
|
|
console.error(`[Job ${job.id}] ❌ 변환 CLI 실행 에러:`, err.message);
|
|
return reject(err);
|
|
}
|
|
console.log(`[Job ${job.id}] 📝 변환기 출력:`, stdout.trim());
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
if (!fs.existsSync(tempOutputPath)) {
|
|
throw new Error(`변환 성공했으나 결과 PDF 파일이 존재하지 않습니다: ${tempOutputPath}`);
|
|
}
|
|
|
|
// 6. 생성된 PDF를 MinIO 스토리지에 업로드
|
|
console.log(`[Job ${job.id}] 📤 변환된 PDF MinIO 스토리지 업로드 중...`);
|
|
const pdfBuffer = fs.readFileSync(tempOutputPath);
|
|
|
|
// preview_key, popup_key 네이밍 규칙 적용
|
|
let previewKey = objectKey.replace('archive/origin/', 'archive/preview/').replace(/\.[^.]+$/, '.pdf');
|
|
let popupKey = objectKey.replace('archive/origin/', 'archive/popup/').replace(/\.[^.]+$/, '.pdf');
|
|
|
|
if (previewKey === objectKey) {
|
|
previewKey = `archive/preview/${path.basename(objectKey)}`.replace(/\.[^.]+$/, '.pdf');
|
|
}
|
|
if (popupKey === objectKey) {
|
|
popupKey = `archive/popup/${path.basename(objectKey)}`.replace(/\.[^.]+$/, '.pdf');
|
|
}
|
|
|
|
// preview_key 업로드
|
|
await s3.send(new PutObjectCommand({
|
|
Bucket: bucket,
|
|
Key: previewKey,
|
|
Body: pdfBuffer,
|
|
ContentType: 'application/pdf'
|
|
}));
|
|
|
|
// popup_key 업로드
|
|
await s3.send(new PutObjectCommand({
|
|
Bucket: bucket,
|
|
Key: popupKey,
|
|
Body: pdfBuffer,
|
|
ContentType: 'application/pdf'
|
|
}));
|
|
console.log(`[Job ${job.id}] 🚀 업로드 성공: \n - Preview Key: ${previewKey}\n - Popup Key: ${popupKey}`);
|
|
|
|
// 7. 데이터베이스 레코드 정보 업데이트 (preview_key, popup_key 반영)
|
|
console.log(`[Job ${job.id}] 🗄️ 데이터베이스 정보 갱신 중...`);
|
|
const envSetting = process.env.NODE_ENV || 'development';
|
|
const tbData = envSetting === 'production' ? 'tb_data' : '_test_tb_data';
|
|
|
|
if (type === 'archive') {
|
|
const updateQuery = `
|
|
UPDATE ver4.${tbData}
|
|
SET preview_key = $1, popup_key = $2, mod_date = NOW(), mod_activity = 'convertPdf'
|
|
WHERE data_id = $3
|
|
`;
|
|
await pool.query(updateQuery, [previewKey, popupKey, dataId]);
|
|
} else if (type === 'officialDoc') {
|
|
const updateQuery = `
|
|
UPDATE ver4.tb_official_doc_file
|
|
SET preview_key = $1, popup_key = $2, mod_date = NOW(), mod_activity = 'convertDocPdf'
|
|
WHERE doc_id = $3
|
|
`;
|
|
await pool.query(updateQuery, [previewKey, popupKey, dataId]);
|
|
}
|
|
console.log(`[Job ${job.id}] ✅ 데이터베이스 업데이트 완료.`);
|
|
|
|
// 8. 로컬 임시 파일 삭제
|
|
try {
|
|
fs.unlinkSync(tempInputPath);
|
|
fs.unlinkSync(tempOutputPath);
|
|
console.log(`[Job ${job.id}] 🧹 임시 파일 정리 완료.`);
|
|
} catch (cleanErr) {
|
|
console.warn(`[Job ${job.id}] ⚠️ 임시 파일 제거 실패:`, cleanErr.message);
|
|
}
|
|
|
|
// 완료 후 completed 이벤트 리스너로 데이터 리턴
|
|
return {
|
|
projectId,
|
|
userInfoString,
|
|
userIp,
|
|
resourcePath,
|
|
dataId,
|
|
storageType,
|
|
previewKey,
|
|
popupKey
|
|
};
|
|
}, {
|
|
connection,
|
|
concurrency: 1 // 한 번에 하나의 변환 프로세스만 처리
|
|
});
|
|
|
|
worker.on('ready', () => {
|
|
console.log('🚀 PDF 변환 워커가 준비되었습니다. 작업을 대기 중입니다...');
|
|
});
|
|
|
|
worker.on('active', (job) => {
|
|
console.log(`▶️ Job ${job.id} 시작`);
|
|
});
|
|
|
|
worker.on('completed', (job, result) => {
|
|
console.log(`✔️ Job ${job.id} 완료`);
|
|
});
|
|
|
|
worker.on('failed', (job, err) => {
|
|
console.error(`❌ Job ${job.id} 실패:`, err.message);
|
|
});
|