6달 전 · 노원재 님의 답변 업데이트
ReactNative ios build 에러 3일째 해결을 못했습니다.
시뮬레이션을 실행하려고 해도 스크립트 문제, iPhone 버전 범위 문제, 시뮬레이터 문제가 계속 발생합니다. 어떤 도움이라도 감사합니다. ReactNative를 처음 접했습니다. 저희 팀에서 저를 도울 수 있는 사람이 없습니다. #프로젝트 환경 mac M2 ruby -v ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin23] node -v v20.10.0 pod --version 1.15.2 package.json { "name": "labts", "version": "0.0.1", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "lint": "eslint .", "start": "react-native start", "test": "jest" }, "dependencies": { "@react-native-community/async-storage": "^1.12.1", "@react-native-community/cli": "13.6.9", "@react-navigation/bottom-tabs": "^6.6.0", "@react-navigation/native": "^6.1.17", "@react-navigation/native-stack": "^6.10.0", "@tanstack/react-query": "^5.51.5", "@types/react-native-vector-icons": "^6.4.18", "axios": "^1.7.2", "date-fns": "^3.6.0", "immer": "^10.1.1", "react": "18.2.0", "react-native": "0.74.3", "react-native-calendars": "^1.1305.0", "react-native-date-picker": "^5.0.4", "react-native-dotenv": "^3.4.11", "react-native-get-random-values": "^1.11.0", "react-native-image-crop-picker": "^0.41.2", "react-native-image-zoom-viewer": "^3.0.1", "react-native-paper": "^5.12.3", "react-native-permissions": "^4.1.5", "react-native-safe-area-context": "^4.10.8", "react-native-screens": "^3.32.0", "react-native-splash-screen": "^3.3.0", "react-native-tab-view": "^3.5.2", "react-native-vector-icons": "^10.1.0", "react-native-vision-camera": "^4.5.1", "uuid": "^10.0.0", "yarn": "^1.22.22" }, "devDependencies": { "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@react-native/babel-preset": "0.74.85", "@react-native/eslint-config": "0.74.85", "@react-native/metro-config": "0.74.85", "@react-native/typescript-config": "0.74.85", "@types/react": "^18.2.6", "@types/react-native-dotenv": "^0.2.2", "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.6.3", "babel-plugin-module-resolver": "^5.0.2", "eslint": "^8.19.0", "jest": "^29.6.3", "prettier": "2.8.8", "react-test-renderer": "18.2.0", "typescript": "5.0.4" }, "engines": { "node": ">=18" } } PodFile require Pod::Executable.execute_command('node', ['-p', 'require.resolve( "react-native/scripts/react_native_pods.rb", {paths: [process.argv[1]]}, )', __dir__]).strip platform :ios, '12.0' use_frameworks! #use_modular_headers! prepare_react_native_project! linkage = ENV['USE_FRAMEWORKS'] if linkage != nil Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green use_frameworks! :linkage => linkage.to_sym end target 'nexlabts' do config = use_native_modules! use_react_native!( :path => config[:reactNativePath], # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) target 'nexlabtsTests' do inherit! :complete # Pods for testing end post_install do |installer| # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false, # :ccache_enabled => true ) end end 제가 아래 에러 3가지에 대해 제가 해본 방법들입니다. 1. node 재설치 node_module 폴더 삭제, package-rock.json 삭제 후 재설치 npm install --legacy-peer-deps yarn install 2. Xcode가 node 읽을 수 있도록 설정 sudo ln -s "$(which node)" /usr/local/bin/node 3. Podfile 내 platform 설정 수정 platform :ios, '12.0' or platform :ios, '14.0' 4. Pods 재설치 rm -rf ~/Library/Developer/Xcode/DerivedData or rm -rf ~/Library/Developer/Xcode/DerivedData/* rm -rf Pods rm Podfile.lock pod install --repo-update Xcode \> Product \> Clean Build Folder. cd ./ios pod cache clean -all pod install --repo-update cd ../ npx react-native run-ios --no-packager --simulator="iPhone 15" or npx react-native run-ios --simulator="iPhone 15" or yarn start > i(run ios) Err 1. cocoaPods 설치할 때 [!] CocoaPods could not find compatible versions for pod "React-RuntimeHermes": In Podfile: React-RuntimeHermes (from ../node_modules/react-native/ReactCommon/react/runtime) Specs satisfying the React-RuntimeHermes (from ../node_modules/react-native/ReactCommon/react/runtime) dependency were found, but they required a higher minimum deployment target. Err2. iOS 실행할때 run-ios --no-packager --simulator="iPhone 15" Build description signature: fc1341421f84b87c5245d346c2c17b66 Build description path: /Users/nowonjae/Library/Developer/Xcode/DerivedData/nexlabts-argvodqcybjfcybstpulfpghnzvm/Build/Intermediates.noindex/XCBuildData/fc1341421f84b87c5245d346c2c17b66.xcbuilddata /Users/nowonjae/Desktop/project/NeXLabRN/ios/nexlabts.xcodeproj:1:1: error: Unable to open base configuration reference file '/Users/nowonjae/Desktop/project/NeXLabRN/ios/Pods/Target Support Files/Pods-nexlabts/Pods-nexlabts.release.xcconfig'. (in target 'nexlabts' from project 'nexlabts') warning: Unable to read contents of XCFileList '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Unable to read contents of XCFileList '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-input-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-frameworks-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase 'Bundle React Native code and images' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase '[CP] Embed Pods Frameworks' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-input-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') error: Unable to load contents of file list: '/Target Support Files/Pods-nexlabts/Pods-nexlabts-resources-Release-output-files.xcfilelist' (in target 'nexlabts' from project 'nexlabts') warning: Run script build phase '[CP] Copy Pods Resources' will be run during every build because it does not specify any outputs. To address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'nexlabts' from project 'nexlabts') --- xcodebuild: WARNING: Using the first of multiple matching destinations: { platform:iOS Simulator, id:B5AA2E84-4F83-4749-A986-A1FCE5E398A3, OS:17.5, name:iPhone 15 } { platform:iOS Simulator, id:B5AA2E84-4F83-4749-A986-A1FCE5E398A3, OS:17.5, name:iPhone 15 } ** BUILD FAILED ** ] Err3. Xcode 로 Build 할때 (Any iOS Simulator Device (arm64, x86_64)) Command PhaseScriptExecution failed with a nonzero exit code
개발자
#reactnative
#xcode
#reactnative-run-ios
답변 1
댓글 0
조회 427
4달 전 · 문종호 님의 새로운 답변
RAG 를 짜는 중에 도무지 어떤 부분이 문제인지 모르겠습니다.
# JSON 파일에서 FAQ 데이터를 로드하는 함수 def load_faq_data_from_json(file_path): with open(file_path, 'r', encoding='utf-8') as f: faq_data = json.load(f) return faq_data # FAQ 데이터 로드 json_file_path = '' faq_data = load_faq_data_from_json(json_file_path) # ChromaDB 클라이언트 및 Embedding 설정 chroma_client = chromadb.Client() # ChromaDB 클라이언트 생성 # 고유한 컬렉션 이름 생성 collection_name = "faq_data_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") collection = chroma_client.create_collection(collection_name) # LangChain의 Text Splitter 설정 text_splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=50 ) # OpenAI 임베딩 설정 openai_api_key = '' embedding_function = OpenAIEmbeddings( model="text-embedding-ada-002", openai_api_key=openai_api_key ) # 텍스트 스플리팅 및 임베딩 생성 함수 def split_and_embed_text(text): splitted_texts = text_splitter.split_text(text) print(f"Splitted texts: {splitted_texts}") try: # OpenAIEmbeddings는 embed_documents를 사용합니다. embeddings = embedding_function.embed_documents(splitted_texts) except Exception as e: print(f"임베딩 생성 중 오류 발생: {e}") return None # 임베딩이 제대로 생성되었는지 확인합니다. if embeddings is None or len(embeddings) == 0: print("임베딩 생성 실패") return None # 임베딩을 numpy 배열로 변환 embeddings = np.array(embeddings) print(f"Embeddings shape: {embeddings.shape}") # 임베딩 벡터의 차원을 확인하고 처리합니다. if embeddings.ndim == 1 and embeddings.shape[0] == 1536: # 임베딩이 1차원 배열이고 길이가 1536인 경우 final_embedding = embeddings elif embeddings.ndim == 2 and embeddings.shape[1] == 1536: # 임베딩이 2차원 배열이고 두 번째 차원이 1536인 경우 final_embedding = np.mean(embeddings, axis=0) else: print("임베딩 벡터의 차원이 예상과 다릅니다.") return None print(f"Final embedding shape: {final_embedding.shape}") return final_embedding # FAQ 데이터를 Vector DB에 저장 def store_faq_data_in_vector_db(faq_data, collection): for faq in faq_data: # 'question'과 'answer'가 있는지 확인하고, 'answer'가 None이 아닌지 확인 if 'question' not in faq or 'answer' not in faq or faq['answer'] is None: print(f"누락된 'question' 또는 'answer'로 인해 항목을 건너뜁니다: {faq}") continue # 다음 항목으로 넘어감 # 텍스트 스플리팅 및 임베딩 생성 question_embedding = split_and_embed_text(faq['question']) if question_embedding is None: print(f"Embedding generation failed for question: {faq['question']}") continue # 임베딩이 없으면 다음 질문으로 넘어감 print(f"Generated embedding for question '{faq['question']}': {question_embedding}") # 각 질문에 고유한 ID 생성 faq_id = str(uuid.uuid4()) # 메타데이터에서 None 값을 제거 metadata = {k: v for k, v in {"answer": faq['answer']}.items() if v is not None} # Vector DB에 저장 collection.add( documents=[faq['question']], metadatas=[metadata], ids=[faq_id], embeddings=[question_embedding] ) # 추가 후 임베딩 확인 (저장된 후 곧바로 확인) stored_results = collection.get(ids=[faq_id], include=["embeddings"]) if stored_results['embeddings'] is not None and len(stored_results['embeddings']) > 0: print(f"Embedding for question '{faq['question']}' successfully stored.") else: print(f"Failed to store embedding for question '{faq['question']}'") # FAQ 데이터를 JSON에서 로드하고 저장 store_faq_data_in_vector_db(faq_data, collection) 이렇게 데이터를 저장하고 # 환경 변수에서 API 키 로드 openai_api_key = os.getenv("OPENAI_API_KEY") if not openai_api_key: raise ValueError("OpenAI API 키가 설정되지 않았습니다. 환경 변수 OPENAI_API_KEY를 설정하세요.") # OpenAI 임베딩 설정 embedding_function = OpenAIEmbeddings( model="text-embedding-ada-002", openai_api_key=openai_api_key ) # LangChain의 Text Splitter 설정 (일관성 유지) text_splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=50 ) # ChromaDB 클라이언트 및 컬렉션 설정 chroma_client = chromadb.Client() collection_name = "faq_data_collection" try: # 이미 존재하는 컬렉션인지 확인하고, 있으면 가져옴 collection = chroma_client.get_collection(name=collection_name) except chromadb.errors.CollectionNotFoundError: # 컬렉션이 존재하지 않을 경우에만 생성 collection = chroma_client.create_collection(name=collection_name) # Vector DB에서 유사 질문 검색 (ChromaDB) def find_similar_question_in_vector_db(new_question_embedding, collection, k=5): results = collection.query(query_embeddings=[new_question_embedding], n_results=k, include=['documents', 'metadatas', 'embeddings']) best_similarity = 0 best_question = None best_answer = None # 검색 결과에서 각 질문의 유사도와 답변을 처리합니다. if 'documents' in results and 'metadatas' in results: documents = results['documents'][0] metadatas = results['metadatas'][0] embeddings = results['embeddings'][0] for i in range(len(documents)): stored_embedding = embeddings[i] metadata = metadatas[i] if stored_embedding is not None: # 코사인 유사도를 통해 유사도를 계산합니다. similarity = cosine_similarity([new_question_embedding], [stored_embedding])[0][0] print(f"유사도: {similarity} for {documents[i]}") # 유사도가 가장 높은 결과를 선택하며, 임계값 이상일 경우에만 선택 if similarity > best_similarity and similarity >= SIMILARITY_THRESHOLD: best_similarity = similarity best_question = documents[i] if isinstance(metadata, list): metadata = metadata[0] best_answer = metadata.get('answer') if isinstance(metadata, dict) else None return best_question, best_answer # Fine-tuned GPT를 사용해 새로운 답변 생성 def gpt_generate_response_from_finetuned_gpt(question, style="의사 A 말투"): prompt = f"다음은 환자의 질문입니다: \"{question}\". 아래 말투를 사용하여 질문에 대해 성실하고 정확한 답변을 작성해주세요.\n\ 말투: {style}" response = client.chat.completions.create( model="", # Fine-tuned된 GPT 모델 ID messages=[ {"role": "system", "content": "You are a helpful medical assistant."}, {"role": "user", "content": prompt}, ], max_tokens=300, temperature=0.7, # 답변의 다양성을 조절합니다. ) return response.choices[0].message.content.strip() # 새로운 질문 처리 및 최종 응답 생성 def generate_final_response(new_question, collection): # 텍스트 스플리팅 및 임베딩 생성 splitted_texts = text_splitter.split_text(new_question) new_question_embedding = np.mean(embedding_function.embed_documents(splitted_texts), axis=0) # ChromaDB에서 유사 질문 검색 similar_question, answer = find_similar_question_in_vector_db(new_question_embedding, collection) if similar_question and answer: final_response = f"질문: {new_question}\n유사 질문: {similar_question}\n기본 답변: {answer}" else: generated_answer = gpt_generate_response_from_finetuned_gpt(new_question) final_response = f"질문: {new_question}\nGPT로 생성된 답변: {generated_answer}\n(이 답변은 벡터데이터에서 유사한 답변을 찾을 수 없어 GPT에 의해 생성되었습니다.)" return final_response # 사용자로부터 새로운 질문 입력 받기 new_question = input("새로운 질문을 입력하세요: ") # 최종 응답 생성 response = generate_final_response(new_question, collection) print(response) 로 데이터베이스에서 유사한 질문-답변 쌍을 끌어오려는데 정확히 같은 질문을 넣어도 (이러면 유사도가 1인데) 저장되어있는 답변이 끌어와지질 않네요...
개발자
#llm#rag
답변 1
댓글 0
조회 74
2년 전 · 커리어리 AI 봇 님의 새로운 답변
리액트로 스프링과 웹소켓 채팅방을 구현했는데 자동 랜더링이 안됩니다..
안녕하세요! 현재 웹소켓으로 스프링과 채팅기능을 구현중에 있습니다 채팅방에서 채팅을 보내고 받는 건 가능한 상태인데 같이 채팅방에 입장해서 A가 B한테 보냈을 때 B가 페이지를 새로고침 하지 않으면 채팅이 자동 랜더링이 되지 않는 상황인데 여러 방법을 참고하고 해봤지만.. 성공하지 않았습니다 어떻게 풀어나가야 할지 잘 모르겠습니다 ㅠㅠ 코드가 길지만 ... 혹시 답변이 가능할까해서 참고해봅니다 좋은 키워드도 추천해주시면 감사하겠습니다!!... export const ChatRoomPage = () => { //메뉴 모달 const [isModalOpen, setIsModalOpen] = useState(false); const [isExitModalOpen, setIsExitModalOpen] = useState(false); const [backgroundPosition, setBackgroundPosition] = useState('static'); const location = useLocation(); const params = location.pathname; const segments = params.split('/'); const RoomUniqueId = segments[4]; const RoomId = segments[5]; const [messageData, setMessageData] = useState([]); const [messageList, setMessageList] = useState([]); const [message, setMessage] = useState(''); const accesskey = Cookies.get('Access_key'); // 채팅방 입장시 안내 문구 기능 const [showModal, setShowModal] = useState(false); const client = useRef({}); useEffect(() => { console.log('유즈이펙트 쉴행'); setShowModal(true); connect('L'); return () => disconnect(); }, []); const connect = type => { client.current = new StompJs.Client({ brokerURL: 'ws://222.102.175.141:8081/ws-stomp', connectHeaders: { Access_key: `Bearer ${accesskey}`, }, debug: function (str) { console.log('str ::', str); }, onConnect: () => { if (type === 'L') { subscribe(); publish(); } else { subscribe1(); publish1(); } }, }); client.current.webSocketFactory = function () { return new SockJS('http://222.102.175.141:8081/ws-stomp'); }; client.current.activate(); return () => disconnect(); }; const subscribe = () => { client.current.subscribe(`/sub/chat/messageList/${localStorage.memberUniqueId}`, message => { // console.log('messageData11 : ', JSON.parse(`${message.body}`)); setMessageData(JSON.parse(`${message.body}`)); const data = JSON.parse(`${message.body}`); setMessageList(data.data.chatMessageList); }); }; const publish = () => { client.current.publish({ destination: `/pub/chat/messageList/${localStorage.memberUniqueId}`, body: JSON.stringify({ chatRoomId: RoomId, chatRoomUniqueId: RoomUniqueId, page: 0, }), }); }; const closeModal = () => { setIsModalOpen(false); setBackgroundPosition('static'); }; const openModal = () => { setIsModalOpen(true); setBackgroundPosition('fixed'); }; const handleBackdropClick = e => { console.log('e ::', e); if (e.target === e.currentTarget) { closeModal(); } }; const ExitopenModal = () => { setIsExitModalOpen(true); }; const ExitcloseModal = () => { setIsExitModalOpen(false); }; const ReportButtonHandler = () => { alert('곧 업데이트 예정입니다!'); }; // 채팅 보내기 const sendMessage = message => { console.log('message :: ', message); connect(); setMessage(''); return () => disconnect(); }; const subscribe1 = () => { client.current.subscribe(`/sub/chat/message/${RoomUniqueId}`, message => { setMessageData({ ...messageList, message }); }); }; const publish1 = () => { client.current.publish({ destination: `/pub/chat/message/${RoomUniqueId}`, body: JSON.stringify({ memberId: `${localStorage.memberId}`, memberName: `${localStorage.memberName}`, memberUniqueId: `${localStorage.memberUniqueId}`, memberProfileImage: `${localStorage.profileImage}`, chatRoomId: RoomId, chatRoomUniqueId: RoomUniqueId, message: message, }), }); }; const disconnect = () => { client.current.deactivate(); }; console.log('messageList :: ', messageList); return ( <> <div style={{ width: '100%', height: '100%', position: backgroundPosition, }} > <Background> <Topbar> <Link to={`${PATH_URL.PARTY_CHAT}/${localStorage.memberUniqueId}`}> <TopBackDiv> <LeftBack /> </TopBackDiv> </Link> <TopbarName>모임이름</TopbarName> <ModalBtn onClick={() => { openModal(); }} > <RoomMenuIcon /> </ModalBtn> </Topbar> <Container> <Contents> <ParticipantDiv>ㅇㅇㅇ님이 참여했습니다.</ParticipantDiv> {messageList?.map((data, index) => { return ( <OtherDiv key={index}> <div style={{ position: 'relative', }} > <OtherImg> <OtherProfile> <img src={data.memberProfileImage} alt="profile" style={{ width: '100%', height: '100%', borderRadius: '8px', }} /> </OtherProfile> <OtherHostIcon> <PartHostIcon /> </OtherHostIcon> </OtherImg> <OthertInfo> <OtherName>{data.sender}</OtherName> <OtherContents> <OtherChatText>{data.message}</OtherChatText> <OtherChatTime>12:19 pm</OtherChatTime> </OtherContents> </OthertInfo> </div> </OtherDiv> ); })}
개발자
#채팅
#웹소켓
#채팅기능
답변 2
댓글 0
조회 595
2년 전 · 익명 님의 질문 업데이트
채팅 새로고침시 연결 끊기는 문제
안녕하세요. 현재 Websocket과 stompjs v6.0.0을 활용해 채팅을 구현했습니다. roomId로 여러 채팅방을 만들 수 있게 구현했고, 현재 새로고침을 하지 않는 이상 잘 돌아갑니다. 그러나, 새로고침 할 시에는 바로 연결이 끊겨 이전의 채팅 내역도 보이지 않고, 연결, 구독 내역이 사라집니다 ... 어떻게 reconnect 해야할까요? 단순히 채팅 페이지에서 useEffect로 connect를 다시 하니 이미 연결 구독이 된 상태라고 뜨더라구요 .... ㅠㅠ (고민글을 올렸을 때 채팅방이 생성되고, 연결 구독이 됩니다. 채팅 시작 버튼을 눌렀을 경우에는 본인이 연결 구독이 되어 1대 1로 상대방과 채팅이 시작되는 구조입니다. ) import { CompatClient, Stomp } from "@stomp/stompjs"; import { createContext, useContext, useMemo, useRef } from "react"; import { useSetRecoilState } from "recoil"; import { messageState } from "../../states/chatting"; import audio from "../../assets/audios/chatting.mp3"; const ChatContext = createContext( {} as { connect: (roomId: number) => void; disconnect: () => void; send: (roomId: number, message: string) => void; }, ); export const useChatContext = () => useContext(ChatContext); export function ChatProvider({ children }: any) { const setMessages = useSetRecoilState(messageState); const token = localStorage.getItem("accessToken"); // 채팅 연결 구독 const client = useRef<CompatClient>(); const connect = (roomId: number) => { client.current = Stomp.over(() => { const sock = new WebSocket("wss://m-ssaem.com:8080/stomp/chat"); return sock; }); client.current.connect( { token: token, }, () => { client.current && client.current.subscribe( `/sub/chat/room/${roomId}`, (message) => onMessageReceived(message, roomId), { token: token!, }, ); }, ); return client; }; const onMessageReceived = (message: any, roomId: number) => { const audioElement = new Audio(audio); audioElement.play(); setMessages((prevMessages) => { const updatedMessages = { ...prevMessages, [roomId]: [...(prevMessages[roomId] || []), JSON.parse(message.body)], }; return updatedMessages; }); }; // 채팅 나가기 const disconnect = () => { if (client.current) { client.current.disconnect(() => { window.location.reload(); }); } }; // 채팅 보내기 const send = (roomId: number, message: string) => { if (client.current) { client.current.send( `/pub/chat/message`, { token: token, }, JSON.stringify({ roomId: roomId, message: message, type: "TALK", }), ); } }; const handlers = useMemo(() => ({ connect, disconnect, send }), []); return ( <ChatContext.Provider value={handlers}>{children}</ChatContext.Provider> ); } ----------이 부분은 connect 하는 부분입니다 --------- const { connect } = useChatContext(); const chatRoomId = worryBoard && worryBoard.chatRoomId; const handleStartChatting = () => { navigate(`/chatting`); connect(chatRoomId!!); }; ------------ 채팅 페이지는 따로 있습니다 --------------
개발자
#websocket
#stompjs
#채팅
#chatting
#react
답변 0
댓글 0
조회 326
Next.js SSR + react-query 조합에서의 serializing 에러
안녕하세요! Next.js SSR + react-query 조합을 사용하려고 하는데요, page 컴포넌트 내 getServerSideProps 함수에서 prefetching을 받아온 후에 serializing 에러가 발생합니다. (Next.js는 13버젼입니다.) 에러 내용은 다음과 같습니다. Error: Error serializing `.dehydratedState.queries[0].state.data.headers` returned from `getServerSideProps` in "/top". Reason: `object` ("[object AxiosHeaders]") cannot be serialized as JSON. Please only return JSON serializable data types. 해당 에러 내용으로 구글링을 해보니, 대부분 getServerSideProps 함수 반환 코드에서 return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; 와 같이 dehydrate(queryClient)값을 JSON화 -> Object화를 하라고 하는데요, 이와 같이 사용해도 또 다시 아래와 같은 에러가 납니다. TypeError: Converting circular structure to JSON --> starting at object with constructor 'ClientRequest' | property 'socket' -> object with constructor 'Socket' --- property '_httpMessage' closes the circle Backend API는 Express.js를 사용하고 있으며, res.status(200).json({ data: ~ })와 같은 방식으로 응답을 주고 있습니다. 어떻게 해결할 수 있을까요? 코드 첨부가 안되네요, 아래는 page 컴포넌트가 위치한 파일의 전체 코드입니다. import type { ReactElement } from 'react'; import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query'; import { format } from 'date-fns'; import TopMusicContainer from '~containers/TopMusicContainer'; import Layout from '~layouts/Layout'; import type { NextPageWithLayout } from '~pages/_app'; import TopMusicService from '~services/topMusicService'; import * as MusicType from '~types/musicType'; export async function getServerSideProps() { const queryClient = new QueryClient(); await queryClient.prefetchQuery(['fetchTopMusic'], () => { const params: MusicType.ListRequestType = { filter: 'title', keyword: '', page: 1, limit: 25, time: format(new Date(), 'yyyyMMddHH'), }; return TopMusicService.list(params); }); return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; } const Top: NextPageWithLayout = (): JSX.Element => { const { data, isLoading } = useQuery({ queryKey: ['fetchTopMusic'], queryFn: () => { const params: MusicType.ListRequestType = { filter: 'title', keyword: '', page: 1, limit: 25, time: format(new Date(), 'yyyyMMddHH'), }; return TopMusicService.list(params); }, }); return ( <section> <TopMusicContainer /> </section> ); }; Top.getLayout = function getLayout(page: ReactElement) { return <Layout>{page}</Layout>; }; export default Top;
개발자
#react
#next.js
#ssr
#react-query
답변 2
댓글 3
추천해요 4
조회 3,059
9달 전 · 최용빈 님의 답변 업데이트
파이썬 오류 좀 고쳐주세요 ㅠㅠ
import time import requests import streamlit as st API_BASE_URL = "http://localhost:8000/qna" # Fastapi로 api 생성 def request_chat_api(user_message: str) -> str: url = API_BASE_URL resp = requests.post( url, json={ "user_message": user_message, }, ) resp = resp.json() print(resp) return resp["answer"] def init_streamlit(): st.set_page_config(page_title='Dr. KHU', page_icon='🩺') if "messages" not in st.session_state: st.session_state.messages = [{"role": "assistant", "content": "안녕하세요! Dr.seo입니다🩺"}] # Initialize chat history if "messages" not in st.session_state: st.session_state.messages = [] # Display chat messages from history on app rerun for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) def chat_main(): if message := st.chat_input(""): # Add user message to chat history st.session_state.messages.append({"role": "user", "content": message}) # Display user message in chat message container with st.chat_message("user"): st.markdown(message) # Display assistant response in chat message container assistant_response = request_chat_api(message) with st.chat_message("assistant"): message_placeholder = st.empty() full_response = "" for lines in assistant_response.split("\n"): for chunk in lines.split(): full_response += chunk + " " time.sleep(0.05) # Add a blinking cursor to simulate typing message_placeholder.markdown(full_response) full_response += "\n" message_placeholder.markdown(full_response) # Add assistant response to chat history st.session_state.messages.append( {"role": "assistant", "content": full_response} ) if __name__ == "__main__": init_streamlit() chat_main() 이 코드를 실행시키면 자꾸 AttributeError: st.session_state has no attribute "messages". Did you forget to initialize it? More info: https://docs.streamlit.io/library/advanced-features/session-state#initialization 라고 뜨네요..
개발자
#파이썬
#python
답변 2
댓글 1
보충이 필요해요 2
조회 333
2년 전 · 익명 님의 새로운 댓글
채팅 기능 client 가 null 값이 돼요
하나의 페이지에서 connectHandler를 작동하고 또다른 페이지에서 sendHandler를 작동하려고 하는데 이렇게 해서는 useChat()이 리렌더링 되면서 client 값이 초기화가 되더라구요 값을 유지하고 싶고 recoil에 client를 담는 건 불가능이라고 떠서... connectHandler와 sendHandler를 다른 hooks로 분리하는 방법도 생각해봤는데 그러면 또 client값이 connect한 값이 아니더라구요 무슨 방법이 있을까요? 제발 도와주세요 ㅠㅠ (한 페이지에서 connectHandler, sendHandler, disconnectHandler 실행하면 잘 작동합니다!) import { CompatClient, Stomp } from "@stomp/stompjs"; import { useRef } from "react"; import { useRecoilState } from "recoil"; import { inputMessageState, messageState } from "../../states/chatting"; export function useChat() { const [messages, setMessages] = useRecoilState(messageState); const [inputMessage, setInputMessage] = useRecoilState(inputMessageState); const token = localStorage.getItem("accessToken"); // 채팅 연결 구독 const client = useRef<CompatClient>(); const connectHandler = () => { client.current = Stomp.over(() => { const sock = new WebSocket("wss://m-ssaem.com:8080/stomp/chat"); return sock; }); client.current.connect( { token: token, }, () => { client.current && client.current.subscribe(`/sub/chat/room/1`, onMessageReceived, { token: token!, }); }, ); }; const onMessageReceived = (message: any) => { setMessages((prevMessage) => [...prevMessage, JSON.parse(message.body)]); }; // 채팅 나가기 const disconnectHandler = () => { if (client.current) { client.current.disconnect(() => { window.location.reload(); }); } }; // 채팅 보내기 const sendHandler = () => { if (client.current && inputMessage.trim() !== "") { client.current.send( `/pub/chat/message`, { token: token, }, JSON.stringify({ roomId: 1, message: inputMessage, type: "TALK", }), ); setInputMessage(""); } }; return { connectHandler, disconnectHandler, sendHandler, }; }
개발자
#react
#chatting
#stompjs
답변 1
댓글 8
조회 231
10달 전 · 익명 님의 질문
Next.js 에서 fluent-ffmpeg 사용 시 에러 해결 가능할까요?
Next.js 에서 puppeteer를 사용해서 특정 url에 접속하여 애니메이션을 png로 100장 정도 캡처하여 생성하고, fluent-ffmpeg를 사용해서 해당 png 이미지들을 mp4 영상으로 만들려고 하는데요. yarn add puppeteer fluent-ffmpeg @ffmpeg-installer/ffmpeg yarn add --dev @types/fluent-ffmpeg 위와 같이 라이브러리들을 설치했구요. 아래 page.tsx 파일에서 코드를 구현했는데요. dev로 실행해서 해당 페이지에 접속을 하면 아래와 같은 에러가 발생하는데요. 해결이 가능할까요?? 다른 라이브러리를 써야할지 구현한 코드가 문제가 있는지 모르겠네요. 도움 부탁드립니다!! # 에러 코드 # 1 of 1 error Next.js (14.2.3) Server Error Error: Cannot find module '/Users/.../animation-capture/node_modules/@ffmpeg-installer/darwin-arm64/package.json' This error happened while generating the page. Any console logs will be displayed in the terminal window. Call Stack webpackEmptyContext file:///Users/.../animation-capture/.next/server/app/capture/page.js (22:10) eval node_modules/@ffmpeg-installer/ffmpeg/index.js (40:27) (rsc)/./node_modules/@ffmpeg-installer/ffmpeg/index.js file:///Users/.../animation-capture/.next/server/vendor-chunks/@ffmpeg-installer.js (20:1) Next.js eval /./src/app/capture/page.tsx (rsc)/./src/app/capture/page.tsx file:///Users/.../animation-capture/.next/server/app/capture/page.js (286:1) Next.js # 코드 구현부 # import { NextApiRequest, NextApiResponse } from 'next'; import puppeteer from 'puppeteer'; import fs from 'fs'; import path from 'path'; import ffmpeg from 'fluent-ffmpeg'; import ffmpegInstaller from '@ffmpeg-installer/ffmpeg'; ffmpeg.setFfmpegPath(ffmpegInstaller.path); .... 중략.... const outputFilePath = path.resolve("./screenshots/video.mp4"); ffmpeg() .addInput(`${folderPath}/screenshot-%d.png`) .inputFPS(10) .output(outputFilePath) .on("end", () => { res.status(200).send(`Video created successfully at ${outputFilePath}`); }) .on("error", (err) => { console.error("Error generating video:", err); res.status(500).send("Failed to generate video"); }) .run(); } catch (error) { console.error("Error capturing screenshots:", error); res.status(500).send("Failed to capture screenshots"); }
개발자
#next.js
#fluent-ffmpeg
#mp4
답변 0
댓글 0
조회 85
일 년 전 · 익명 님의 질문
Next.js 14 서버액션을 사용한 폼 데이터 처리 중 질문입니다.
next.js 14의 서버액션을 사용해서 사용자로부터 form으로 데이터를 입력받아 DB에 저장하고 첨부파일을 서버에 업로드하는 모듈을 개발중입니다. 문제점은, 첨부파일을 input으로 입력받아 서버액션 함수로 formData로 넘겨주는데, 서버액션 함수에서 첨부파일의 파일명에 한글이 포함됐을 경우 파일명의 한글이 유니코드로 보이는 문자열로 찍히고 있습니다. // Form.jsx "use client"; // [other code] export default function Form() { const [formState, formAction] = useFormState(inquiryAction, { success: undefined, message: "", }); const { register, handleSubmit, reset, formState: { errors }, } = useForm(); const onSubmit = (data) => { const formData = new FormData(); const jsonData = JSON.stringify(data); formData.append("jsonData", jsonData); data.attachment[0] && formData.append("attachment", data.attachment[0]); formAction(formData); }; return ( <form action={handleSubmit(onSubmit)}> // [other code] <FileInput label="첨부파일" {...register("attachment")} error={errors} resetTrigger={resetTrigger} /> // [other code] </form> ); } // inquiryAction.js "use server"; import { connectDB } from "./connectDB"; export async function inquiryAction(prevState, formData) { const fields = JSON.parse(formData.get("jsonData")); const attachment = formData.get("attachment"); console.log(attachment); } Form 컴포넌트는 클라이언트 컴포넌트이고, react-hook-form과 next14의 서버액션을 사용하기 위해, form의 action에는 react-hook-form의 handleSubmit함수에 onSubmit 함수를 인자로 전달합니다. onSumbit 함수에서 서버액션 함수를 가져와 처리를 하고 있습니다. 그런데 여기서 서버함수쪽에서 첨부파일을 받을 때 한글 파일명이 다 깨지고 있네요... 기존에 api route와 fetch api를 사용했을 땐 문제가 없었는데.. 제가 놓치고 있는 부분이 있을까요?
개발자
#next.js
#server-action
답변 0
댓글 0
조회 509
2년 전 · 익명 님의 질문 업데이트
ESLINT 어떤 것을 수정해야 내용처럼 fix 되는 현상을 막을 수 있을까요?
안녕하세요~ eslint, prettier에서 제가 무엇을 잘못했는지 계속 아래처럼 코드가 수정되고 있습니다. const res = (await request) < User > (context, `/users/${id}`); 어떻게 하면 고칠 수 있을까요? 1. (await request) 소괄호 제거 2. < User > 제너릭 사이 공백 제거 결론: const res = await request<User>(context, `/users/${id}`) 로 만들고 싶습니다.. 아래는 eslint 적용한 내용 입니다. ``` //eslint.config.js module.exports = { env: { browser: true, es2021: true, }, extends: [ 'plugin:react/recommended', 'airbnb', 'plugin:storybook/recommended', ], parser: '@typescript-eslint/parser', parserOptions: { project: ['./tsconfig.json'], ecmaVersion: 2018, sourceType: 'module', createDefaultProgram: true, }, plugins: ['react', '@typescript-eslint', 'prettier', 'import'], rules: { 'react/react-in-jsx-scope': 'off', 'react/jsx-props-no-spreading': 'off', 'import/prefer-default-export': 'off', 'implicit-arrow-linebreak': 'off', 'react/jsx-filename-extension': [ 2, { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, ], 'object-curly-newline': [ 'error', { ObjectPattern: { multiline: true, minProperties: 2, }, ImportDeclaration: 'never', ExportDeclaration: { multiline: true, minProperties: 3, }, }, ], 'import/extensions': [ 'error', 'ignorePackages', { js: 'never', mjs: 'never', jsx: 'never', ts: 'never', tsx: 'never', }, ], 'import/order': [ 'error', { alphabetize: { order: 'asc', }, groups: [ 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type', ], }, ], 'import/no-unresolved': 'off', }, settings: { 'import/resolver': { typescript: { alwaysTryTypes: true, }, node: { paths: ['src'], }, }, }, }; ```
개발자
#eslint
#prettier
답변 0
댓글 0
조회 77
2년 전 · Hello World 님의 질문
DRF를 자세히 공부해보고싶은데 독학으로 하려니 자꾸 턱턱걸립니다... django 백엔드에서 프론트엔드로 보낸 data가 안불러와져요
django rest framework로 웹을 개발하는 것을 공부하는 중인데 막히는부분이 많아서 점점 괴로워지네요. 이거를 보면서 독학하고 있는데요. https://wikidocs.net/book/9596 django views.py에서 response(data)를 발신하는 것까지는 되는데 next.js를 이용한 프론트에서 data를 받아오지 못하고 있어요... 왜 그럴까요? 디버그 메시지가 안뜨니 더 알기가 어렵네요. # backend/views.py """ from django.shortcuts import render from django.http import HttpResponse from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import AllowAny from rest_framework.response import Response # Create your views here. @api_view(['GET']) @permission_classes([AllowAny]) def hello_world(request): return Response('Hello, World!') # frontend/index.js """ import React, { useState, useEffect } from "react"; // config.js export const BACKEND_URL = "http://127.0.0.1:8000/"; const Home = () => { // State to store the data fetched from the backend const [data, setData] = useState(""); // useEffect hook to fetch data from the backend when the component mounts useEffect(() => { // Fetch data from the backend API using the '/api/hello' endpoint fetch("${BACKEND_URL}api/hello") .then((response) => response.json()) .then((data) => setData(data)); }, []); // Render the component JSX return ( <div> <h1>Welcome to Fine-Tuning Chatbot!</h1> <p>{data}</p> </div> ); }; export default Home; """ pure django-template를 이용해서 홈페이지 만들고 그 원리를 이해하는 것까지는 되는데 DRF는 정말 다른 세상이라고 느껴지네요...ㅎㅎ 좋은 강의나 책이 있으면 추천부탁드립니다. ㅠㅠ 위에 언급한 강의로 RESTful 배워보려는데, 기술스택이 많아서 그런건지 정보량도 많고 어렵네요 ㅠ
개발자
#django
#rest
#next.js
답변 0
댓글 0
조회 294
일 년 전 · 익명 님의 답변 업데이트
queryString에 관하여
http://test.com?auth=true&data={"endPoint":"/auth","data":"{"email":"test@test.com","name":"홍길동"}"} const query = queryString.parse(window.location.search); query 사용해서 data안에 객체로 되어있는 정보들 중 data안에 email 정보(test@test.com)을 뽑아오려면 어떻게해야하나요? const query = queryString.parse(window.location.search); console.log('query.data',query.data) //query.data까지는 뽑히는데 console.log('query.data.data', query.data.data) //undefined 그래서 const parsedData = JSON.parse(query?.data); //JSON형식 const email = parsedData.data.email; //test@test.com 정보가 뽑혔어요! 하지만 query?.data에서 에러가 납니다. **error 메세지 No overload matches this call. Overload 1 of 2, '(text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined): unknown', gave the following error. Argument of type 'string | (string | null)[] | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'. Overload 2 of 2, '(text: string, reviver?: ((this: any, key: string, value: any) => any) | undefined): any', gave the following error. Argument of type 'string | (string | null)[] | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'. query?.data에서 위와 같은 에러가 납니다.. query?.data의 type은 JSON형식 아니고 string입니다. console.log(query?.data) // {"endpoint":"/auth","data":{"email":"test@test.com","name":"홍길동"}} string에서 email을 뽑아야할까요?.. JSON형식으로 가져올순 없을까요? 에러 원인 및 피드백 부탁드립니다.
개발자
#react
#json
#querystring
#parse
#stringify?
답변 2
댓글 0
조회 86
일 년 전 · 허니 님의 새로운 답변
리액트 로그인질문..
react + 파이어베이스만 써오다가 react + express 조합을 오랜만에 사용중인데 긴가민가한점이있어서 질문드립니다 ㅠㅠ import axios from 'axios'; const instance = axios.create({ baseURL: process.env.REACT_APP_API_BASE_URL, timeout: 2500, headers: { 'Content-Type': 'application/json', withCredential: true, }, }); instance.interceptors.request.use( (config) => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = token; } else { delete config.headers['Authorization']; } return config; }, (error) => { return Promise.reject(error); } ); export default instance; 이렇게 인터셉터를주어서, 요청할 때마다 토큰을 전달하고있습니다. 그리고 프라이빗 라우터 + 유저정보 인증 훅을 사용해서 로그인사용자만 접근가능하게 페이지를 설정해줬습니다. 근데 질문 1. 네트워크탭 Request Headers 에서 Authorization을 확인해보면 토큰정보가 그대로 노출되고있는데요 원래 이렇게되나요? 질문 2. Bearer + token 이렇게 보내는 경우는 포스트맨이나 이런걸로 테스트할때만 Bearer 을 붙여서 보내주면될까요? 질문 3. 검색 키워드가 생각이안나서 질문으로 올립니다. 아주 옛날에는 (4년전) App.tsx에서 if(localstorage.token){ setAuthToekn(token); } 이런식으로 하고, setAuthToken함수는 import axios from 'axios'; const setAuthToken = (token) => { if (token) { axios.defaults.headers.common['x-auth-token'] = token; } else { delete axios.defaults.headers.common['x-auth-token']; } }; export default setAuthToken; 이런식으로 되어있었는데요, 요즘은 제가 짠 코드처럼 axios.인터셉터 식으로 하는게 맞나요? 질문 4. 그럼 요즘도 회원가입/로그인시 로컬스토리지에 유저 정보 (닉네임 이름 이메일, 토큰정보)만 저장해두고 로그인하면, 로그인버튼이 회원 닉네임으로 변하게 해준다던가.. 이런식으로 분기처리를 하나요? ( 저는 이렇게하고있어서요.. 로딩처리를 줄수도있겠지만 깜빡거리는게 싫고, 또 로컬스토리지로안하면 로그인버튼으로 잠깐바꼇다가 회원닉네임이 표시되더라구요) 질문5. (질문4와 이어집니다.) 만약 질문4처럼하면 사용자가 사이트에 계속 로그인중인데, 이 토큰이 끝났는지 판단하려는 코드를 따로 작성해줘야할까요? 예를들면, App.tsx에 서버 api/auth같은거에 요청보내는 로직을 작성해서, 토큰만보내서 유효한 토큰인지 아닌지, 유효하지않은토큰이면 에러를 리턴시켜준다던가, 리프레쉬토큰을 발급시켜서 연장시켜준다던가 이런식으로하면될까요? 마지막으로 질문이 좀 많고 중구난방인데 죄송스럽고 조심스럽네요..
개발자
#react
#login
#register
#local-storage
#jwt
답변 1
댓글 0
조회 107
2년 전 · 엄홍재 님의 새로운 답변
nextjs 초보 fetch & DELETE 동작이 안되요.
app/api/list/[id]/route.js export async function DELETE(request, {params}) { const id = params.id; const {searchParams} = request.nextUrl; const sort = searchParams.get('sort'); return NextResponse.json({message: 'test', id, sort}); } 위처럼 되어있고, app/list/ListItem.js <button className="blue"> <span onClick={() => { fetch('/api/list/' + post._id, { method: 'DELETE' }).then(() => { console.log('call delete'); }); }}>삭제</span> </button> 위 처럼 코드를 작성했는데, DELETE route가 실행이 되지 않습니다. 어떻게 해야 하나요?... POST의 경우에는 아래와 같은데 동작이 잘 되서요.. app/write/route.js export async function POST(request) { const data = await request.formData(); let body = Object.fromEntries(data); const db = (await connectDB).db('exam'); await db.collection('post').insertOne(body); return new Response('POST'); } app/write/page.js <form action="/api/write" method="POST"> <input type="text" name="title" placeholder="제목"/> <br/> <input type="text" name="content" placeholder="내용"/> <button type="submit">NEW</button> </form>
개발자
#nextjs
답변 1
댓글 0
조회 169
<input type="file"> 관련하여..
<template> <label for="camera1"> 촬영 </label> <input type="file" id="camera1" accept="image/*" capture="camera" style="display: none" @change="onStartFileSelected" /> </template> <script> const startFile = ref({}) //@change const onStartFileSelected = event => { const startFileInfo = event.target.files[0] startFile.value = startFileInfo if (startFileInfo) { startImageYn.value = 'Y' console.log('startFileInfo', typeof startFileInfo) } else { startImageYn.value = ' ' console.log('no image') } } // 파일 포함 다음 상태 업데이트하는 api const fileUpdateStatusApiHandler = async () => { const formData = new FormData() console.log('startFile===',startFile.value)//File형식 데이터 정상 찍힘 -> 타입 : 객체(Object) console.log('startFile',JSON.stringify(startFile.value)) //안찍힘???왜?????? 객체->스트링으로 바꿈 formData.append('file_upload', JSON.stringify(startFile.value),startFile.value) //빈값이 들어감.. try { const res: any = await ApiCert.post( API_URL.url, formData, { headers: { 'Content-Type': 'multipart/form-data' } } ) console.log('response =====', res) if (res.resCode === 'OK') { console.log('OK') } else { alert(res.resMsg) } } catch (err: any) { console.log('error =====', err) } } </script> file형식 데이터를 api보낼때 formData에 넣어 보내고싶은데 file이 안들어가네요.. file_upload: {} 빈값으로 들어갑니다. 도와주세용
개발자
#input
#type="file"
#vue3
#javascript
#form-data
답변 2
댓글 0
조회 180
일 년 전 · 손우진 님의 새로운 답변
nextauth 를 이용해 springboot 의 jwt를 받아와서 로그인을 구현중입니다.
안녕하세요. nextauth 를 이용해 로그인 구현중입니다. 흔히 생각하는 소셜 로그인이 아닌 username 과 password를 통해 springboot security 에서 jwt를 반환 받아오려고 합니다. // app/api/auth/[...nextauth]/route.ts CredentialsProvider({ name: 'Credentials', credentials: { username: { label: 'Username', type: 'text', placeholder: '아이디' }, password: { label: 'Password', type: 'password' }, }, async authorize(credentials, req) { const res = await fetch(`${process.env.NEXTAUTH_URL}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: credentials?.username, password: credentials?.password, }), }); const user = await res.json(); console.log(user); if (user) { return user; } else { return null; } }, }), 그런데 위의 코드처럼 코드를 구성했을때 .env.local 파일의 NEXTAUTH_URL 이 등록이 되면 authorize 가 작동하지 않습니다 . 에러 log라도 있으면 어떻게라도 해보겠는데.. 그것도 없습니다. 그래서 NEXTAUTH_URL 를 등록하지 않고 const res = await fetch(`http://localhost:8080/auth`, 바꾸면 이것역시 에러 log 없이 브라우저상에서 Error 라는 문구만 보여줍니다. 이렇게 생각했을때 authorize는 값을 내부에서만 가져올수 있는거 같은데요. 실제로 가상의 const user = {....} 를 만들어서 반환하면 로그인이 아주 잘 됩니다. 하지만 제가 원하는 방법은 아니죠..ㅍ 검색해보면 prisma 가 많이 나오는데.. 그것 또한 시스템상 제가 원하는 방식이 아닙니다. springboot security 에서 jwt를 받아와서 nextauth에 로그인을 할수 있는 방법은 없을까요? custom login 페이지를 만들어서 해야 할거 같은데 방법을 잘 모르겠습니다. 현재 springboot 서버에 cors 설정이 정상적으로 허용된 상태입니다.
개발자
#nextauth
#springboot
#jwt
#nextjs
#typescript
답변 1
댓글 0
조회 1,203
7달 전 · 성지수 님의 새로운 답변
환경변수 주입 및 빌드(모노레포)
모노레포 프로젝트 환경 및 요구사항 - 프로젝트는 front(React), back(Node), common 패키지로 구성되어 있으며, 모노레포로 되어있음 - Dockerfile 스크립트에서는 build → run 순서로 실행되도록 구현되어 있음 - 서버 배포 시에는 하나의 포트만 사용해야 하므로 back(Node)에서 front의 정적 파일(index.html)을 실행해야 함 - Docker 빌드 시, front(webpack 빌드)와 back 둘 다 build 스크립트를 실행하고, 이미지 실행 시에는 back만 run 스크립트를 실행하여 back에서 front의 정적 파일을 실행해야 함 - 환경변수는 이미지 실행 시 docker-compose.yml의 env_file 속성을 사용해 외부에서 주입해야 함(환경변수가 바뀌는 상황이 있다면 .env 파일을 수정한 후 다시 빌드해야 합니다.) 예외 상황 Docker 빌드할 때 .env 파일을 포함하지 않고 run 스크립트를 실행(이미지 실행) 시 주입하는데, front 패키지에는 run 스크립트가 존재하지 않습니다. (back에서 front의 정적 파일을 실행) 즉, 환경변수가 주입되지 않고 있음 해결 방법 첫번째는 back에서 front의 dist 파일 호출 시 환경변수 객체를 만들어 value 값을 key 값으로 치환해서 함께 넘기고, index.html의 head에서 window 객체를 활용해 값을 받는 방법 하지만 이 방법은 개발자 도구에서 환경변수 값이 노출되고, window 객체를 활용하기 때문에 새로운 브라우저를 생성할 때마다 사이드 이펙트가 발생할 수 있음 두번째는 모노레포 root 패키지에서 prerun 스크립트로 front의 build 스크립트를 실행하는 방법 스크립트 -> "prerun": "lerna run build --parallel --scope @projectname/front --stream", 이 방법은 빌드를 두 번 실행하므로 자원을 많이 소모하지만, 예외 상황이 없는 것으로 보임 질문 받은 내용 Q. 환경변수를 바꾸는 상황이 존재할까요? A. 회사의 인프라 환경에 따라 다르지만, 포트나 IP가 변경될 수 있는 상황이 있음 예를 들어, 외부 서버에 요청을 보낼 경우 그 서버의 정보가 바뀌면 설정을 변경해야 하며, 다른 서버에 요청할 때 인증 정보를 입력해야 하는데, 토큰이나 사용자 패스워드 값이 변경될 경우에도 수정이 필요함 고민되는 부분 다른 해결 방법이나 비슷한 상황이 있다면 함께 논의하고 싶습니다. ㅎㅎ *프로젝트 구조 root ├── packages │ ├── back │ │ ├── package.json │ │ ├── index.ts │ │ ├── .babelrc │ │ └── ... │ ├── common │ ├── front │ │ ├── public │ │ │ └── index.html │ │ ├── package.json │ │ ├── src │ │ │ └── index.js │ │ ├── src │ │ ├── webpack.config.js │ │ └── ... ├── package.json ├── lerna.json ├── docker-compose.yml ├── Dockerfile * 프로젝트의 중요 정보는 제외했습니다.
개발자
#환경변수주입
#모노레포
#빌드
#node.js
#react
답변 1
댓글 0
조회 73
일 년 전 · 백승훈 님의 댓글 업데이트
Nginx, Express 연결 후 프론트에서 이미지 파일 전송시 408 Error
안녕하세요. 혼자 해보던 도중 도저히 해결이 되지 않아 문의드립니다. - 서버 설계 - 현재 저는 Express앱을 AWS EC2 인스턴스에 NGINX를 설치한 후 proxy_pass에 express앱이 구동중인 port를 연결하여 사용하고 있습니다. 이 과정에서 ALB를 통해 ACM을 연동하여 HTTPS 프로토콜이 사용가능하게 설정까지 하였습니다. 이미지 파일업로드는 multer-s3를 이용해 s3버킷과 연결하여 업로드 되는 방식입니다. - 문제상황 - 로그인과 기본적인 CRUD는 문제없이 되는데, 프론트에서 이미지 파일(multipart/form-data)을 서버로 전송하면 504 오류가 출력됩니다. 1. nginx의 access.log에는 해당 uri의 상태코드가 408이라 출력됩니다. 2. nginx의 error.log에는 readv() failed (104: Connection reset by peer) while reading upstream가 출력됩니다. 3. 개발자도구의 console창에 'server의 이미지 업로드 uri' from origin '프론트 도메인'이 has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 이라 출력됩니다. 위 오류를 해결해보기 위해 시도해본 것은 아래와 같습니다 1. NGINX의 proxy_connect_timeout, proxy_send_timeout, proxy_read_timeout, send_timeout을 600으로 설정, client_max_body_size를 500M으로 설정 2. ALB의 유휴제한시간 600 설정 3. Express 앱에 app.set('trust proxy', true); 추가 4. Express-session에 app.use(session({proxy:true})) 추가 5. body-parser에 app.use(bodyParser.json({limit: '1000mb'})); 추가 및 app.use(bodyParser.urlencoded({limit: '1000mb', extended: true})); 추가 6. 업로드되는 uri의 미들웨어에 (req, res, next) => { req.setTimeout(1000000); next(); } 추가 7. upload.single()미들웨어를 주석처리 후 req.file 출력 시도 아직도 해결을 하지 못하였습니다 ㅜㅜ 연휴임에도 도와주시면 너무 감사하겠습니다..
개발자
#aws
#alb
#nginx
#express
#ec2
답변 2
댓글 4
조회 284
일 년 전 · 유택범 님의 답변 업데이트
JSON 데이터 DB 저장
안녕하세요. Java Spring으로 백엔드 서버 마이그레이션, 알람 기능 개발 중에 고민이 있어 질문드립니다. [상황] 기존 알람 서비스는 하나의 서비스 Class로 통합해서 두고, 문자, 푸쉬알람, 이메일 3가지 경우로 기능을 swith하여 각 기능별로 서비스를 호출하는 방식으로 구현되어 있습니다. 알람과 관련된 데이터는 JSON 파일에서 불러와 jsonObejct와 Map 자료구조를 통해 가공하여 기능 구현이 되어있습니다. 알람 데이터 JSON 파일은 상황별 코드에 따라 문자, 푸쉬알람, 이메일로 데이터가 구분되어 있습니다. JSON 파일은 대략적으로 아래와 같은 형태입니다. (mail 알람의 경우엔 HTML 파일을 전송하는 방식입니다. thyemleaf를 통해 전달받은 파라미터 값만 HTML에 주입하는 형태로 구현되어 있어서 msg 안에 String 대신 JSON 형태의 데이터가 들어가고 있습니다.) [알람 JSON 데이터 구조 예시] { "code1" : { "Sms" : { "title" : "sms 제목", "msg" : "sms 메세지", }, "Push" : { "title" : "push alarm 제목", "msg" : "push alarm 메세지", }, "Mail": { "title" : "mail 제목", "msg": { "name" : "#{name}", "use" : "#{use}", ... }, "code2" : {... }, ... } [문제 인지 & 개선 방향 고민] 알람 데이터가 JSON으로 정의되어 있다보니, 기능 개발을 위해 Json 파일을 읽고, JsonObject와 Map 자료구조로 데이터 가공을 할 수 밖에 없는 상황인데요. 제가 알기로는 자바에서 Map 자료구조를 사용하게되면 타입 자체가 불명확해서 지양해야 하며, JSON 파일로 관리 시 하드코딩에 해당되어 기능 자체에 제약이 많다고 판단하여 해당 상황을 DTO로 관리함과 동시에 Database에서 관리하도록 바꿔볼까 생각중입니다. [궁금증 & QnA] 이 상황에서 고민 & 궁금증이 몇 가지 있습니다. 1. 해당 알람 데이터를 DB 관리로 바꾸는게 더 나은 선택인지? -> 만약 바꾼다고 하면 Alarm Table을 따로 두고, User Table과 N:M 매핑하는식으로 구현할 생각입니다. 2. , mail HTML Template이 다양하다보니, Template 마다 넘겨줘야하는 JSON 데이터가 다양한 상황입니다. 그러다보니 mail msg 컬럼에는 다양한 형태의 JSON 데이터가 들어가게 되어 이걸 DB로 구현한다고 하니 막막한 상황입니다. -> 결국 해당 부분만 JSON 데이터로 넣는것 말고는 떠오르는 방법이 없네요.(해당 부분만 JSON 데이터로 넣으면 오히려 관리가 더 어려워지지 않을까 고민입니다.) 3. 다른 분들은 알람 기능을 어떻게 구현하는지 궁금합니다.
개발자
#spring
#database
#json
#fcm
답변 1
댓글 0
조회 293
일 년 전 · 권수경 님의 질문 업데이트
[node.js] 세션을 이용한 로그인 구현 중 브라우저 쿠키의 maxAge 초기화 안 되는 문제 발생
Node.js(v18.17.0) + TypeScript로 개발을 진행하고 있습니다. express 로 서버를 구축했고, express-session , session-file-store 라이브러리를 사용해서 로그인 기능을 구현했습니다. 제가 원하는 것은 세션 만료 시간을 연장하는 기능을 만드는 것인데... 서버에서는 세션 쿠키 만료 시간이 재설정(초기화)가 되는데 브라우저에 저장된 쿠키 만료 시간은 그대로인 상태라 연장이 되지 않습니다. 처음 제 코드는 이렇습니다. ```ts export const extendSession = async (req: Request, res: Response) => { req.session.resetMaxAge(); res.json({ success: true, message: '세션 연장 성공', expiredTime: req.session.cookie.expires.getTime(), }); }; ``` 이런 식으로 resetMaxAge() 함수를 통해서 만료 시간을 연장할 수 있을거라 생각했습니다. (트라이캐치문은 생략하였습니다.) 콘솔에 값을 확인해봤을 때에도 연장이 잘 된 것으로 보이는데 브라우저에 저장된 쿠키의 만료 시간을 확인해보면 최초의 쿠키 값 그대로였습니다. 이를 해결하기 위해서 만료 시간을 따로 정해서 줘보기도 했습니다. `req.session.cookie.maxAge = 3600000;` 하지만 마찬가지로 세션 쿠키의 만료 시간은 바뀌지만 브라우저에 저장된 쿠키의 expires가 변하지 않았습니다. 따로 헤더를 줘보아도 새로운 쿠키가 생성될 뿐 해결책이 되지 않았습니다. `res.setHeader('Set-Cookie', 'Max-Age=3600000')` express-session 미들웨어가 자동으로 set-cookie 헤더를 설정해주어서 제가 여기에 어떻게 접근할 수 있을지 모르겠습니다. 브라우저의 쿠키가 초기화 적용을 받지 못하는 것이 문제인 것 같은데... 원인은 대충 알 것 같으면서 해결책을 모르겠습니다. session 설정은 이렇습니다! ```ts app.use( session({ secret: process.env.SESSION_SECRET_KEY, resave: false, saveUninitialized: false, cookie: { sameSite: 'lax', secure: false, httpOnly: true, maxAge: 1000 * 60 * 5, }, store: new FileStore({ reapInterval: 3000, }), }), ); ```
개발자
#node.js
#express
#express-session
#session
#cookie
답변 0
댓글 0
조회 183
AWS Beanstalk 배포 하기 전에 신경써줘야 할 것이 있나요 ?
현재 nodejs로 백엔드를 구성하고 EB에 배포하는데 몇 가지 오류가 생겨서 질문 드립니다. 프로젝트 구조는 다음과 같습니다. -customer └ .gitignore └ .dockerignore └ src ( 백엔드 코드 ) └ .gitignore └ Dockerfile └ Package.json -product └ customer 와 동일 -shopping └ customer 와 동일 -proxy └ Dockerfile docker-compose.yaml 리전 ap-northeast-2(seoul region) 로컬에서 빌드하고 작동하는 거 확인 후 위 코드를 모두 압축해서 EB에 올렸습니다. EB에 올리기전에 vpc , igw, 등등 미리 생성해줘야 할 게 있나요? 추가로 .gitignore 같은 파일도 같이 올려도 되나요 ? ( 초보라서 질문의 질이 떨어질 수 있지만 고수분들의 너그러운 양해를 부탁드립니다 .ㅠㅠ ) 에러 로그를 보면 1. ELB Fail -> VPC has no internet gateway 2. Stack named 'awseb-e-33wucpgpey-stack' aborted operation. Current state: 'CREATE_FAILED' Reason: The following resource(s) failed to create: [AWSEBV2LoadBalancer, AWSEBInstanceLaunchWaitCondition] 3. LaunchWaitCondition failed. The expected number of EC2 instances were not initialized within the given time. Rebuild the environment. If this persists, contact support.
개발자
#docker
#aws
#nodejs
답변 1
댓글 0
추천해요 1
조회 379
2년 전 · 권혁진 님의 새로운 답변
Next.js API 에서 쿠키를 접근할수 있는 방법이 궁금합니
안녕하세요 nexst.js 쓰고 있는 주니어 프런트엔드 개발자입니다 DB API에 접근하기 위해 next에서도 API를 구성했는데 쿠키에 저장되있는 토큰값을 가져오질 못하고 있네요 방법 알려주시면 감사하겠습니다 아래 파일은 src/pages/api 에 존재합니다 const getOrder = async (token, order_no) => { return await fetch(`${process.env.NEXT_PUBLIC_API_HOST}/front/order/${order_no}`, { headers: { Authorization: `Bearer ${token}`, Accept: "application/json" } }).then(res => res.json()).then(data => data) } export default async function handler(req, res) { const { order_no } = req.query const token = "쿠키에 어떻게 접근해야 하나요???" const order = await getOrder(token, order_no) try { res.status(200).json({ ...order }) } catch (err) { res.status(200).json({ err }) } }
개발자
#next.js
#restapi
#cookie
답변 2
댓글 2
조회 729
2년 전 · 태진 님의 새로운 댓글
Nextjs async 서버컴포넌트 질문있습니다.
// page.tsx import WeatherList from "@/components/containers/main/WeatherList"; export default function Home({ searchParams, }: { searchParams: { country: string }; }) { const country = searchParams.country ? searchParams.country : "kr"; return ( <> <WeatherList country={country} /> </> ); } // server component export default async function WeatherList({ country }: { country: string }) { const res = await fetch( `http://localhost:3000/api/cityList?country=${country}` ); const cityList = await res.json(); return ( ... ); } 'WeatherList' cannot be used as a JSX component. page.tsx 에서 WeatherList 컴포넌트부분에 위와같은 에러가 뜹니다.. @types/react 를 18.2.8 버전으로 바꿔줘도 해결이안되는데 저와 같은분 계실까요?
개발자
#next.js
답변 2
댓글 2
조회 405
7달 전 · 성지수 님의 질문
micro repo 세팅하면서 격은 문제(같은 문제 격는 분들 댁글)
이번 프로젝트에서 하나의 레포지토리에서 client, server, admin, common 4가지 패키지를 만들었습니다. client, server, admin은 common을 의존하도록 모노레포로 만들었고, client는 admin을 의존할 수 있게 micro로 만들었습니다. 간단한 패키지 설명: - client: 메뉴 헤더 등 구현, 페이지는 admin을 remote 해서 사용, React로 구현 - admin: 페이지에 나오는 콘텐츠의 전반적인 부분이 컴포넌트로 되어있음, React로 구현 - server: Node.js로 되어있고 실제 Spring 서버에서 준 데이터를 포맷하는 형태 - common: 공통 컴포넌트, 라벨 등 문제1: 다른 프로젝트에서 expose 되어있는 Next.js 프로젝트(scss로 스타일 구현)를 client에서 사용할 때 의존성 관련 오류가 생깁니다. client의 package.json에서 peerDependencies로 next를 설정해줘야 하는지, 양쪽 패키지(다른 프로젝트와 client)에서 Next.js와 React를 share 설정을 해야 되는지 잘 모르겠습니다. 여러 방법으로 시도는 해봤지만 의존성 오류나 Next.js에서 훅을 사용 못하는 오류 때문에 해결하지 못하고 있습니다. 문제2: 빌드 최적화를 위해 트리쉐이킹이나 코드 스플리팅을 해야 합니다. 웹팩에서 아래와 같이 코드 스플리팅을 하면 청크 파일 이름이 겹치기 때문에 filename을 해시값으로 설정해야 합니다. 여기서 문제가 생기는데, micro의 client처럼 remote 하는 부분에서 remote.js, app.js 청크가 필요하기 때문에 이름이 해시값으로 바뀌면 해당 청크를 찾을 수 없습니다. 또한 ModuleFederationPlugin이 빌드 시 자동으로 코드 스플리팅을 해준다는 이야기도 있는데, 이 부분은 정확하지 않습니다. 저와 같은 문제를 격고 있거나 해결하신 분들 같이 나눴으면 합니다.
개발자
#micro
#react
#monorepo
#nextjs
#build
답변 0
댓글 0
조회 26
자바스크립트 협업시 궁금한 점이 있습니다!
자바스크립트로 프론트 1명과 백엔드 1명이 협업으로 토이프로젝트를 한다고 가정했을때요, (포폴용 협업 플젝 느낌..) nodejs랑 express로 port 설정해서 app.use() 같은 작업은 백엔드가 하는건가요..? 혼자 프로젝트를 하면서 프론트부터 nodejs 까지 다 만지다보니.. 뭔가 좀 구분하는게 어렵습니다.. 예를들어, router.js 파일에는 app.route("/board").get(function).post(function2); 이런 코드가 있고 controller.js 파일에는 const renderBoard = () => { return res.render("board"); }; 이렇게 board를 render 해주는 코드가 있다고 치면요.. renderBoard 함수는 말 그대로 board라는 템플릿을 화면에 뿌려주는거니까 프론트 개발자가 작업하고 app.route ~ 코드는 서버와 통신하는거니까 백엔드가 작성하는건가요?? 그래서 협업할 땐, 프론트 왈: "야 /board get 할때 함수 이름 function이고, post 할땐 function2다" 아니면 반대로, 백엔드 왈: "야 /board get함수 이름 function 이고 post는 function2 니까 헷갈리지 말고 짜라!" 라고 말하는건가요... 죄송합니다.. 말로 설명하면 깔끔할 것 같은데 글로 쓰니까 좀 이상하네요... restful api가 결국엔 서버와 통신을 하기 위한건데.. 만약, route하는 코드부터 get, post할 때 작동할 function 까지 전부 백엔드가 작업하는거라면, 프론트는 html, css + 동적웹을 위한 js를 해주면 되는건가요? 제가 배운거로는.. pug같은거 사용해서 템플릿 뿌려준다거나, scss를 적용한다거나.. 물론 이때 필요한 npm 사용법이나, package.json 설정법 등은 프론트가 해야겠죠!! 아직 협업 경험이 없다보니.. 실제로 협업을 하게되면 어떻게 업무를 나누는지 궁금합니다. 글이 좀 두서없네요. 아으.. 근데 이 뭔가 가려운 부분을 긁어주고싶습니다ㅜㅜ 선배님들 조언 부탁드립니다.. 답변 미리 감사드립니다!!!
개발자
#javascript
#frontend
#node.js
#express.js
#backend
답변 1
댓글 1
조회 369
일 년 전 · 강병진 님의 새로운 답변
리액트 로그인 jwt 구현중 나타나는 에러
제가 jwt를 통해서 로그인을 구현하고 있는데요. 서버에서 jwt토큰을 가져오면 그 토큰을 디코딩해서 사용하고 있습니다. 로그인 코드를 짜놓고 실행을 시키면 저렇게 에러 코드가 나오고 let payload = testGetCK.substring(testGetCK.indexOf('.')+1,testGetCK.lastIndexOf('.')); let dec = JSON.parse(base64.decode(payload)); console.log(dec.id); console.log(dec.nickname); 이부분을 지우면 화면이 잘 나타나게 되고 저상태로 아이디랑 비밀번호를 넣어 로그인 버튼을 누르면 jwt토큰이 잘 들어옵니다. 토큰이 들어온 상태서 다시 let payload = testGetCK.substring(testGetCK.indexOf('.')+1,testGetCK.lastIndexOf('.')); let dec = JSON.parse(base64.decode(payload)); console.log(dec.id); console.log(dec.nickname); 이 코드를 넣으면 작동이 되는데 예외 처리를 해서 작업을 해야하나요??..
개발자
#react
#로그인
#jwt토큰
#jwt로그인
#jwt
답변 1
댓글 0
조회 428
일 년 전 · 강병진 님의 새로운 답변
React 환경 변수 설정 파일 사용 (.env 파일) 관하여
환경에 따라서 경로를 다르게 설정하기 위해서 다음과 같이 .env 파일을 정의하였습니다. 하지만 사용하는 곳에서 console.log 확인해 보니 정의한 주소로 나오지 않습니다. 무엇이 잘못 되었을까요? // .env.development API = https://m.j-sone.com:6420 URL_BASE = https://m.j-sone.com:6420/game_down/fgt-game // .env.production API = https://w.j-sone.com:6421 URL_BASE = https://jsonecdn.j-sone.com/fgt-game // common.ts export const API = process.env.API; export const URL_BASE = process.env.URL_BASE; console.log(API) => http://localhost:3000/undefined
개발자
#react
#node.js
답변 1
댓글 0
조회 297
일 년 전 · 익명 님의 질문
[Django] TypeError: Object of type InMemoryUploadedFile is not JSON serializable 오류
안녕하세요 장고를 통해 api를 개발하다가 TypeError: Object of type InMemoryUploadedFile is not JSON serializable 오류가 발생했습니다. 해당 오류에 대한 내용을 아래의 링크에 정리해놨습니다 보고 답변해주시면 감사하겠습니다!! https://luminous-kitty-425.notion.site/Django-TypeError-Object-of-type-InMemoryUploadedFile-is-not-JSON-serializable-37a91af3ff2f475e88af0b6cadcb0699
개발자
#django
답변 0
댓글 0
조회 104
일 년 전 · 익명 님의 질문
POST Body 가 간헐적으로 잘려서 들어옵니다.
App (react-native) 에서 RNFS 로 여러장의 이미지를 base64로 변환하여 post body 에 넣어 요청을 보냅니다. 하지만 간헐적으로 서버 (spring boot) 에서 post body 가 잘려서 들어오고 EXCEPTION : org.springframework.http.converter.HttpMessageNotReadableException ERROR MESSAGE : JSON parse error: java.io.EOFException 아래와 같은 에러가 발생합니다. 동일 이미지들을 다시 base64로 변환하여 요청하면 대부분 성공합니다. 어떤 문제일까요? spring boot yml 에는 아래와 같이 설정해두었습니다. server: port: tomcat: connection-timeout: 1800000 max-http-post-size: 100MB max-swallow-size: 100MB threads: max:
개발자
#react-native
#spring-boot
답변 0
댓글 0
추천해요 1
보충이 필요해요 1
조회 127
일 년 전 · 김대근 님의 새로운 답변
WSL 주피터노트북 실행시 메시지 원인
ipped non-installed server(s): bash-language-server, dockerfile-language-server-nodejs, javascript-typescript-langserver, jedi-language-server, julia-language-server, pyright, python-language-server, python-lsp-server, r-languageserver, sql-language-server, texlab, typescript-language-server, unified-language-server, vscode-css-languageserver-bin, vscode-html-languageserver-bin, vscode-json-languageserver-bin, yaml-language-server 라는 메시지가 wsl 주피터 실행시 자꾸뜨는데 원인이뭔지모르겠어요ㅠㅠ 커널이자꾸죽는이유와도 연관있을까요
개발자
#wsl
#jupyter-notebook
#인공지능
#pytorch
#linux
답변 1
댓글 0
보충이 필요해요 2
조회 420