From 444c087a113c634265af1b5a2be98e3eae8bab26 Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 6 Feb 2026 16:23:38 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20API=20URL=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8C=8C=EC=9D=BC=20=EB=AF=B8=EB=A6=AC=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - next.config.mjs에서 API 프록시 설정을 Docker 환경에 맞게 수정하여, SERVER_API_URL을 사용하도록 변경했습니다. - InteractiveDataTable 및 TableListComponent에서 상대 경로 대신 getFilePreviewUrl 함수를 사용하여 전체 URL을 사용하도록 개선했습니다. - FileManagerModal 및 FileUploadComponent에서 Blob 다운로드 방식을 apiClient를 통해 통일하여 Docker 환경에서의 호환성을 높였습니다. - 불필요한 previewUrl 사용을 제거하고, 항상 apiClient를 통해 파일을 다운로드하도록 변경했습니다. --- .../screen/InteractiveDataTable.tsx | 3 ++- frontend/lib/api/file.ts | 6 ++--- .../v2-file-upload/FileManagerModal.tsx | 14 ++--------- .../v2-file-upload/FileUploadComponent.tsx | 24 ++++++------------- .../v2-table-list/TableListComponent.tsx | 4 +++- frontend/next.config.mjs | 5 +++- 6 files changed, 21 insertions(+), 35 deletions(-) diff --git a/frontend/components/screen/InteractiveDataTable.tsx b/frontend/components/screen/InteractiveDataTable.tsx index 582aa413..8efde578 100644 --- a/frontend/components/screen/InteractiveDataTable.tsx +++ b/frontend/components/screen/InteractiveDataTable.tsx @@ -2230,8 +2230,9 @@ export const InteractiveDataTable: React.FC = ({ // value가 objid (숫자 또는 숫자 문자열)인 경우 파일 API URL 사용 // 🔑 download 대신 preview 사용 (공개 접근 허용) const isObjid = /^\d+$/.test(String(value)); + // 🔑 상대 경로(/api/...) 대신 전체 URL 사용 (Docker 환경에서 Next.js rewrite 의존 방지) const imageUrl = isObjid - ? `/api/files/preview/${value}` + ? getFilePreviewUrl(String(value)) : getFullImageUrl(String(value)); return ( diff --git a/frontend/lib/api/file.ts b/frontend/lib/api/file.ts index 042c555c..0eaf5579 100644 --- a/frontend/lib/api/file.ts +++ b/frontend/lib/api/file.ts @@ -1,4 +1,4 @@ -import { apiClient } from "./client"; +import { apiClient, API_BASE_URL } from "./client"; export interface FileInfo { id: string; @@ -231,10 +231,10 @@ export const getLinkedFiles = async ( /** * 파일 미리보기 URL 생성 + * 🔑 상대 경로(/api) 대신 API_BASE_URL 사용 (Docker 환경에서 Next.js rewrite 의존 방지) */ export const getFilePreviewUrl = (fileId: string): string => { - const baseUrl = process.env.NEXT_PUBLIC_API_URL || "/api"; - return `${baseUrl}/files/preview/${fileId}`; + return `${API_BASE_URL}/files/preview/${fileId}`; }; /** diff --git a/frontend/lib/registry/components/v2-file-upload/FileManagerModal.tsx b/frontend/lib/registry/components/v2-file-upload/FileManagerModal.tsx index 3c00aeca..f6586866 100644 --- a/frontend/lib/registry/components/v2-file-upload/FileManagerModal.tsx +++ b/frontend/lib/registry/components/v2-file-upload/FileManagerModal.tsx @@ -163,17 +163,12 @@ export const FileManagerModal: React.FC = ({ const ext = file.fileExt.toLowerCase().replace('.', ''); if (imageExtensions.includes(ext) || file.isImage) { try { - // 🔑 이미 previewUrl이 있으면 바로 사용 - if (file.previewUrl) { - setPreviewImageUrl(file.previewUrl); - return; - } - // 이전 Blob URL 해제 if (previewImageUrl) { URL.revokeObjectURL(previewImageUrl); } + // 🔑 항상 apiClient를 통해 Blob 다운로드 (Docker 환경에서 상대 경로 문제 방지) const { apiClient } = await import("@/lib/api/client"); const response = await apiClient.get(`/files/preview/${file.objid}`, { responseType: 'blob' @@ -184,12 +179,7 @@ export const FileManagerModal: React.FC = ({ setPreviewImageUrl(blobUrl); } catch (error) { console.error("이미지 로드 실패:", error); - // 🔑 에러 발생 시에도 previewUrl이 있으면 사용 - if (file.previewUrl) { - setPreviewImageUrl(file.previewUrl); - } else { - setPreviewImageUrl(null); - } + setPreviewImageUrl(null); } } else { setPreviewImageUrl(null); diff --git a/frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx b/frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx index d716b9ab..9af1a58a 100644 --- a/frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx +++ b/frontend/lib/registry/components/v2-file-upload/FileUploadComponent.tsx @@ -3,7 +3,7 @@ import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { toast } from "sonner"; -import { uploadFiles, downloadFile, deleteFile, getComponentFiles, getFileInfoByObjid } from "@/lib/api/file"; +import { uploadFiles, downloadFile, deleteFile, getComponentFiles, getFileInfoByObjid, getFilePreviewUrl } from "@/lib/api/file"; import { GlobalFileManager } from "@/lib/api/globalFile"; import { formatFileSize } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -205,9 +205,7 @@ const FileUploadComponent: React.FC = ({ return; } - const previewUrl = `/api/files/preview/${objidStr}`; - - // 🔑 실제 파일 정보 조회 + // 🔑 실제 파일 정보 조회 (previewUrl 제거 - apiClient blob 다운로드 방식으로 통일) (async () => { try { const fileInfoResponse = await getFileInfoByObjid(objidStr); @@ -220,15 +218,14 @@ const FileUploadComponent: React.FC = ({ realFileName: realFileName, fileExt: fileExt, fileSize: fileSize, - filePath: previewUrl, + filePath: getFilePreviewUrl(objidStr), regdate: regdate, isImage: true, - previewUrl: previewUrl, isRepresentative: isRepresentative, }; setUploadedFiles([fileInfo]); - setRepresentativeImageUrl(previewUrl); + // representativeImageUrl은 loadRepresentativeImage에서 blob으로 로드됨 } else { // 파일 정보 조회 실패 시 최소 정보로 추가 console.warn("🖼️ [FileUploadComponent] 파일 정보 조회 실패, 최소 정보 사용"); @@ -237,14 +234,13 @@ const FileUploadComponent: React.FC = ({ realFileName: `image_${objidStr}.jpg`, fileExt: '.jpg', fileSize: 0, - filePath: previewUrl, + filePath: getFilePreviewUrl(objidStr), regdate: new Date().toISOString(), isImage: true, - previewUrl: previewUrl, }; setUploadedFiles([minimalFileInfo]); - setRepresentativeImageUrl(previewUrl); + // representativeImageUrl은 loadRepresentativeImage에서 blob으로 로드됨 } } catch (error) { console.error("🖼️ [FileUploadComponent] 파일 정보 조회 오류:", error); @@ -875,14 +871,8 @@ const FileUploadComponent: React.FC = ({ return; } - // 🔑 이미 previewUrl이 설정된 경우 바로 사용 (API 호출 스킵) - if (file.previewUrl) { - setRepresentativeImageUrl(file.previewUrl); - return; - } - // API 클라이언트를 통해 Blob으로 다운로드 (인증 토큰 포함) - // 🔑 download 대신 preview 사용 (공개 접근) + // 🔑 previewUrl 상대 경로 대신 apiClient를 사용하여 Docker 환경에서도 정상 동작 const response = await apiClient.get(`/files/preview/${file.objid}`, { params: { serverFilename: file.savedFileName, diff --git a/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx b/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx index b820d370..6e529ab9 100644 --- a/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/v2-table-list/TableListComponent.tsx @@ -8,6 +8,7 @@ import { entityJoinApi } from "@/lib/api/entityJoin"; import { codeCache } from "@/lib/caching/codeCache"; import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization"; import { getFullImageUrl } from "@/lib/api/client"; +import { getFilePreviewUrl } from "@/lib/api/file"; import { Button } from "@/components/ui/button"; import { v2EventBus, V2_EVENTS, V2ErrorBoundary } from "@/lib/v2-core"; @@ -4066,8 +4067,9 @@ export const TableListComponent: React.FC = ({ // 🔑 download 대신 preview 사용 (공개 접근 허용) const strValue = String(value); const isObjid = /^\d+$/.test(strValue); + // 🔑 상대 경로(/api/...) 대신 전체 URL 사용 (Docker 환경에서 Next.js rewrite 의존 방지) const imageUrl = isObjid - ? `/api/files/preview/${strValue}` + ? getFilePreviewUrl(strValue) : getFullImageUrl(strValue); return (
diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index 2e23bc81..262c4deb 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -20,11 +20,14 @@ const nextConfig = { }, // API 프록시 설정 - 백엔드로 요청 전달 + // Docker 환경: SERVER_API_URL=http://backend:3001 사용 + // 로컬 개발: http://localhost:8080 사용 async rewrites() { + const backendUrl = process.env.SERVER_API_URL || "http://localhost:8080"; return [ { source: "/api/:path*", - destination: "http://localhost:8080/api/:path*", + destination: `${backendUrl}/api/:path*`, }, ]; },