바코드 업데이트 및 AI LLm 적용용

This commit is contained in:
2026-03-05 19:08:08 +09:00
parent 5f3b144b12
commit bfd97c9717
62 changed files with 9570 additions and 55 deletions

View File

@@ -38,13 +38,16 @@ process.on("uncaughtException", (error: Error) => {
// SIGTERM 시그널 처리 (Docker/Kubernetes 환경)
process.on("SIGTERM", () => {
logger.info("📴 SIGTERM 시그널 수신, graceful shutdown 시작...");
// 여기서 연결 풀 정리 등 cleanup 로직 추가 가능
const { stopAiAssistant } = require("./utils/startAiAssistant");
stopAiAssistant();
process.exit(0);
});
// SIGINT 시그널 처리 (Ctrl+C)
process.on("SIGINT", () => {
logger.info("📴 SIGINT 시그널 수신, graceful shutdown 시작...");
const { stopAiAssistant } = require("./utils/startAiAssistant");
stopAiAssistant();
process.exit(0);
});
@@ -127,6 +130,7 @@ import cascadingHierarchyRoutes from "./routes/cascadingHierarchyRoutes"; // 다
import categoryValueCascadingRoutes from "./routes/categoryValueCascadingRoutes"; // 카테고리 값 연쇄관계
import categoryTreeRoutes from "./routes/categoryTreeRoutes"; // 카테고리 트리 (테스트)
import processWorkStandardRoutes from "./routes/processWorkStandardRoutes"; // 공정 작업기준
import aiAssistantProxy from "./routes/aiAssistantProxy"; // AI 어시스턴트 API 프록시 (같은 포트로 서비스)
import { BatchSchedulerService } from "./services/batchSchedulerService";
// import collectionRoutes from "./routes/collectionRoutes"; // 임시 주석
// import batchRoutes from "./routes/batchRoutes"; // 임시 주석
@@ -313,6 +317,7 @@ app.use("/api/category-value-cascading", categoryValueCascadingRoutes); // 카
app.use("/api/category-tree", categoryTreeRoutes); // 카테고리 트리 (테스트)
app.use("/api/process-work-standard", processWorkStandardRoutes); // 공정 작업기준
app.use("/api", screenEmbeddingRoutes); // 화면 임베딩 및 데이터 전달
app.use("/api/ai/v1", aiAssistantProxy); // AI 어시스턴트 (동일 서비스 내 프록시 → AI 서비스 포트)
app.use("/api/vehicle", vehicleTripRoutes); // 차량 운행 이력 관리
// app.use("/api/collections", collectionRoutes); // 임시 주석
// app.use("/api/batch", batchRoutes); // 임시 주석
@@ -398,6 +403,14 @@ app.listen(PORT, HOST, async () => {
} catch (error) {
logger.error(`❌ 메일 자동 삭제 스케줄러 시작 실패:`, error);
}
// AI 어시스턴트 서비스 함께 기동 (한 번에 킬 가능)
try {
const { startAiAssistant } = await import("./utils/startAiAssistant");
startAiAssistant();
} catch (error) {
logger.warn("⚠️ AI 어시스턴트 기동 스킵:", error);
}
});
export default app;

View File

@@ -0,0 +1,31 @@
/**
* AI 어시스턴트 API 프록시
* - /api/ai/v1/* 요청을 AI 서비스(기본 3100 포트)로 전달
* - VEXPLOR와 같은 서비스로 쓰려면: 프론트(9771) → 백엔드(8080) → 여기서 3100으로 프록시
*/
import { createProxyMiddleware } from "http-proxy-middleware";
import type { RequestHandler } from "express";
const AI_SERVICE_URL =
process.env.AI_ASSISTANT_SERVICE_URL || "http://127.0.0.1:3100";
const aiAssistantProxy: RequestHandler = createProxyMiddleware({
target: AI_SERVICE_URL,
changeOrigin: true,
pathRewrite: { "^/api/ai/v1": "/api/v1" },
// 대상 서비스 미기동 시 502 등 에러 처리 (v3 타입에 없을 수 있음)
onError: (_err, _req, res) => {
if (!res.headersSent) {
res.status(502).json({
success: false,
error: {
code: "AI_SERVICE_UNAVAILABLE",
message:
"AI 어시스턴트 서비스를 사용할 수 없습니다. AI 서비스(기본 3100 포트)를 기동한 뒤 다시 시도하세요.",
},
});
}
},
} as Parameters<typeof createProxyMiddleware>[0]);
export default aiAssistantProxy;

View File

@@ -0,0 +1,65 @@
/**
* AI 어시스턴트 서비스를 자식 프로세스로 기동
* - backend-node 서버 기동 시 함께 띄우고, 종료 시 함께 종료 (한 번에 킬)
*/
import path from "path";
import { spawn, ChildProcess } from "child_process";
import { logger } from "./logger";
const AI_PORT = process.env.AI_ASSISTANT_SERVICE_PORT || "3100";
let aiAssistantProcess: ChildProcess | null = null;
/** ERP-node/ai-assistant 경로 (backend-node 기준 상대) */
function getAiAssistantDir(): string {
return path.resolve(process.cwd(), "..", "ai-assistant");
}
/**
* AI 어시스턴트 서비스 기동 (있으면 띄움, 실패해도 backend는 계속 동작)
*/
export function startAiAssistant(): void {
const aiDir = getAiAssistantDir();
const appPath = path.join(aiDir, "src", "app.js");
try {
const fs = require("fs");
if (!fs.existsSync(appPath)) {
logger.info(`⏭️ AI 어시스턴트 스킵 (경로 없음: ${appPath})`);
return;
}
} catch {
return;
}
aiAssistantProcess = spawn("node", ["src/app.js"], {
cwd: aiDir,
stdio: "inherit",
env: { ...process.env, PORT: AI_PORT },
shell: true, // Windows에서 node 경로 인식
});
aiAssistantProcess.on("error", (err) => {
logger.warn(`⚠️ AI 어시스턴트 프로세스 에러: ${err.message}`);
});
aiAssistantProcess.on("exit", (code, signal) => {
aiAssistantProcess = null;
if (code != null && code !== 0) {
logger.warn(`⚠️ AI 어시스턴트 종료 (code=${code}, signal=${signal})`);
}
});
logger.info(`🤖 AI 어시스턴트 서비스 기동 (포트 ${AI_PORT}, cwd: ${aiDir})`);
}
/**
* AI 어시스턴트 프로세스 종료 (SIGTERM/SIGINT 시 호출)
*/
export function stopAiAssistant(): void {
if (aiAssistantProcess && aiAssistantProcess.kill) {
aiAssistantProcess.kill("SIGTERM");
aiAssistantProcess = null;
logger.info("🤖 AI 어시스턴트 프로세스 종료");
}
}