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*`, }, ]; },