이미지 미리보기 기능
This commit is contained in:
@@ -17,6 +17,7 @@ import screenManagementRoutes from "./routes/screenManagementRoutes";
|
||||
import commonCodeRoutes from "./routes/commonCodeRoutes";
|
||||
import dynamicFormRoutes from "./routes/dynamicFormRoutes";
|
||||
import fileRoutes from "./routes/fileRoutes";
|
||||
import companyManagementRoutes from "./routes/companyManagementRoutes";
|
||||
// import userRoutes from './routes/userRoutes';
|
||||
// import menuRoutes from './routes/menuRoutes';
|
||||
|
||||
@@ -81,6 +82,7 @@ app.use("/api/screen-management", screenManagementRoutes);
|
||||
app.use("/api/common-codes", commonCodeRoutes);
|
||||
app.use("/api/dynamic-form", dynamicFormRoutes);
|
||||
app.use("/api/files", fileRoutes);
|
||||
app.use("/api/company-management", companyManagementRoutes);
|
||||
// app.use('/api/users', userRoutes);
|
||||
// app.use('/api/menus', menuRoutes);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { PrismaClient } from "@prisma/client";
|
||||
import config from "../config/environment";
|
||||
import { AdminService } from "../services/adminService";
|
||||
import { EncryptUtil } from "../utils/encryptUtil";
|
||||
import { FileSystemManager } from "../utils/fileSystemManager";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -2095,6 +2096,19 @@ export const createCompany = async (
|
||||
const insertResult = await client.query(insertQuery, insertValues);
|
||||
const createdCompany = insertResult.rows[0];
|
||||
|
||||
// 회사 폴더 초기화 (파일 시스템)
|
||||
try {
|
||||
FileSystemManager.initializeCompanyFolder(createdCompany.company_code);
|
||||
logger.info("회사 폴더 초기화 완료", {
|
||||
companyCode: createdCompany.company_code,
|
||||
});
|
||||
} catch (folderError) {
|
||||
logger.warn("회사 폴더 초기화 실패 (회사 등록은 성공)", {
|
||||
companyCode: createdCompany.company_code,
|
||||
error: folderError,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info("회사 등록 성공", {
|
||||
companyCode: createdCompany.company_code,
|
||||
companyName: createdCompany.company_name,
|
||||
|
||||
182
backend-node/src/routes/companyManagementRoutes.ts
Normal file
182
backend-node/src/routes/companyManagementRoutes.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import express from "express";
|
||||
import { authenticateToken } from "../middleware/authMiddleware";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { logger } from "../utils/logger";
|
||||
import { FileSystemManager } from "../utils/fileSystemManager";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const router = express.Router();
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// 모든 라우트에 인증 미들웨어 적용
|
||||
router.use(authenticateToken);
|
||||
|
||||
/**
|
||||
* DELETE /api/company-management/:companyCode
|
||||
* 회사 삭제 및 파일 정리
|
||||
*/
|
||||
router.delete(
|
||||
"/:companyCode",
|
||||
async (req: AuthenticatedRequest, res): Promise<void> => {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
const { createBackup = true } = req.body;
|
||||
|
||||
logger.info("회사 삭제 요청", {
|
||||
companyCode,
|
||||
createBackup,
|
||||
userId: req.user?.userId,
|
||||
});
|
||||
|
||||
// 1. 회사 존재 확인
|
||||
const existingCompany = await prisma.company_mng.findUnique({
|
||||
where: { company_code: companyCode },
|
||||
});
|
||||
|
||||
if (!existingCompany) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: "존재하지 않는 회사입니다.",
|
||||
errorCode: "COMPANY_NOT_FOUND",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 회사 파일 정리 (백업 또는 삭제)
|
||||
try {
|
||||
await FileSystemManager.cleanupCompanyFiles(companyCode, createBackup);
|
||||
logger.info("회사 파일 정리 완료", { companyCode, createBackup });
|
||||
} catch (fileError) {
|
||||
logger.error("회사 파일 정리 실패", { companyCode, error: fileError });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "회사 파일 정리 중 오류가 발생했습니다.",
|
||||
error:
|
||||
fileError instanceof Error ? fileError.message : "Unknown error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 데이터베이스에서 회사 삭제 (soft delete)
|
||||
await prisma.company_mng.update({
|
||||
where: { company_code: companyCode },
|
||||
data: {
|
||||
status: "deleted",
|
||||
},
|
||||
});
|
||||
|
||||
logger.info("회사 삭제 완료", {
|
||||
companyCode,
|
||||
companyName: existingCompany.company_name,
|
||||
deletedBy: req.user?.userId,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `회사 '${existingCompany.company_name}'이(가) 성공적으로 삭제되었습니다.`,
|
||||
data: {
|
||||
companyCode,
|
||||
companyName: existingCompany.company_name,
|
||||
backupCreated: createBackup,
|
||||
deletedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("회사 삭제 실패", {
|
||||
error,
|
||||
companyCode: req.params.companyCode,
|
||||
});
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "회사 삭제 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/company-management/:companyCode/disk-usage
|
||||
* 회사별 디스크 사용량 조회
|
||||
*/
|
||||
router.get(
|
||||
"/:companyCode/disk-usage",
|
||||
async (req: AuthenticatedRequest, res): Promise<void> => {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
|
||||
const diskUsage = FileSystemManager.getCompanyDiskUsage(companyCode);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
companyCode,
|
||||
fileCount: diskUsage.fileCount,
|
||||
totalSize: diskUsage.totalSize,
|
||||
totalSizeMB:
|
||||
Math.round((diskUsage.totalSize / 1024 / 1024) * 100) / 100,
|
||||
lastChecked: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("디스크 사용량 조회 실패", {
|
||||
error,
|
||||
companyCode: req.params.companyCode,
|
||||
});
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "디스크 사용량 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/company-management/disk-usage/all
|
||||
* 전체 회사 디스크 사용량 조회
|
||||
*/
|
||||
router.get(
|
||||
"/disk-usage/all",
|
||||
async (req: AuthenticatedRequest, res): Promise<void> => {
|
||||
try {
|
||||
const allUsage = FileSystemManager.getAllCompaniesDiskUsage();
|
||||
|
||||
const totalStats = allUsage.reduce(
|
||||
(acc, company) => ({
|
||||
totalFiles: acc.totalFiles + company.fileCount,
|
||||
totalSize: acc.totalSize + company.totalSize,
|
||||
}),
|
||||
{ totalFiles: 0, totalSize: 0 }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
companies: allUsage.map((company) => ({
|
||||
...company,
|
||||
totalSizeMB:
|
||||
Math.round((company.totalSize / 1024 / 1024) * 100) / 100,
|
||||
})),
|
||||
summary: {
|
||||
totalCompanies: allUsage.length,
|
||||
totalFiles: totalStats.totalFiles,
|
||||
totalSize: totalStats.totalSize,
|
||||
totalSizeMB:
|
||||
Math.round((totalStats.totalSize / 1024 / 1024) * 100) / 100,
|
||||
},
|
||||
lastChecked: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("전체 디스크 사용량 조회 실패", { error });
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "전체 디스크 사용량 조회 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -5,6 +5,7 @@ import fs from "fs";
|
||||
import { authenticateToken } from "../middleware/authMiddleware";
|
||||
import { AuthenticatedRequest } from "../types/auth";
|
||||
import { logger } from "../utils/logger";
|
||||
import { FileSystemManager } from "../utils/fileSystemManager";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -16,21 +17,53 @@ if (!fs.existsSync(UPLOAD_PATH)) {
|
||||
fs.mkdirSync(UPLOAD_PATH, { recursive: true });
|
||||
}
|
||||
|
||||
// Multer 설정 - 파일 업로드용
|
||||
// Multer 설정 - 회사별 폴더 구조 지원
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
return cb(null, UPLOAD_PATH);
|
||||
try {
|
||||
// 사용자의 회사 코드 가져오기
|
||||
const user = (req as AuthenticatedRequest).user;
|
||||
const companyCode = user?.companyCode || "default";
|
||||
|
||||
// 회사별 날짜별 폴더 생성
|
||||
const uploadPath = FileSystemManager.createCompanyUploadPath(companyCode);
|
||||
|
||||
logger.info("파일 업로드 대상 폴더", {
|
||||
companyCode,
|
||||
uploadPath,
|
||||
userId: user?.userId,
|
||||
});
|
||||
|
||||
return cb(null, uploadPath);
|
||||
} catch (error) {
|
||||
logger.error("업로드 폴더 생성 실패", error);
|
||||
return cb(error as Error, "");
|
||||
}
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
// 파일명: timestamp_originalname
|
||||
const timestamp = Date.now();
|
||||
const originalName = Buffer.from(file.originalname, "latin1").toString(
|
||||
"utf8"
|
||||
);
|
||||
const ext = path.extname(originalName);
|
||||
const nameWithoutExt = path.basename(originalName, ext);
|
||||
const safeFileName = `${timestamp}_${nameWithoutExt}${ext}`;
|
||||
return cb(null, safeFileName);
|
||||
try {
|
||||
// 사용자의 회사 코드 가져오기
|
||||
const user = (req as AuthenticatedRequest).user;
|
||||
const companyCode = user?.companyCode || "default";
|
||||
|
||||
// 회사코드가 포함된 안전한 파일명 생성
|
||||
const safeFileName = FileSystemManager.generateSafeFileName(
|
||||
file.originalname,
|
||||
companyCode
|
||||
);
|
||||
|
||||
logger.info("파일명 생성", {
|
||||
originalName: file.originalname,
|
||||
safeFileName,
|
||||
companyCode,
|
||||
userId: user?.userId,
|
||||
});
|
||||
|
||||
return cb(null, safeFileName);
|
||||
} catch (error) {
|
||||
logger.error("파일명 생성 실패", error);
|
||||
return cb(error as Error, "");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -251,6 +284,128 @@ router.delete(
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 이미지 미리보기
|
||||
* GET /api/files/preview/:fileId
|
||||
*/
|
||||
router.get(
|
||||
"/preview/:fileId",
|
||||
async (req: AuthenticatedRequest, res): Promise<void> => {
|
||||
try {
|
||||
const { fileId } = req.params;
|
||||
const { serverFilename } = req.query;
|
||||
|
||||
if (!serverFilename) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "서버 파일명이 필요합니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 회사별 폴더 구조를 고려하여 파일 경로 찾기
|
||||
const user = req.user;
|
||||
const companyCode = user?.companyCode || "default";
|
||||
|
||||
// 먼저 회사별 폴더에서 찾기
|
||||
let filePath = FileSystemManager.findFileInCompanyFolders(
|
||||
companyCode,
|
||||
serverFilename as string
|
||||
);
|
||||
|
||||
// 찾지 못하면 기본 uploads 폴더에서 찾기
|
||||
if (!filePath) {
|
||||
filePath = path.join(UPLOAD_PATH, serverFilename as string);
|
||||
}
|
||||
|
||||
// 파일 존재 확인
|
||||
if (!fs.existsSync(filePath)) {
|
||||
logger.warn("이미지 파일을 찾을 수 없음", {
|
||||
fileId,
|
||||
serverFilename,
|
||||
filePath,
|
||||
userId: user?.userId,
|
||||
});
|
||||
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
message: "요청한 이미지 파일을 찾을 수 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 파일 확장자로 MIME 타입 결정
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const mimeTypes: { [key: string]: string } = {
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".bmp": "image/bmp",
|
||||
".svg": "image/svg+xml",
|
||||
};
|
||||
|
||||
const mimeType = mimeTypes[ext] || "application/octet-stream";
|
||||
|
||||
// 이미지 파일이 아닌 경우 에러 반환
|
||||
if (!mimeType.startsWith("image/")) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "이미지 파일이 아닙니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 파일 정보 확인
|
||||
const stats = fs.statSync(filePath);
|
||||
|
||||
logger.info("이미지 미리보기 요청", {
|
||||
fileId,
|
||||
serverFilename,
|
||||
mimeType,
|
||||
fileSize: stats.size,
|
||||
userId: user?.userId,
|
||||
});
|
||||
|
||||
// 캐시 헤더 설정 (이미지는 캐시 가능)
|
||||
res.setHeader("Content-Type", mimeType);
|
||||
res.setHeader("Content-Length", stats.size);
|
||||
res.setHeader("Cache-Control", "public, max-age=86400"); // 24시간 캐시
|
||||
res.setHeader("Last-Modified", stats.mtime.toUTCString());
|
||||
|
||||
// If-Modified-Since 헤더 확인
|
||||
const ifModifiedSince = req.headers["if-modified-since"];
|
||||
if (ifModifiedSince && new Date(ifModifiedSince) >= stats.mtime) {
|
||||
res.status(304).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// 파일 스트림으로 전송
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
|
||||
fileStream.on("error", (error) => {
|
||||
logger.error("이미지 스트림 오류:", error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "이미지 전송 중 오류가 발생했습니다.",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
fileStream.pipe(res);
|
||||
} catch (error) {
|
||||
logger.error("이미지 미리보기 오류:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: "이미지 미리보기 중 오류가 발생했습니다.",
|
||||
error: error instanceof Error ? error.message : "알 수 없는 오류",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 파일 정보 조회
|
||||
* GET /api/files/info/:fileId
|
||||
|
||||
280
backend-node/src/utils/fileSystemManager.ts
Normal file
280
backend-node/src/utils/fileSystemManager.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { logger } from "./logger";
|
||||
|
||||
/**
|
||||
* 파일 시스템 관리 유틸리티
|
||||
* 회사별 폴더 구조 관리
|
||||
*/
|
||||
export class FileSystemManager {
|
||||
private static readonly BASE_UPLOAD_PATH = path.join(
|
||||
process.cwd(),
|
||||
"uploads"
|
||||
);
|
||||
private static readonly SHARED_FOLDER = "shared";
|
||||
|
||||
/**
|
||||
* 회사별 업로드 경로 생성
|
||||
* @param companyCode 회사 코드
|
||||
* @param date 업로드 날짜 (선택적)
|
||||
* @returns 생성된 폴더 경로
|
||||
*/
|
||||
static createCompanyUploadPath(companyCode: string, date?: Date): string {
|
||||
const uploadDate = date || new Date();
|
||||
const year = uploadDate.getFullYear();
|
||||
const month = String(uploadDate.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(uploadDate.getDate()).padStart(2, "0");
|
||||
|
||||
const folderPath = path.join(
|
||||
this.BASE_UPLOAD_PATH,
|
||||
`company_${companyCode}`,
|
||||
String(year),
|
||||
month,
|
||||
day
|
||||
);
|
||||
|
||||
// 폴더가 없으면 생성 (recursive)
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
fs.mkdirSync(folderPath, { recursive: true });
|
||||
logger.info(`회사별 업로드 폴더 생성: ${folderPath}`);
|
||||
}
|
||||
|
||||
return folderPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회사 등록 시 기본 폴더 구조 생성
|
||||
* @param companyCode 회사 코드
|
||||
*/
|
||||
static initializeCompanyFolder(companyCode: string): void {
|
||||
try {
|
||||
const companyBasePath = path.join(
|
||||
this.BASE_UPLOAD_PATH,
|
||||
`company_${companyCode}`
|
||||
);
|
||||
|
||||
if (!fs.existsSync(companyBasePath)) {
|
||||
fs.mkdirSync(companyBasePath, { recursive: true });
|
||||
|
||||
// README 파일 생성 (폴더 설명)
|
||||
const readmePath = path.join(companyBasePath, "README.txt");
|
||||
const readmeContent = `회사 코드: ${companyCode}
|
||||
생성일: ${new Date().toISOString()}
|
||||
폴더 구조: YYYY/MM/DD/파일명
|
||||
관리자: 시스템 자동 생성`;
|
||||
|
||||
fs.writeFileSync(readmePath, readmeContent, "utf8");
|
||||
|
||||
logger.info(`회사 폴더 초기화 완료: ${companyCode}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`회사 폴더 초기화 실패: ${companyCode}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 회사 삭제 시 폴더 및 파일 정리
|
||||
* @param companyCode 회사 코드
|
||||
* @param createBackup 백업 생성 여부
|
||||
*/
|
||||
static async cleanupCompanyFiles(
|
||||
companyCode: string,
|
||||
createBackup: boolean = true
|
||||
): Promise<void> {
|
||||
try {
|
||||
const companyPath = path.join(
|
||||
this.BASE_UPLOAD_PATH,
|
||||
`company_${companyCode}`
|
||||
);
|
||||
|
||||
if (!fs.existsSync(companyPath)) {
|
||||
logger.warn(`삭제할 회사 폴더가 없습니다: ${companyCode}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 백업 생성
|
||||
if (createBackup) {
|
||||
const backupPath = path.join(
|
||||
this.BASE_UPLOAD_PATH,
|
||||
"deleted_companies",
|
||||
`${companyCode}_${Date.now()}`
|
||||
);
|
||||
|
||||
if (!fs.existsSync(path.dirname(backupPath))) {
|
||||
fs.mkdirSync(path.dirname(backupPath), { recursive: true });
|
||||
}
|
||||
|
||||
// 폴더 이동 (백업)
|
||||
fs.renameSync(companyPath, backupPath);
|
||||
logger.info(`회사 파일 백업 완료: ${companyCode} -> ${backupPath}`);
|
||||
} else {
|
||||
// 완전 삭제
|
||||
fs.rmSync(companyPath, { recursive: true, force: true });
|
||||
logger.info(`회사 파일 완전 삭제: ${companyCode}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`회사 파일 정리 실패: ${companyCode}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통 파일 경로 (시스템 관리용)
|
||||
*/
|
||||
static getSharedPath(): string {
|
||||
const sharedPath = path.join(this.BASE_UPLOAD_PATH, this.SHARED_FOLDER);
|
||||
|
||||
if (!fs.existsSync(sharedPath)) {
|
||||
fs.mkdirSync(sharedPath, { recursive: true });
|
||||
}
|
||||
|
||||
return sharedPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회사별 디스크 사용량 조회
|
||||
* @param companyCode 회사 코드
|
||||
*/
|
||||
static getCompanyDiskUsage(companyCode: string): {
|
||||
fileCount: number;
|
||||
totalSize: number;
|
||||
} {
|
||||
try {
|
||||
const companyPath = path.join(
|
||||
this.BASE_UPLOAD_PATH,
|
||||
`company_${companyCode}`
|
||||
);
|
||||
|
||||
if (!fs.existsSync(companyPath)) {
|
||||
return { fileCount: 0, totalSize: 0 };
|
||||
}
|
||||
|
||||
let fileCount = 0;
|
||||
let totalSize = 0;
|
||||
|
||||
const scanDirectory = (dirPath: string) => {
|
||||
const items = fs.readdirSync(dirPath);
|
||||
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(dirPath, item);
|
||||
const stats = fs.statSync(itemPath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
scanDirectory(itemPath);
|
||||
} else {
|
||||
fileCount++;
|
||||
totalSize += stats.size;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scanDirectory(companyPath);
|
||||
|
||||
return { fileCount, totalSize };
|
||||
} catch (error) {
|
||||
logger.error(`디스크 사용량 조회 실패: ${companyCode}`, error);
|
||||
return { fileCount: 0, totalSize: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 전체 시스템 디스크 사용량 조회
|
||||
*/
|
||||
static getAllCompaniesDiskUsage(): Array<{
|
||||
companyCode: string;
|
||||
fileCount: number;
|
||||
totalSize: number;
|
||||
}> {
|
||||
try {
|
||||
const baseDir = this.BASE_UPLOAD_PATH;
|
||||
|
||||
if (!fs.existsSync(baseDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const companies = fs
|
||||
.readdirSync(baseDir)
|
||||
.filter((item) => item.startsWith("company_"))
|
||||
.map((folder) => folder.replace("company_", ""));
|
||||
|
||||
return companies.map((companyCode) => ({
|
||||
companyCode,
|
||||
...this.getCompanyDiskUsage(companyCode),
|
||||
}));
|
||||
} catch (error) {
|
||||
logger.error("전체 디스크 사용량 조회 실패", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 안전한 파일명 생성 (기존 로직 + 회사 정보)
|
||||
* @param originalName 원본 파일명
|
||||
* @param companyCode 회사 코드
|
||||
*/
|
||||
static generateSafeFileName(
|
||||
originalName: string,
|
||||
companyCode: string
|
||||
): string {
|
||||
const timestamp = Date.now();
|
||||
const cleanName = Buffer.from(originalName, "latin1").toString("utf8");
|
||||
const ext = path.extname(cleanName);
|
||||
const nameWithoutExt = path.basename(cleanName, ext);
|
||||
|
||||
// 회사코드_타임스탬프_파일명 형식
|
||||
return `${companyCode}_${timestamp}_${nameWithoutExt}${ext}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회사별 폴더에서 파일 찾기
|
||||
* @param companyCode 회사 코드
|
||||
* @param serverFilename 서버 파일명
|
||||
* @returns 파일 경로 (찾지 못하면 null)
|
||||
*/
|
||||
static findFileInCompanyFolders(
|
||||
companyCode: string,
|
||||
serverFilename: string
|
||||
): string | null {
|
||||
try {
|
||||
const companyBasePath = path.join(
|
||||
this.BASE_UPLOAD_PATH,
|
||||
`company_${companyCode}`
|
||||
);
|
||||
|
||||
if (!fs.existsSync(companyBasePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 재귀적으로 폴더 탐색
|
||||
const findFileRecursively = (dirPath: string): string | null => {
|
||||
try {
|
||||
const items = fs.readdirSync(dirPath);
|
||||
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(dirPath, item);
|
||||
const stats = fs.statSync(itemPath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
// 하위 폴더에서 재귀 검색
|
||||
const found = findFileRecursively(itemPath);
|
||||
if (found) return found;
|
||||
} else if (item === serverFilename) {
|
||||
// 파일을 찾았으면 경로 반환
|
||||
return itemPath;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`폴더 탐색 오류: ${dirPath}`, error);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return findFileRecursively(companyBasePath);
|
||||
} catch (error) {
|
||||
logger.error(`파일 찾기 실패: ${companyCode}/${serverFilename}`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user